/* * Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.rjeschke.weel; import java.io.UnsupportedEncodingException; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; /** * Class for writing Java .class files. * * @author René Jeschke <rene_jeschke@yahoo.de> */ final class JvmClassWriter { /** .class file version 50.0 */ private final static int CLASS_VERSION = 0x00320000; /** Constant pool. */ private ArrayList<JvmConstant> constants = new ArrayList<JvmConstant>(); /** Constant pool hashmap. */ private HashMap<JvmConstant, Integer> mapConstants = new HashMap<JvmConstant, Integer>(); /** Methods. */ private ArrayList<JvmMethodWriter> methods = new ArrayList<JvmMethodWriter>(); /** The full class name. */ final String className; /** * Constructor. * * @param className * The full class name. */ public JvmClassWriter(final String className) { // Insert unused constant #0 this.constants.add(null); this.className = className; // Put class constant this.addConstant(new JvmConstant(JvmConstant.CONSTANT_Class, this .addConstant(new JvmConstant(className.replace('.', '/'))))); // Put 'Code' constant this.addConstant(new JvmConstant("Code")); // Put super class constant this.addConstant(new JvmConstant(JvmConstant.CONSTANT_Class, this .addConstant(new JvmConstant("java/lang/Object")))); // Create default constructor final JvmMethodWriter mw = this.createMethod("<init>", "()V", Modifier.PRIVATE); mw.aload(0); mw.invokeSpecial("java.lang.Object", "<init>", "()V"); mw.addOp(JvmOp.RETURN); } /** * Check if this class writer has any other methods than '<init>'. * * @return <code>true</code> if so. */ public boolean hasMethods() { return this.methods.size() > 1; } /** * Creates a method. * * @param methodName * The name of the method. * @param descriptor * The descriptor * @return A JvmMethodWriter. */ public JvmMethodWriter createMethod(final String methodName, final String descriptor) { return this.createMethod(methodName, descriptor, Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC); } /** * Creates a method. * * @param methodName * The name of the method. * @param descriptor * The descriptor * @param access * The access modifiers. * @return A JvmMethodWriter. */ public JvmMethodWriter createMethod(final String methodName, final String descriptor, final int access) { final JvmMethodWriter mw = new JvmMethodWriter(this, methodName, descriptor, access); mw.nameIndex = this.addConstant(new JvmConstant(methodName)); mw.descriptorIndex = this.addConstant(new JvmConstant(descriptor)); this.methods.add(mw); return mw; } /** * Adds a constant. * * @param c * The constant. * @return The index of this constant in the constant pool. */ public int addConstant(final JvmConstant c) { final Integer t = this.mapConstants.get(c); if(t != null) return t; final int idx = this.constants.size(); this.mapConstants.put(c, idx); this.constants.add(c); if(c.type == JvmConstant.CONSTANT_Double || c.type == JvmConstant.CONSTANT_Long) this.constants.add(null); return idx; } public int addMethodRefConstant(final String className, final String name, final String type) { final int c = this.addConstant(new JvmConstant( JvmConstant.CONSTANT_Class, this.addConstant(new JvmConstant( className.replace('.', '/'))))); return this.addConstant(new JvmConstant(JvmConstant.CONSTANT_Methodref, c, this.addConstant(new JvmConstant( JvmConstant.CONSTANT_NameAndType, this .addConstant(new JvmConstant(name)), this .addConstant(new JvmConstant(type)))))); } /** * Builds a .class file. * * @return A byte array containing the .class file. */ public byte[] build() { final ByteList bytes = new ByteList(); try { // .class header bytes.addInteger(0xcafebabe); bytes.addShort(CLASS_VERSION); bytes.addShort(CLASS_VERSION >> 16); // write constants bytes.addShort(this.constants.size()); for(int i = 1; i < this.constants.size(); i++) { final JvmConstant c = this.constants.get(i); bytes.add(c.type); switch(c.type) { case JvmConstant.CONSTANT_Utf8: { final byte[] str = c.stringValue.getBytes("UTF-8"); bytes.addShort(str.length); for(int n = 0; n < str.length; n++) bytes.add(str[n]); break; } case JvmConstant.CONSTANT_Long: bytes.addInteger((int) (c.longValue >> 32)); bytes.addInteger((int) c.longValue); i++; break; case JvmConstant.CONSTANT_Double: { final long d = Double.doubleToLongBits(c.doubleValue); bytes.addInteger((int) (d >> 32)); bytes.addInteger((int) d); i++; break; } case JvmConstant.CONSTANT_Integer: bytes.addInteger(c.intValue); break; case JvmConstant.CONSTANT_Float: bytes.addInteger(Float.floatToIntBits(c.floatValue)); break; case JvmConstant.CONSTANT_Class: case JvmConstant.CONSTANT_String: bytes.addShort(c.index0); break; case JvmConstant.CONSTANT_Fieldref: case JvmConstant.CONSTANT_InterfaceMethodref: case JvmConstant.CONSTANT_Methodref: case JvmConstant.CONSTANT_NameAndType: bytes.addShort(c.index0); bytes.addShort(c.index1); break; } } // class access modifiers bytes.addShort(Modifier.PUBLIC | Modifier.FINAL); bytes.addShort(2); // this bytes.addShort(5); // super class bytes.addShort(0); // interfaces bytes.addShort(0); // fields // write methods bytes.addShort(this.methods.size()); for(int i = 0; i < this.methods.size(); i++) { final JvmMethodWriter mw = this.methods.get(i); bytes.addShort(mw.access); bytes.addShort(mw.nameIndex); bytes.addShort(mw.descriptorIndex); bytes.addShort(1); // attributes bytes.addShort(3); // "Code" final byte[] code = mw.code.toArray(); bytes.addInteger(12 + code.length); // size bytes.addShort(mw.maxStack); bytes.addShort(mw.maxLocals); bytes.addInteger(code.length); for(int n = 0; n < code.length; n++) bytes.add(code[n]); bytes.addShort(0); // exception table bytes.addShort(0); // attributes } bytes.addShort(0); // attributes } catch(UnsupportedEncodingException e) { e.printStackTrace(); return null; } return bytes.toArray(); } /** * Builds a method descriptor. * * @param args * The types. * @return The method descriptor. */ public static String buildDescriptor(Class<?>... args) { final StringBuilder sb = new StringBuilder(); for(int i = 0; i < args.length; i++) { final Class<?> c = args[i]; if(c == void.class) sb.append('V'); else if(c == byte.class) sb.append('B'); else if(c == char.class) sb.append('C'); else if(c == short.class) sb.append('S'); else if(c == int.class) sb.append('I'); else if(c == long.class) sb.append('J'); else if(c == float.class) sb.append('F'); else if(c == double.class) sb.append('D'); else if(c == boolean.class) sb.append('Z'); else { sb.append('L'); sb.append(c.getCanonicalName().replace('.', '/')); sb.append(';'); } } return sb.toString(); } }