001package serp.bytecode;
002
003import java.util.*;
004
005import serp.bytecode.lowlevel.*;
006
007/**
008 * Pseudo-instruction used to place {@link Class} objects onto the stack.
009 * This logical instruction may actually involve a large chunk of code, and
010 * may even add static synthetic fields and methods to the owning class.
011 * Therefore, once the type of class being loaded is set, it cannot
012 * be changed. Also, this instruction is invalid as the target of
013 * any jump instruction or exception handler.
014 *
015 * @author Abe White
016 */
017public class ClassConstantInstruction {
018    private static final Class[] _params = new Class[] { String.class };
019    private static final Map _wrappers = new HashMap();
020    static {
021        _wrappers.put(byte.class.getName(), Byte.class);
022        _wrappers.put(boolean.class.getName(), Boolean.class);
023        _wrappers.put(char.class.getName(), Character.class);
024        _wrappers.put(double.class.getName(), Double.class);
025        _wrappers.put(float.class.getName(), Float.class);
026        _wrappers.put(int.class.getName(), Integer.class);
027        _wrappers.put(long.class.getName(), Long.class);
028        _wrappers.put(short.class.getName(), Short.class);
029    }
030
031    private Instruction _ins = null;
032    private Code _code = null;
033    private BCClass _class = null;
034    private boolean _invalid = false;
035
036    ClassConstantInstruction(BCClass bc, Code code, Instruction nop) {
037        _class = bc;
038        _code = code;
039        _ins = nop;
040    }
041
042    /**
043     * Set the type of class being loaded.
044     *
045     * @return the first Instruction of the block added by setting the type
046     * @throws IllegalStateException if type has already been set
047     */
048    public Instruction setClass(String name) {
049        name = _class.getProject().getNameCache().getExternalForm(name, false);
050        setClassName(name, getWrapperClass(name));
051        return _ins;
052    }
053
054    /**
055     * Set the type of class being loaded.
056     *
057     * @return the first Instruction of the block added by setting the type
058     * @throws IllegalStateException if type has already been set
059     */
060    public Instruction setClass(Class type) {
061        return setClass(type.getName());
062    }
063
064    /**
065     * Set the type of class being loaded.
066     *
067     * @return the first Instruction of the block added by setting the type
068     * @throws IllegalStateException if type has already been set
069     */
070    public Instruction setClass(BCClass type) {
071        return setClass(type.getName());
072    }
073
074    /**
075     * Set the name of the class to load.
076     */
077    private void setClassName(String name, Class wrapper) {
078        if (_invalid)
079            throw new IllegalStateException();
080
081        // remember the position of the code iterator
082        Instruction before = (_code.hasNext()) ? _code.next() : null;
083        _code.before(_ins);
084        _code.next();
085        if (wrapper != null)
086            _code.getstatic().setField(wrapper, "TYPE", Class.class);
087        else
088            setObject(name);
089
090        // move to the old position
091        if (before != null)
092            _code.before(before);
093        else
094            _code.afterLast();
095        _invalid = true;
096    }
097
098    /**
099     * Adds fields and methods as necessary to load a class constant of
100     * an object type.
101     */
102    private void setObject(String name) {
103        BCField field = addClassField(name);
104        BCMethod method = addClassLoadMethod();
105
106        // copied from the way jikes loads classes
107        _code.getstatic().setField(field);
108        JumpInstruction ifnull = _code.ifnull();
109        _code.getstatic().setField(field);
110        JumpInstruction go2 = _code.go2();
111        ifnull.setTarget(_code.constant().setValue(name));
112        _code.invokestatic().setMethod(method);
113        _code.dup();
114        _code.putstatic().setField(field);
115        go2.setTarget(_code.nop());
116    }
117
118    /**
119     * Adds a static field to hold the loaded class constant.
120     */
121    private BCField addClassField(String name) {
122        String fieldName = "class$L" 
123            + name.replace('.', '$').replace('[', '$').replace(';', '$');
124        BCField field = _class.getDeclaredField(fieldName);
125        if (field == null) {
126            field = _class.declareField(fieldName, Class.class);
127            field.makePackage();
128            field.setStatic(true);
129            field.setSynthetic(true);
130        }
131        return field;
132    }
133
134    /**
135     * Adds the standard <code>class$<code> method used inernally by classes
136     * to load class constants for object types.
137     */
138    private BCMethod addClassLoadMethod() {
139        BCMethod method = _class.getDeclaredMethod("class$", _params);
140        if (method != null)
141            return method;
142
143        // add the special synthetic method
144        method = _class.declareMethod("class$", Class.class, _params);
145        method.setStatic(true);
146        method.makePackage();
147        method.setSynthetic(true);
148
149        // copied directly from the output of the jikes compiler
150        Code code = method.getCode(true);
151        code.setMaxStack(3);
152        code.setMaxLocals(2);
153
154        Instruction tryStart = code.aload().setLocal(0);
155        code.invokestatic().setMethod(Class.class, "forName", Class.class, 
156            _params);
157        Instruction tryEnd = code.areturn();
158        Instruction handlerStart = code.astore().setLocal(1);
159        code.anew().setType(NoClassDefFoundError.class);
160        code.dup();
161        code.aload().setLocal(1);
162        code.invokevirtual().setMethod(Throwable.class, "getMessage", 
163            String.class, null);
164        code.invokespecial().setMethod(NoClassDefFoundError.class, "<init>", 
165            void.class, _params);
166        code.athrow();
167        code.addExceptionHandler(tryStart, tryEnd, handlerStart,
168            ClassNotFoundException.class);
169        return method;
170    }
171
172    /**
173     * Return the wrapper type for the given primitive class, or null
174     * if the given name is not a primitive type. The given name should
175     * be in external form.
176     */
177    private static Class getWrapperClass(String name) {
178        if (name == null)
179            return null;
180        return (Class) _wrappers.get(name);
181    }
182}