    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.ExceptionTable;
    031import org.apache.bcel.classfile.Field;
    032import org.apache.bcel.classfile.JavaClass;
    033import org.apache.bcel.classfile.Method;
    034import org.apache.bcel.classfile.Utility;
    035import org.apache.bcel.generic.ArrayType;
    036import org.apache.bcel.generic.ConstantPoolGen;
    037import org.apache.bcel.generic.MethodGen;
    038import org.apache.bcel.generic.Type;
    039import org.apache.commons.lang3.StringUtils;
    040
    041/**
    042 * This class takes a given JavaClass object and converts it to a Java program that creates that very class using BCEL.
    043 * This gives new users of BCEL a useful example showing how things are done with BCEL. It does not cover all features
    044 * of BCEL, but tries to mimic hand-written code as close as possible.
    045 */
    046public class BCELifier extends org.apache.bcel.classfile.EmptyVisitor {
    047
    048    /**
    049     * Enum corresponding to flag source.
    050     */
    051    public enum FLAGS {
    052        UNKNOWN, CLASS, METHOD,
    053    }
    054
    055    // The base package name for imports; assumes Const is at the top level
    056    // N.B we use the class so renames will be detected by the compiler/IDE
    057    private static final String BASE_PACKAGE = Const.class.getPackage().getName();
    058    private static final String CONSTANT_PREFIX = Const.class.getSimpleName() + ".";
    059
    060    // Needs to be accessible from unit test code
    061    static JavaClass getJavaClass(final String name) throws ClassNotFoundException, IOException {
    062        JavaClass javaClass;
    063        if ((javaClass = Repository.lookupClass(name)) == null) {
    064            javaClass = new ClassParser(name).parse(); // May throw IOException
    065        }
    066        return javaClass;
    067    }
    068
    069    /**
    070     * Default main method
    071     */
    072    public static void main(final String[] argv) throws Exception {
    073        if (argv.length != 1) {
    074            System.out.println("Usage: BCELifier className");
    075            System.out.println("\tThe class must exist on the classpath");
    076            return;
    077        }
    078        final BCELifier bcelifier = new BCELifier(getJavaClass(argv[0]), System.out);
    079        bcelifier.start();
    080    }
    081
    082    static String printArgumentTypes(final Type[] argTypes) {
    083        if (argTypes.length == 0) {
    084            return "Type.NO_ARGS";
    085        }
    086        final StringBuilder args = new StringBuilder();
    087        for (int i = 0; i < argTypes.length; i++) {
    088            args.append(printType(argTypes[i]));
    089            if (i < argTypes.length - 1) {
    090                args.append(", ");
    091            }
    092        }
    093        return "new Type[] { " + args.toString() + " }";
    094    }
    095
    096    static String printFlags(final int flags) {
    097        return printFlags(flags, FLAGS.UNKNOWN);
    098    }
    099
    100    /**
    101     * Return a string with the flag settings
    102     *
    103     * @param flags the flags field to interpret
    104     * @param location the item type
    105     * @return the formatted string
    106     * @since 6.0 made public
    107     */
    108    public static String printFlags(final int flags, final FLAGS location) {
    109        if (flags == 0) {
    110            return "0";
    111        }
    112        final StringBuilder buf = new StringBuilder();
    113        for (int i = 0, pow = 1; pow <= Const.MAX_ACC_FLAG_I; i++) {
    114            if ((flags & pow) != 0) {
    115                if (pow == Const.ACC_SYNCHRONIZED && location == FLAGS.CLASS) {
    116                    buf.append(CONSTANT_PREFIX).append("ACC_SUPER | ");
    117                } else if (pow == Const.ACC_VOLATILE && location == FLAGS.METHOD) {
    118                    buf.append(CONSTANT_PREFIX).append("ACC_BRIDGE | ");
    119                } else if (pow == Const.ACC_TRANSIENT && location == FLAGS.METHOD) {
    120                    buf.append(CONSTANT_PREFIX).append("ACC_VARARGS | ");
    121                } else if (i < Const.ACCESS_NAMES_LENGTH) {
    122                    buf.append(CONSTANT_PREFIX).append("ACC_").append(Const.getAccessName(i).toUpperCase(Locale.ENGLISH)).append(" | ");
    123                } else {
    124                    buf.append(String.format(CONSTANT_PREFIX + "ACC_BIT %x | ", pow));
    125                }
    126            }
    127            pow <<= 1;
    128        }
    129        final String str = buf.toString();
    130        return str.substring(0, str.length() - 3);
    131    }
    132
    133    static String printType(final String signature) {
    134        final Type type = Type.getType(signature);
    135        final byte t = type.getType();
    136        if (t <= Const.T_VOID) {
    137            return "Type." + Const.getTypeName(t).toUpperCase(Locale.ENGLISH);
    138        }
    139        if (type.toString().equals("java.lang.String")) {
    140            return "Type.STRING";
    141        }
    142        if (type.toString().equals("java.lang.Object")) {
    143            return "Type.OBJECT";
    144        }
    145        if (type.toString().equals("java.lang.StringBuffer")) {
    146            return "Type.STRINGBUFFER";
    147        }
    148        if (type instanceof ArrayType) {
    149            final ArrayType at = (ArrayType) type;
    150            return "new ArrayType(" + printType(at.getBasicType()) + ", " + at.getDimensions() + ")";
    151        }
    152        return "new ObjectType(\"" + Utility.signatureToString(signature, false) + "\")";
    153    }
    154
    155    static String printType(final Type type) {
    156        return printType(type.getSignature());
    157    }
    158
    159    private final JavaClass clazz;
    160
    161    private final PrintWriter printWriter;
    162
    163    private final ConstantPoolGen constantPoolGen;
    164
    165    /**
    166     * Constructs a new instance.
    167     *
    168     * @param clazz Java class to "decompile".
    169     * @param out where to print the Java program in UTF-8.
    170     */
    171    public BCELifier(final JavaClass clazz, final OutputStream out) {
    172        this.clazz = clazz;
    173        this.printWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), false);
    174        this.constantPoolGen = new ConstantPoolGen(this.clazz.getConstantPool());
    175    }
    176
    177    private void printCreate() {
    178        printWriter.println("  public void create(OutputStream out) throws IOException {");
    179        final Field[] fields = clazz.getFields();
    180        if (fields.length > 0) {
    181            printWriter.println("    createFields();");
    182        }
    183        final Method[] methods = clazz.getMethods();
    184        for (int i = 0; i < methods.length; i++) {
    185            printWriter.println("    createMethod_" + i + "();");
    186        }
    187        printWriter.println("    _cg.getJavaClass().dump(out);");
    188        printWriter.println("  }");
    189        printWriter.println();
    190    }
    191
    192    private void printMain() {
    193        final String className = clazz.getClassName();
    194        printWriter.println("  public static void main(String[] args) throws Exception {");
    195        printWriter.println("    " + className + "Creator creator = new " + className + "Creator();");
    196        printWriter.println("    creator.create(new FileOutputStream(\"" + className + ".class\"));");
    197        printWriter.println("  }");
    198    }
    199
    200    /**
    201     * Start Java code generation
    202     */
    203    public void start() {
    204        visitJavaClass(clazz);
    205        printWriter.flush();
    206    }
    207
    208    @Override
    209    public void visitField(final Field field) {
    210        printWriter.println();
    211        printWriter.println(
    212            "    field = new FieldGen(" + printFlags(field.getAccessFlags()) + ", " + printType(field.getSignature()) + ", \"" + field.getName() + "\", _cp);");
    213        final ConstantValue cv = field.getConstantValue();
    214        if (cv != null) {
    215            printWriter.print("    field.setInitValue(");
    216            if (field.getType() == Type.CHAR) {
    217                printWriter.print("(char)");
    218            }
    219            if (field.getType() == Type.SHORT) {
    220                printWriter.print("(short)");
    221            }
    222            if (field.getType() == Type.BYTE) {
    223                printWriter.print("(byte)");
    224            }
    225            printWriter.print(cv);
    226            if (field.getType() == Type.LONG) {
    227                printWriter.print("L");
    228            }
    229            if (field.getType() == Type.FLOAT) {
    230                printWriter.print("F");
    231            }
    232            if (field.getType() == Type.DOUBLE) {
    233                printWriter.print("D");
    234            }
    235            printWriter.println(");");
    236        }
    237        printWriter.println("    _cg.addField(field.getField());");
    238    }
    239
    240    @Override
    241    public void visitJavaClass(final JavaClass clazz) {
    242        String className = clazz.getClassName();
    243        final String superName = clazz.getSuperclassName();
    244        final String packageName = clazz.getPackageName();
    245        final String inter = Utility.printArray(clazz.getInterfaceNames(), false, true);
    246        if (StringUtils.isNotEmpty(packageName)) {
    247            className = className.substring(packageName.length() + 1);
    248            printWriter.println("package " + packageName + ";");
    249            printWriter.println();
    250        }
    251        printWriter.println("import " + BASE_PACKAGE + ".generic.*;");
    252        printWriter.println("import " + BASE_PACKAGE + ".classfile.*;");
    253        printWriter.println("import " + BASE_PACKAGE + ".*;");
    254        printWriter.println("import java.io.*;");
    255        printWriter.println();
    256        printWriter.println("public class " + className + "Creator {");
    257        printWriter.println("  private InstructionFactory _factory;");
    258        printWriter.println("  private ConstantPoolGen    _cp;");
    259        printWriter.println("  private ClassGen           _cg;");
    260        printWriter.println();
    261        printWriter.println("  public " + className + "Creator() {");
    262        printWriter.println("    _cg = new ClassGen(\"" + (packageName.isEmpty() ? className : packageName + "." + className) + "\", \"" + superName
    263            + "\", " + "\"" + clazz.getSourceFileName() + "\", " + printFlags(clazz.getAccessFlags(), FLAGS.CLASS) + ", " + "new String[] { " + inter + " });");
    264        printWriter.println("    _cg.setMajor(" + clazz.getMajor() + ");");
    265        printWriter.println("    _cg.setMinor(" + clazz.getMinor() + ");");
    266        printWriter.println();
    267        printWriter.println("    _cp = _cg.getConstantPool();");
    268        printWriter.println("    _factory = new InstructionFactory(_cg, _cp);");
    269        printWriter.println("  }");
    270        printWriter.println();
    271        printCreate();
    272        final Field[] fields = clazz.getFields();
    273        if (fields.length > 0) {
    274            printWriter.println("  private void createFields() {");
    275            printWriter.println("    FieldGen field;");
    276            for (final Field field : fields) {
    277                field.accept(this);
    278            }
    279            printWriter.println("  }");
    280            printWriter.println();
    281        }
    282        final Method[] methods = clazz.getMethods();
    283        for (int i = 0; i < methods.length; i++) {
    284            printWriter.println("  private void createMethod_" + i + "() {");
    285            methods[i].accept(this);
    286            printWriter.println("  }");
    287            printWriter.println();
    288        }
    289        printMain();
    290        printWriter.println("}");
    291    }
    292
    293    @Override
    294    public void visitMethod(final Method method) {
    295        final MethodGen mg = new MethodGen(method, clazz.getClassName(), constantPoolGen);
    296        printWriter.println("    InstructionList il = new InstructionList();");
    297        printWriter.println("    MethodGen method = new MethodGen(" + printFlags(method.getAccessFlags(), FLAGS.METHOD) + ", " + printType(mg.getReturnType())
    298            + ", " + printArgumentTypes(mg.getArgumentTypes()) + ", " + "new String[] { " + Utility.printArray(mg.getArgumentNames(), false, true) + " }, \""
    299            + method.getName() + "\", \"" + clazz.getClassName() + "\", il, _cp);");
    300        final ExceptionTable exceptionTable = method.getExceptionTable();
    301        if (exceptionTable != null) {
    302            final String[] exceptionNames = exceptionTable.getExceptionNames();
    303            for (final String exceptionName : exceptionNames) {
    304                printWriter.print("    method.addException(\"");
    305                printWriter.print(exceptionName);
    306                printWriter.println("\");");
    307            }
    308        }
    309        printWriter.println();
    310        final BCELFactory factory = new BCELFactory(mg, printWriter);
    311        factory.start();
    312        printWriter.println("    method.setMaxStack();");
    313        printWriter.println("    method.setMaxLocals();");
    314        printWriter.println("    _cg.addMethod(method.getMethod());");
    315        printWriter.println("    il.dispose();");
    316    }
    317}