001    package serp.bytecode;
002    
003    import java.util.*;
004    
005    import 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     */
017    public 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    }