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