View Javadoc
1   /*
2    * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.codemodel.internal;
27  
28  import java.io.File;
29  import java.io.IOException;
30  import java.io.PrintStream;
31  import java.lang.reflect.Modifier;
32  import java.util.ArrayList;
33  import java.util.Collections;
34  import java.util.HashMap;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.Map;
38  
39  import com.sun.codemodel.internal.writer.FileCodeWriter;
40  import com.sun.codemodel.internal.writer.ProgressCodeWriter;
41  
42  
43  /**
44   * Root of the code DOM.
45   *
46   * <p>
47   * Here's your typical CodeModel application.
48   *
49   * <pre>
50   * JCodeModel cm = new JCodeModel();
51   *
52   * // generate source code by populating the 'cm' tree.
53   * cm._class(...);
54   * ...
55   *
56   * // write them out
57   * cm.build(new File("."));
58   * </pre>
59   *
60   * <p>
61   * Every CodeModel node is always owned by one {@link JCodeModel} object
62   * at any given time (which can be often accesesd by the <tt>owner()</tt> method.)
63   *
64   * As such, when you generate Java code, most of the operation works
65   * in a top-down fashion. For example, you create a class from {@link JCodeModel},
66   * which gives you a {@link JDefinedClass}. Then you invoke a method on it
67   * to generate a new method, which gives you {@link JMethod}, and so on.
68   *
69   * There are a few exceptions to this, most notably building {@link JExpression}s,
70   * but generally you work with CodeModel in a top-down fashion.
71   *
72   * Because of this design, most of the CodeModel classes aren't directly instanciable.
73   *
74   *
75   * <h2>Where to go from here?</h2>
76   * <p>
77   * Most of the time you'd want to populate new type definitions in a {@link JCodeModel}.
78   * See {@link #_class(String, ClassType)}.
79   */
80  public final class JCodeModel {
81  
82      /** The packages that this JCodeWriter contains. */
83      private HashMap<String,JPackage> packages = new HashMap<String,JPackage>();
84  
85      /** All JReferencedClasses are pooled here. */
86      private final HashMap<Class<?>,JReferencedClass> refClasses = new HashMap<Class<?>,JReferencedClass>();
87  
88  
89      /** Obtains a reference to the special "null" type. */
90      public final JNullType NULL = new JNullType(this);
91      // primitive types
92      public final JPrimitiveType VOID    = new JPrimitiveType(this,"void",   Void.class);
93      public final JPrimitiveType BOOLEAN = new JPrimitiveType(this,"boolean",Boolean.class);
94      public final JPrimitiveType BYTE    = new JPrimitiveType(this,"byte",   Byte.class);
95      public final JPrimitiveType SHORT   = new JPrimitiveType(this,"short",  Short.class);
96      public final JPrimitiveType CHAR    = new JPrimitiveType(this,"char",   Character.class);
97      public final JPrimitiveType INT     = new JPrimitiveType(this,"int",    Integer.class);
98      public final JPrimitiveType FLOAT   = new JPrimitiveType(this,"float",  Float.class);
99      public final JPrimitiveType LONG    = new JPrimitiveType(this,"long",   Long.class);
100     public final JPrimitiveType DOUBLE  = new JPrimitiveType(this,"double", Double.class);
101 
102     /**
103      * If the flag is true, we will consider two classes "Foo" and "foo"
104      * as a collision.
105      */
106     protected static final boolean isCaseSensitiveFileSystem = getFileSystemCaseSensitivity();
107 
108     private static boolean getFileSystemCaseSensitivity() {
109         try {
110             // let the system property override, in case the user really
111             // wants to override.
112             if( System.getProperty("com.sun.codemodel.internal.FileSystemCaseSensitive")!=null )
113                 return true;
114         } catch( Exception e ) {}
115 
116         // on Unix, it's case sensitive.
117         return (File.separatorChar == '/');
118     }
119 
120 
121     public JCodeModel() {}
122 
123     /**
124      * Add a package to the list of packages to be generated
125      *
126      * @param name
127      *        Name of the package. Use "" to indicate the root package.
128      *
129      * @return Newly generated package
130      */
131     public JPackage _package(String name) {
132         JPackage p = packages.get(name);
133         if (p == null) {
134             p = new JPackage(name, this);
135             packages.put(name, p);
136         }
137         return p;
138     }
139 
140     public final JPackage rootPackage() {
141         return _package("");
142     }
143 
144     /**
145      * Returns an iterator that walks the packages defined using this code
146      * writer.
147      */
148     public Iterator<JPackage> packages() {
149         return packages.values().iterator();
150     }
151 
152     /**
153      * Creates a new generated class.
154      *
155      * @exception JClassAlreadyExistsException
156      *      When the specified class/interface was already created.
157      */
158     public JDefinedClass _class(String fullyqualifiedName) throws JClassAlreadyExistsException {
159         return _class(fullyqualifiedName,ClassType.CLASS);
160     }
161 
162     /**
163      * Creates a dummy, unknown {@link JClass} that represents a given name.
164      *
165      * <p>
166      * This method is useful when the code generation needs to include the user-specified
167      * class that may or may not exist, and only thing known about it is a class name.
168      */
169     public JClass directClass(String name) {
170         return new JDirectClass(this,name);
171     }
172 
173     /**
174      * Creates a new generated class.
175      *
176      * @exception JClassAlreadyExistsException
177      *      When the specified class/interface was already created.
178      */
179     public JDefinedClass _class(int mods, String fullyqualifiedName,ClassType t) throws JClassAlreadyExistsException {
180         int idx = fullyqualifiedName.lastIndexOf('.');
181         if( idx<0 )     return rootPackage()._class(fullyqualifiedName);
182         else
183             return _package(fullyqualifiedName.substring(0,idx))
184                 ._class(mods, fullyqualifiedName.substring(idx+1), t );
185     }
186 
187     /**
188      * Creates a new generated class.
189      *
190      * @exception JClassAlreadyExistsException
191      *      When the specified class/interface was already created.
192      */
193     public JDefinedClass _class(String fullyqualifiedName,ClassType t) throws JClassAlreadyExistsException {
194         return _class( JMod.PUBLIC, fullyqualifiedName, t );
195     }
196 
197     /**
198      * Gets a reference to the already created generated class.
199      *
200      * @return null
201      *      If the class is not yet created.
202      * @see JPackage#_getClass(String)
203      */
204     public JDefinedClass _getClass(String fullyQualifiedName) {
205         int idx = fullyQualifiedName.lastIndexOf('.');
206         if( idx<0 )     return rootPackage()._getClass(fullyQualifiedName);
207         else
208             return _package(fullyQualifiedName.substring(0,idx))
209                 ._getClass( fullyQualifiedName.substring(idx+1) );
210     }
211 
212     /**
213      * Creates a new anonymous class.
214      *
215      * @deprecated
216      *      The naming convention doesn't match the rest of the CodeModel.
217      *      Use {@link #anonymousClass(JClass)} instead.
218      */
219     public JDefinedClass newAnonymousClass(JClass baseType) {
220         return new JAnonymousClass(baseType);
221     }
222 
223     /**
224      * Creates a new anonymous class.
225      */
226     public JDefinedClass anonymousClass(JClass baseType) {
227         return new JAnonymousClass(baseType);
228     }
229 
230     public JDefinedClass anonymousClass(Class<?> baseType) {
231         return anonymousClass(ref(baseType));
232     }
233 
234     /**
235      * Generates Java source code.
236      * A convenience method for <code>build(destDir,destDir,System.out)</code>.
237      *
238      * @param   destDir
239      *          source files are generated into this directory.
240      * @param   status
241      *      if non-null, progress indication will be sent to this stream.
242      */
243     public void build( File destDir, PrintStream status ) throws IOException {
244         build(destDir,destDir,status);
245     }
246 
247     /**
248      * Generates Java source code.
249      * A convenience method that calls {@link #build(CodeWriter,CodeWriter)}.
250      *
251      * @param   srcDir
252      *          Java source files are generated into this directory.
253      * @param   resourceDir
254      *          Other resource files are generated into this directory.
255      * @param   status
256      *      if non-null, progress indication will be sent to this stream.
257      */
258     public void build( File srcDir, File resourceDir, PrintStream status ) throws IOException {
259         CodeWriter src = new FileCodeWriter(srcDir);
260         CodeWriter res = new FileCodeWriter(resourceDir);
261         if(status!=null) {
262             src = new ProgressCodeWriter(src, status );
263             res = new ProgressCodeWriter(res, status );
264         }
265         build(src,res);
266     }
267 
268     /**
269      * A convenience method for <code>build(destDir,System.out)</code>.
270      */
271     public void build( File destDir ) throws IOException {
272         build(destDir,System.out);
273     }
274 
275     /**
276      * A convenience method for <code>build(srcDir,resourceDir,System.out)</code>.
277      */
278     public void build( File srcDir, File resourceDir ) throws IOException {
279         build(srcDir,resourceDir,System.out);
280     }
281 
282     /**
283      * A convenience method for <code>build(out,out)</code>.
284      */
285     public void build( CodeWriter out ) throws IOException {
286         build(out,out);
287     }
288 
289     /**
290      * Generates Java source code.
291      */
292     public void build( CodeWriter source, CodeWriter resource ) throws IOException {
293         JPackage[] pkgs = packages.values().toArray(new JPackage[packages.size()]);
294         // avoid concurrent modification exception
295         for( JPackage pkg : pkgs )
296             pkg.build(source,resource);
297         source.close();
298         resource.close();
299     }
300 
301     /**
302      * Returns the number of files to be generated if
303      * {@link #build} is invoked now.
304      */
305     public int countArtifacts() {
306         int r = 0;
307         JPackage[] pkgs = packages.values().toArray(new JPackage[packages.size()]);
308         // avoid concurrent modification exception
309         for( JPackage pkg : pkgs )
310             r += pkg.countArtifacts();
311         return r;
312     }
313 
314 
315     /**
316      * Obtains a reference to an existing class from its Class object.
317      *
318      * <p>
319      * The parameter may not be primitive.
320      *
321      * @see #_ref(Class) for the version that handles more cases.
322      */
323     public JClass ref(Class<?> clazz) {
324         JReferencedClass jrc = (JReferencedClass)refClasses.get(clazz);
325         if (jrc == null) {
326             if (clazz.isPrimitive())
327                 throw new IllegalArgumentException(clazz+" is a primitive");
328             if (clazz.isArray()) {
329                 return new JArrayClass(this, _ref(clazz.getComponentType()));
330             } else {
331                 jrc = new JReferencedClass(clazz);
332                 refClasses.put(clazz, jrc);
333             }
334         }
335         return jrc;
336     }
337 
338     public JType _ref(Class<?> c) {
339         if(c.isPrimitive())
340             return JType.parse(this,c.getName());
341         else
342             return ref(c);
343     }
344 
345     /**
346      * Obtains a reference to an existing class from its fully-qualified
347      * class name.
348      *
349      * <p>
350      * First, this method attempts to load the class of the given name.
351      * If that fails, we assume that the class is derived straight from
352      * {@link Object}, and return a {@link JClass}.
353      */
354     public JClass ref(String fullyQualifiedClassName) {
355         try {
356             // try the context class loader first
357             return ref(SecureLoader.getContextClassLoader().loadClass(fullyQualifiedClassName));
358         } catch (ClassNotFoundException e) {
359             // fall through
360         }
361         // then the default mechanism.
362         try {
363             return ref(Class.forName(fullyQualifiedClassName));
364         } catch (ClassNotFoundException e1) {
365             // fall through
366         }
367 
368         // assume it's not visible to us.
369         return new JDirectClass(this,fullyQualifiedClassName);
370     }
371 
372     /**
373      * Cached for {@link #wildcard()}.
374      */
375     private JClass wildcard;
376 
377     /**
378      * Gets a {@link JClass} representation for "?",
379      * which is equivalent to "? extends Object".
380      */
381     public JClass wildcard() {
382         if(wildcard==null)
383             wildcard = ref(Object.class).wildcard();
384         return wildcard;
385     }
386 
387     /**
388      * Obtains a type object from a type name.
389      *
390      * <p>
391      * This method handles primitive types, arrays, and existing {@link Class}es.
392      *
393      * @exception ClassNotFoundException
394      *      If the specified type is not found.
395      */
396     public JType parseType(String name) throws ClassNotFoundException {
397         // array
398         if(name.endsWith("[]"))
399             return parseType(name.substring(0,name.length()-2)).array();
400 
401         // try primitive type
402         try {
403             return JType.parse(this,name);
404         } catch (IllegalArgumentException e) {
405             ;
406         }
407 
408         // existing class
409         return new TypeNameParser(name).parseTypeName();
410     }
411 
412     private final class TypeNameParser {
413         private final String s;
414         private int idx;
415 
416         public TypeNameParser(String s) {
417             this.s = s;
418         }
419 
420         /**
421          * Parses a type name token T (which can be potentially of the form Tr&ly;T1,T2,...>,
422          * or "? extends/super T".)
423          *
424          * @return the index of the character next to T.
425          */
426         JClass parseTypeName() throws ClassNotFoundException {
427             int start = idx;
428 
429             if(s.charAt(idx)=='?') {
430                 // wildcard
431                 idx++;
432                 ws();
433                 String head = s.substring(idx);
434                 if(head.startsWith("extends")) {
435                     idx+=7;
436                     ws();
437                     return parseTypeName().wildcard();
438                 } else
439                 if(head.startsWith("super")) {
440                     throw new UnsupportedOperationException("? super T not implemented");
441                 } else {
442                     // not supported
443                     throw new IllegalArgumentException("only extends/super can follow ?, but found "+s.substring(idx));
444                 }
445             }
446 
447             while(idx<s.length()) {
448                 char ch = s.charAt(idx);
449                 if(Character.isJavaIdentifierStart(ch)
450                 || Character.isJavaIdentifierPart(ch)
451                 || ch=='.')
452                     idx++;
453                 else
454                     break;
455             }
456 
457             JClass clazz = ref(s.substring(start,idx));
458 
459             return parseSuffix(clazz);
460         }
461 
462         /**
463          * Parses additional left-associative suffixes, like type arguments
464          * and array specifiers.
465          */
466         private JClass parseSuffix(JClass clazz) throws ClassNotFoundException {
467             if(idx==s.length())
468                 return clazz; // hit EOL
469 
470             char ch = s.charAt(idx);
471 
472             if(ch=='<')
473                 return parseSuffix(parseArguments(clazz));
474 
475             if(ch=='[') {
476                 if(s.charAt(idx+1)==']') {
477                     idx+=2;
478                     return parseSuffix(clazz.array());
479                 }
480                 throw new IllegalArgumentException("Expected ']' but found "+s.substring(idx+1));
481             }
482 
483             return clazz;
484         }
485 
486         /**
487          * Skips whitespaces
488          */
489         private void ws() {
490             while(Character.isWhitespace(s.charAt(idx)) && idx<s.length())
491                 idx++;
492         }
493 
494         /**
495          * Parses '&lt;T1,T2,...,Tn>'
496          *
497          * @return the index of the character next to '>'
498          */
499         private JClass parseArguments(JClass rawType) throws ClassNotFoundException {
500             if(s.charAt(idx)!='<')
501                 throw new IllegalArgumentException();
502             idx++;
503 
504             List<JClass> args = new ArrayList<JClass>();
505 
506             while(true) {
507                 args.add(parseTypeName());
508                 if(idx==s.length())
509                     throw new IllegalArgumentException("Missing '>' in "+s);
510                 char ch = s.charAt(idx);
511                 if(ch=='>')
512                     return rawType.narrow(args.toArray(new JClass[args.size()]));
513 
514                 if(ch!=',')
515                     throw new IllegalArgumentException(s);
516                 idx++;
517             }
518 
519         }
520     }
521 
522     /**
523      * References to existing classes.
524      *
525      * <p>
526      * JReferencedClass is kept in a pool so that they are shared.
527      * There is one pool for each JCodeModel object.
528      *
529      * <p>
530      * It is impossible to cache JReferencedClass globally only because
531      * there is the _package() method, which obtains the owner JPackage
532      * object, which is scoped to JCodeModel.
533      */
534     private class JReferencedClass extends JClass implements JDeclaration {
535         private final Class<?> _class;
536 
537         JReferencedClass(Class<?> _clazz) {
538             super(JCodeModel.this);
539             this._class = _clazz;
540             assert !_class.isArray();
541         }
542 
543         public String name() {
544             return _class.getSimpleName().replace('$','.');
545         }
546 
547         public String fullName() {
548             return _class.getName().replace('$','.');
549         }
550 
551         public String binaryName() {
552             return _class.getName();
553         }
554 
555         public JClass outer() {
556             Class<?> p = _class.getDeclaringClass();
557             if(p==null)     return null;
558             return ref(p);
559         }
560 
561         public JPackage _package() {
562             String name = fullName();
563 
564             // this type is array
565             if (name.indexOf('[') != -1)
566                 return JCodeModel.this._package("");
567 
568             // other normal case
569             int idx = name.lastIndexOf('.');
570             if (idx < 0)
571                 return JCodeModel.this._package("");
572             else
573                 return JCodeModel.this._package(name.substring(0, idx));
574         }
575 
576         public JClass _extends() {
577             Class<?> sp = _class.getSuperclass();
578             if (sp == null) {
579                 if(isInterface())
580                     return owner().ref(Object.class);
581                 return null;
582             } else
583                 return ref(sp);
584         }
585 
586         public Iterator<JClass> _implements() {
587             final Class<?>[] interfaces = _class.getInterfaces();
588             return new Iterator<JClass>() {
589                 private int idx = 0;
590                 public boolean hasNext() {
591                     return idx < interfaces.length;
592                 }
593                 public JClass next() {
594                     return JCodeModel.this.ref(interfaces[idx++]);
595                 }
596                 public void remove() {
597                     throw new UnsupportedOperationException();
598                 }
599             };
600         }
601 
602         public boolean isInterface() {
603             return _class.isInterface();
604         }
605 
606         public boolean isAbstract() {
607             return Modifier.isAbstract(_class.getModifiers());
608         }
609 
610         public JPrimitiveType getPrimitiveType() {
611             Class<?> v = boxToPrimitive.get(_class);
612             if(v!=null)
613                 return JType.parse(JCodeModel.this,v.getName());
614             else
615                 return null;
616         }
617 
618         public boolean isArray() {
619             return false;
620         }
621 
622         public void declare(JFormatter f) {
623         }
624 
625         public JTypeVar[] typeParams() {
626             // TODO: does JDK 1.5 reflection provides these information?
627             return super.typeParams();
628         }
629 
630         protected JClass substituteParams(JTypeVar[] variables, List<JClass> bindings) {
631             // TODO: does JDK 1.5 reflection provides these information?
632             return this;
633         }
634     }
635 
636     /**
637      * Conversion from primitive type {@link Class} (such as {@link Integer#TYPE}
638      * to its boxed type (such as <tt>Integer.class</tt>)
639      */
640     public static final Map<Class<?>,Class<?>> primitiveToBox;
641     /**
642      * The reverse look up for {@link #primitiveToBox}
643      */
644     public static final Map<Class<?>,Class<?>> boxToPrimitive;
645 
646     static {
647         Map<Class<?>,Class<?>> m1 = new HashMap<Class<?>,Class<?>>();
648         Map<Class<?>,Class<?>> m2 = new HashMap<Class<?>,Class<?>>();
649 
650         m1.put(Boolean.class,Boolean.TYPE);
651         m1.put(Byte.class,Byte.TYPE);
652         m1.put(Character.class,Character.TYPE);
653         m1.put(Double.class,Double.TYPE);
654         m1.put(Float.class,Float.TYPE);
655         m1.put(Integer.class,Integer.TYPE);
656         m1.put(Long.class,Long.TYPE);
657         m1.put(Short.class,Short.TYPE);
658         m1.put(Void.class,Void.TYPE);
659 
660         for (Map.Entry<Class<?>, Class<?>> e : m1.entrySet())
661             m2.put(e.getValue(),e.getKey());
662 
663         boxToPrimitive = Collections.unmodifiableMap(m1);
664         primitiveToBox = Collections.unmodifiableMap(m2);
665 
666     }
667 }