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}