    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017package org.apache.bcel.util;
    018
    019import java.io.IOException;
    020import java.io.OutputStream;
    021import java.io.OutputStreamWriter;
    022import java.io.PrintWriter;
    023import java.nio.charset.StandardCharsets;
    024import java.util.Locale;
    025
    026import org.apache.bcel.Const;
    027import org.apache.bcel.Repository;
    028import org.apache.bcel.classfile.ClassParser;
    029import org.apache.bcel.classfile.ConstantValue;
    030import org.apache.bcel.classfile.Field;
    031import org.apache.bcel.classfile.JavaClass;
    032import org.apache.bcel.classfile.Method;
    033import org.apache.bcel.classfile.Utility;
    034import org.apache.bcel.generic.ArrayType;
    035import org.apache.bcel.generic.ConstantPoolGen;
    036import org.apache.bcel.generic.MethodGen;
    037import org.apache.bcel.generic.Type;
    038import org.apache.commons.lang3.StringUtils;
    039
    040/**
    041 * This class takes a given JavaClass object and converts it to a Java program that creates that very class using BCEL.
    042 * This gives new users of BCEL a useful example showing how things are done with BCEL. It does not cover all features
    043 * of BCEL, but tries to mimic hand-written code as close as possible.
    044 */
    045public class BCELifier extends org.apache.bcel.classfile.EmptyVisitor {
    046
    047    /**
    048     * Enum corresponding to flag source.
    049     */
    050    public enum FLAGS {
    051        UNKNOWN, CLASS, METHOD,
    052    }
    053
    054    // The base package name for imports; assumes Const is at the top level
    055    // N.B we use the class so renames will be detected by the compiler/IDE
    056    private static final String BASE_PACKAGE = Const.class.getPackage().getName();
    057    private static final String CONSTANT_PREFIX = Const.class.getSimpleName() + ".";
    058
    059    // Needs to be accessible from unit test code
    060    static JavaClass getJavaClass(final String name) throws ClassNotFoundException, IOException {
    061        JavaClass javaClass;
    062        if ((javaClass = Repository.lookupClass(name)) == null) {
    063            javaClass = new ClassParser(name).parse(); // May throw IOException
    064        }
    065        return javaClass;
    066    }
    067
    068    /**
    069     * Default main method
    070     */
    071    public static void main(final String[] argv) throws Exception {
    072        if (argv.length != 1) {
    073            System.out.println("Usage: BCELifier className");
    074            System.out.println("\tThe class must exist on the classpath");
    075            return;
    076        }
    077        final BCELifier bcelifier = new BCELifier(getJavaClass(argv[0]), System.out);
    078        bcelifier.start();
    079    }
    080
    081    static String printArgumentTypes(final Type[] argTypes) {
    082        if (argTypes.length == 0) {
    083            return "Type.NO_ARGS";
    084        }
    085        final StringBuilder args = new StringBuilder();
    086        for (int i = 0; i < argTypes.length; i++) {
    087            args.append(printType(argTypes[i]));
    088            if (i < argTypes.length - 1) {
    089                args.append(", ");
    090            }
    091        }
    092        return "new Type[] { " + args.toString() + " }";
    093    }
    094
    095    static String printFlags(final int flags) {
    096        return printFlags(flags, FLAGS.UNKNOWN);
    097    }
    098
    099    /**
    100     * Return a string with the flag settings
    101     *
    102     * @param flags the flags field to interpret
    103     * @param location the item type
    104     * @return the formatted string
    105     * @since 6.0 made public
    106     */
    107    public static String printFlags(final int flags, final FLAGS location) {
    108        if (flags == 0) {
    109            return "0";
    110        }
    111        final StringBuilder buf = new StringBuilder();
    112        for (int i = 0, pow = 1; pow <= Const.MAX_ACC_FLAG_I; i++) {
    113            if ((flags & pow) != 0) {
    114                if (pow == Const.ACC_SYNCHRONIZED && location == FLAGS.CLASS) {
    115                    buf.append(CONSTANT_PREFIX).append("ACC_SUPER | ");
    116                } else if (pow == Const.ACC_VOLATILE && location == FLAGS.METHOD) {
    117                    buf.append(CONSTANT_PREFIX).append("ACC_BRIDGE | ");
    118                } else if (pow == Const.ACC_TRANSIENT && location == FLAGS.METHOD) {
    119                    buf.append(CONSTANT_PREFIX).append("ACC_VARARGS | ");
    120                } else if (i < Const.ACCESS_NAMES_LENGTH) {
    121                    buf.append(CONSTANT_PREFIX).append("ACC_").append(Const.getAccessName(i).toUpperCase(Locale.ENGLISH)).append(" | ");
    122                } else {
    123                    buf.append(String.format(CONSTANT_PREFIX + "ACC_BIT %x | ", pow));
    124                }
    125            }
    126            pow <<= 1;
    127        }
    128        final String str = buf.toString();
    129        return str.substring(0, str.length() - 3);
    130    }
    131
    132    static String printType(final String signature) {
    133        final Type type = Type.getType(signature);
    134        final byte t = type.getType();
    135        if (t <= Const.T_VOID) {
    136            return "Type." + Const.getTypeName(t).toUpperCase(Locale.ENGLISH);
    137        }
    138        if (type.toString().equals("java.lang.String")) {
    139            return "Type.STRING";
    140        }
    141        if (type.toString().equals("java.lang.Object")) {
    142            return "Type.OBJECT";
    143        }
    144        if (type.toString().equals("java.lang.StringBuffer")) {
    145            return "Type.STRINGBUFFER";
    146        }
    147        if (type instanceof ArrayType) {
    148            final ArrayType at = (ArrayType) type;
    149            return "new ArrayType(" + printType(at.getBasicType()) + ", " + at.getDimensions() + ")";
    150        }
    151        return "new ObjectType(\"" + Utility.signatureToString(signature, false) + "\")";
    152    }
    153
    154    static String printType(final Type type) {
    155        return printType(type.getSignature());
    156    }
    157
    158    private final JavaClass clazz;
    159
    160    private final PrintWriter printWriter;
    161
    162    private final ConstantPoolGen constantPoolGen;
    163
    164    /**
    165     * Constructs a new instance.
    166     *
    167     * @param clazz Java class to "decompile".
    168     * @param out where to print the Java program in UTF-8.
    169     */
    170    public BCELifier(final JavaClass clazz, final OutputStream out) {
    171        this.clazz = clazz;
    172        this.printWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), false);
    173        this.constantPoolGen = new ConstantPoolGen(this.clazz.getConstantPool());
    174    }
    175
    176    private void printCreate() {
    177        printWriter.println("  public void create(OutputStream out) throws IOException {");
    178        final Field[] fields = clazz.getFields();
    179        if (fields.length > 0) {
    180            printWriter.println("    createFields();");
    181        }
    182        final Method[] methods = clazz.getMethods();
    183        for (int i = 0; i < methods.length; i++) {
    184            printWriter.println("    createMethod_" + i + "();");
    185        }
    186        printWriter.println("    _cg.getJavaClass().dump(out);");
    187        printWriter.println("  }");
    188        printWriter.println();
    189    }
    190
    191    private void printMain() {
    192        final String className = clazz.getClassName();
    193        printWriter.println("  public static void main(String[] args) throws Exception {");
    194        printWriter.println("    " + className + "Creator creator = new " + className + "Creator();");
    195        printWriter.println("    creator.create(new FileOutputStream(\"" + className + ".class\"));");
    196        printWriter.println("  }");
    197    }
    198
    199    /**
    200     * Start Java code generation
    201     */
    202    public void start() {
    203        visitJavaClass(clazz);
    204        printWriter.flush();
    205    }
    206
    207    @Override
    208    public void visitField(final Field field) {
    209        printWriter.println();
    210        printWriter.println(
    211            "    field = new FieldGen(" + printFlags(field.getAccessFlags()) + ", " + printType(field.getSignature()) + ", \"" + field.getName() + "\", _cp);");
    212        final ConstantValue cv = field.getConstantValue();
    213        if (cv != null) {
    214            printWriter.println("    field.setInitValue(" + cv + ")");
    215        }
    216        printWriter.println("    _cg.addField(field.getField());");
    217    }
    218
    219    @Override
    220    public void visitJavaClass(final JavaClass clazz) {
    221        String className = clazz.getClassName();
    222        final String superName = clazz.getSuperclassName();
    223        final String packageName = clazz.getPackageName();
    224        final String inter = Utility.printArray(clazz.getInterfaceNames(), false, true);
    225        if (StringUtils.isNotEmpty(inter)) {
    226            className = className.substring(packageName.length() + 1);
    227            printWriter.println("package " + packageName + ";");
    228            printWriter.println();
    229        }
    230        printWriter.println("import " + BASE_PACKAGE + ".generic.*;");
    231        printWriter.println("import " + BASE_PACKAGE + ".classfile.*;");
    232        printWriter.println("import " + BASE_PACKAGE + ".*;");
    233        printWriter.println("import java.io.*;");
    234        printWriter.println();
    235        printWriter.println("public class " + className + "Creator {");
    236        printWriter.println("  private InstructionFactory _factory;");
    237        printWriter.println("  private ConstantPoolGen    _cp;");
    238        printWriter.println("  private ClassGen           _cg;");
    239        printWriter.println();
    240        printWriter.println("  public " + className + "Creator() {");
    241        printWriter.println("    _cg = new ClassGen(\"" + (packageName.isEmpty() ? className : packageName + "." + className) + "\", \"" + superName
    242            + "\", " + "\"" + clazz.getSourceFileName() + "\", " + printFlags(clazz.getAccessFlags(), FLAGS.CLASS) + ", " + "new String[] { " + inter + " });");
    243        printWriter.println("    _cg.setMajor(" + clazz.getMajor() + ");");
    244        printWriter.println("    _cg.setMinor(" + clazz.getMinor() + ");");
    245        printWriter.println();
    246        printWriter.println("    _cp = _cg.getConstantPool();");
    247        printWriter.println("    _factory = new InstructionFactory(_cg, _cp);");
    248        printWriter.println("  }");
    249        printWriter.println();
    250        printCreate();
    251        final Field[] fields = clazz.getFields();
    252        if (fields.length > 0) {
    253            printWriter.println("  private void createFields() {");
    254            printWriter.println("    FieldGen field;");
    255            for (final Field field : fields) {
    256                field.accept(this);
    257            }
    258            printWriter.println("  }");
    259            printWriter.println();
    260        }
    261        final Method[] methods = clazz.getMethods();
    262        for (int i = 0; i < methods.length; i++) {
    263            printWriter.println("  private void createMethod_" + i + "() {");
    264            methods[i].accept(this);
    265            printWriter.println("  }");
    266            printWriter.println();
    267        }
    268        printMain();
    269        printWriter.println("}");
    270    }
    271
    272    @Override
    273    public void visitMethod(final Method method) {
    274        final MethodGen mg = new MethodGen(method, clazz.getClassName(), constantPoolGen);
    275        printWriter.println("    InstructionList il = new InstructionList();");
    276        printWriter.println("    MethodGen method = new MethodGen(" + printFlags(method.getAccessFlags(), FLAGS.METHOD) + ", " + printType(mg.getReturnType())
    277            + ", " + printArgumentTypes(mg.getArgumentTypes()) + ", " + "new String[] { " + Utility.printArray(mg.getArgumentNames(), false, true) + " }, \""
    278            + method.getName() + "\", \"" + clazz.getClassName() + "\", il, _cp);");
    279        printWriter.println();
    280        final BCELFactory factory = new BCELFactory(mg, printWriter);
    281        factory.start();
    282        printWriter.println("    method.setMaxStack();");
    283        printWriter.println("    method.setMaxLocals();");
    284        printWriter.println("    _cg.addMethod(method.getMethod());");
    285        printWriter.println("    il.dispose();");
    286    }
    287}