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}