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}