001    /*
002     * $Id: DummyClassGenerator.java,v 1.3 2005/05/27 10:13:09 russel Exp $
003     *
004     * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005     *
006     * Redistribution and use of this software and associated documentation
007     * ("Software"), with or without modification, are permitted provided that the
008     * following conditions are met: 1. Redistributions of source code must retain
009     * copyright statements and notices. Redistributions must also contain a copy
010     * of this document. 2. Redistributions in binary form must reproduce the above
011     * copyright notice, this list of conditions and the following disclaimer in
012     * the documentation and/or other materials provided with the distribution. 3.
013     * The name "groovy" must not be used to endorse or promote products derived
014     * from this Software without prior written permission of The Codehaus. For
015     * written permission, please contact info@codehaus.org. 4. Products derived
016     * from this Software may not be called "groovy" nor may "groovy" appear in
017     * their names without prior written permission of The Codehaus. "groovy" is a
018     * registered trademark of The Codehaus. 5. Due credit should be given to The
019     * Codehaus - http://groovy.codehaus.org/
020     *
021     * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
022     * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
024     * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
025     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
027     * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
028     * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
029     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
030     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
031     * DAMAGE.
032     *
033     */
034    package org.codehaus.groovy.classgen;
035    
036    import groovy.lang.GroovyRuntimeException;
037    import groovy.lang.MissingClassException;
038    import org.codehaus.groovy.ast.*;
039    import org.objectweb.asm.ClassVisitor;
040    import org.objectweb.asm.MethodVisitor;
041    
042    import java.util.*;
043    
044    /**
045     * To generate a class that has all the fields and methods, except that fields are not initilized
046     * and methods are empty. It's intended for being used as a place holder during code generation
047     * of reference to the "this" class itself.
048     *
049     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
050     * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
051     *
052     * @version $Revision: 1.3 $
053     */
054    public class DummyClassGenerator extends ClassGenerator {
055    
056        private ClassVisitor cw;
057        private MethodVisitor cv;
058        private GeneratorContext context;
059    
060        private String sourceFile;
061    
062        // current class details
063        private ClassNode classNode;
064        private String internalClassName;
065        private String internalBaseClassName;
066    
067    
068        public DummyClassGenerator(
069            GeneratorContext context,
070            ClassVisitor classVisitor,
071            ClassLoader classLoader,
072            String sourceFile) {
073            super(classLoader);
074            this.context = context;
075            this.cw = classVisitor;
076            this.sourceFile = sourceFile;
077        }
078    
079        // GroovyClassVisitor interface
080        //-------------------------------------------------------------------------
081        public void visitClass(ClassNode classNode) {
082            try {
083                this.classNode = classNode;
084                this.internalClassName = BytecodeHelper.getClassInternalName(classNode.getName());
085    
086                //System.out.println("Generating class: " + classNode.getName());
087    
088                // lets check that the classes are all valid
089                classNode.setSuperClass(checkValidType(classNode.getSuperClass(), classNode, "Must be a valid base class"));
090                String[] interfaces = classNode.getInterfaces();
091                for (int i = 0; i < interfaces.length; i++ ) {
092                    interfaces[i] = checkValidType(interfaces[i], classNode, "Must be a valid interface name");
093                }
094    
095                this.internalBaseClassName = BytecodeHelper.getClassInternalName(classNode.getSuperClass());
096    
097                cw.visit(
098                    asmJDKVersion,
099                    classNode.getModifiers(),
100                    internalClassName,
101                    (String)null,
102                    internalBaseClassName,
103                    BytecodeHelper.getClassInternalNames(classNode.getInterfaces())
104                    );
105    
106                classNode.visitContents(this);
107    
108                for (Iterator iter = innerClasses.iterator(); iter.hasNext();) {
109                    ClassNode innerClass = (ClassNode) iter.next();
110                    String innerClassName = innerClass.getName();
111                    String innerClassInternalName = BytecodeHelper.getClassInternalName(innerClassName);
112                    String outerClassName = internalClassName; // default for inner classes
113                    MethodNode enclosingMethod = innerClass.getEnclosingMethod();
114                    if (enclosingMethod != null) {
115                        // local inner classes do not specify the outer class name
116                        outerClassName = null;
117                    }
118                    cw.visitInnerClass(
119                        innerClassInternalName,
120                        outerClassName,
121                        innerClassName,
122                        innerClass.getModifiers());
123                }
124                cw.visitEnd();
125            }
126            catch (GroovyRuntimeException e) {
127                e.setModule(classNode.getModule());
128                throw e;
129            }
130        }
131    
132        public void visitConstructor(ConstructorNode node) {
133    
134            visitParameters(node, node.getParameters());
135    
136            String methodType = BytecodeHelper.getMethodDescriptor("void", node.getParameters());
137            cv = cw.visitMethod(node.getModifiers(), "<init>", methodType, null, null);
138            cv.visitTypeInsn(NEW, "java/lang/RuntimeException");
139            cv.visitInsn(DUP);
140            cv.visitLdcInsn("not intended for execution");
141            cv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V");
142            cv.visitInsn(ATHROW);
143            cv.visitMaxs(0, 0);
144        }
145    
146        public void visitMethod(MethodNode node) {
147    
148            visitParameters(node, node.getParameters());
149            node.setReturnType(checkValidType(node.getReturnType(), node, "Must be a valid return type"));
150    
151            String methodType = BytecodeHelper.getMethodDescriptor(node.getReturnType(), node.getParameters());
152            cv = cw.visitMethod(node.getModifiers(), node.getName(), methodType, null, null);
153    
154            cv.visitTypeInsn(NEW, "java/lang/RuntimeException");
155            cv.visitInsn(DUP);
156            cv.visitLdcInsn("not intended for execution");
157            cv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V");
158            cv.visitInsn(ATHROW);
159    
160            cv.visitMaxs(0, 0);
161        }
162    
163        public void visitField(FieldNode fieldNode) {
164    
165            // lets check that the classes are all valid
166            fieldNode.setType(checkValidType(fieldNode.getType(), fieldNode, "Must be a valid field class for field: " + fieldNode.getName()));
167    
168            cw.visitField(
169                fieldNode.getModifiers(),
170                fieldNode.getName(),
171                BytecodeHelper.getTypeDescription(fieldNode.getType()),
172                null, //fieldValue,  //br  all the sudden that one cannot init the field here. init is done in static initilizer and instace intializer.
173                null);
174        }
175    
176        /**
177         * Creates a getter, setter and field
178         */
179        public void visitProperty(PropertyNode statement) {
180        }
181    
182    
183        protected String checkValidType(String type, ASTNode node, String message) {
184            if (type!= null && type.length() == 0)
185                return "java.lang.Object";
186            if (type.endsWith("[]")) {
187                String postfix = "[]";
188                String prefix = type.substring(0, type.length() - 2);
189                return checkValidType(prefix, node, message) + postfix;
190            }
191            int idx = type.indexOf('$');
192            if (idx > 0) {
193                String postfix = type.substring(idx);
194                String prefix = type.substring(0, idx);
195                return checkValidType(prefix, node, message) + postfix;
196            }
197            if (BytecodeHelper.isPrimitiveType(type) || "void".equals(type)) {
198                return type;
199            }
200            String original = type;
201            type = resolveClassName(type);
202            if (type != null) {
203                return type;
204            }
205    
206            throw new MissingClassException(original, node, message + " for class: " + classNode.getName());
207        }
208        protected String resolveClassName(String type) {
209            return classNode.resolveClassName(type);
210        }
211    
212        protected static boolean isPrimitiveFieldType(String type) {
213            return type.equals("java.lang.String")
214                || type.equals("java.lang.Integer")
215                || type.equals("java.lang.Double")
216                || type.equals("java.lang.Long")
217                || type.equals("java.lang.Float");
218        }
219        protected Class loadClass(String name) {
220            if (name.equals(this.classNode.getName())) {
221                return Object.class;
222            }
223    
224            if (name == null) {
225                return null;
226            }
227            else if (name.length() == 0) {
228                return Object.class;
229            }
230    
231            else if ("void".equals(name)) {
232                return void.class;
233            }
234            else if ("boolean".equals(name)) {
235                return boolean.class;
236            }
237            else if ("byte".equals(name)) {
238                return byte.class;
239            }
240            else if ("short".equals(name)) {
241                return short.class;
242            }
243            else if ("char".equals(name)) {
244                return char.class;
245            }
246            else if ("int".equals(name)) {
247                return int.class;
248            }
249            else if ("long".equals(name)) {
250                return long.class;
251            }
252            else if ("float".equals(name)) {
253                return float.class;
254            }
255            else if ("double".equals(name)) {
256                return double.class;
257            }
258    
259            name = BytecodeHelper.formatNameForClassLoading(name);
260    
261            try {
262                    Class cls = (Class)classCache.get(name);
263                    if (cls != null)
264                            return cls;
265    
266                    CompileUnit compileUnit = getCompileUnit();
267                if (compileUnit != null) {
268                    cls = compileUnit.loadClass(name);
269                    classCache.put(name, cls);
270                    return cls;
271                }
272                else {
273                    throw new ClassGeneratorException("Could not load class: " + name);
274                }
275            }
276            catch (ClassNotFoundException e) {
277                throw new ClassGeneratorException("Error when compiling class: " + classNode.getName() + ". Reason: could not load class: " + name + " reason: " + e, e);
278            }
279        }
280    
281        Map classCache = new HashMap();
282        {
283            classCache.put("int", Integer.TYPE);
284            classCache.put("byte", Byte.TYPE);
285            classCache.put("short", Short.TYPE);
286            classCache.put("char", Character.TYPE);
287            classCache.put("boolean", Boolean.TYPE);
288            classCache.put("long", Long.TYPE);
289            classCache.put("double", Double.TYPE);
290            classCache.put("float", Float.TYPE);
291        }
292        protected CompileUnit getCompileUnit() {
293            CompileUnit answer = classNode.getCompileUnit();
294            if (answer == null) {
295                answer = context.getCompileUnit();
296            }
297            return answer;
298        }
299    
300        protected void visitParameters(ASTNode node, Parameter[] parameters) {
301            for (int i = 0, size = parameters.length; i < size; i++ ) {
302                visitParameter(node, parameters[i]);
303            }
304        }
305    
306        protected void visitParameter(ASTNode node, Parameter parameter) {
307            if (! parameter.isDynamicType()) {
308                parameter.setType(checkValidType(parameter.getType(), node, "Must be a valid parameter class"));
309            }
310        }
311    
312    }