Thu, 12 Oct 2017 19:44:07 +0800
merge
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 */
26 package com.sun.codemodel.internal;
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;
39 import com.sun.codemodel.internal.writer.FileCodeWriter;
40 import com.sun.codemodel.internal.writer.ProgressCodeWriter;
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 {
82 /** The packages that this JCodeWriter contains. */
83 private HashMap<String,JPackage> packages = new HashMap<String,JPackage>();
85 /** All JReferencedClasses are pooled here. */
86 private final HashMap<Class<?>,JReferencedClass> refClasses = new HashMap<Class<?>,JReferencedClass>();
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);
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();
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 ) {}
116 // on Unix, it's case sensitive.
117 return (File.separatorChar == '/');
118 }
121 public JCodeModel() {}
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 }
140 public final JPackage rootPackage() {
141 return _package("");
142 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
223 /**
224 * Creates a new anonymous class.
225 */
226 public JDefinedClass anonymousClass(JClass baseType) {
227 return new JAnonymousClass(baseType);
228 }
230 public JDefinedClass anonymousClass(Class<?> baseType) {
231 return anonymousClass(ref(baseType));
232 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
338 public JType _ref(Class<?> c) {
339 if(c.isPrimitive())
340 return JType.parse(this,c.getName());
341 else
342 return ref(c);
343 }
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 }
368 // assume it's not visible to us.
369 return new JDirectClass(this,fullyQualifiedClassName);
370 }
372 /**
373 * Cached for {@link #wildcard()}.
374 */
375 private JClass wildcard;
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 }
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();
401 // try primitive type
402 try {
403 return JType.parse(this,name);
404 } catch (IllegalArgumentException e) {
405 ;
406 }
408 // existing class
409 return new TypeNameParser(name).parseTypeName();
410 }
412 private final class TypeNameParser {
413 private final String s;
414 private int idx;
416 public TypeNameParser(String s) {
417 this.s = s;
418 }
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;
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 }
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 }
457 JClass clazz = ref(s.substring(start,idx));
459 return parseSuffix(clazz);
460 }
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
470 char ch = s.charAt(idx);
472 if(ch=='<')
473 return parseSuffix(parseArguments(clazz));
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 }
483 return clazz;
484 }
486 /**
487 * Skips whitespaces
488 */
489 private void ws() {
490 while(Character.isWhitespace(s.charAt(idx)) && idx<s.length())
491 idx++;
492 }
494 /**
495 * Parses '<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++;
504 List<JClass> args = new ArrayList<JClass>();
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()]));
514 if(ch!=',')
515 throw new IllegalArgumentException(s);
516 idx++;
517 }
519 }
520 }
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;
537 JReferencedClass(Class<?> _clazz) {
538 super(JCodeModel.this);
539 this._class = _clazz;
540 assert !_class.isArray();
541 }
543 public String name() {
544 return _class.getSimpleName().replace('$','.');
545 }
547 public String fullName() {
548 return _class.getName().replace('$','.');
549 }
551 public String binaryName() {
552 return _class.getName();
553 }
555 public JClass outer() {
556 Class<?> p = _class.getDeclaringClass();
557 if(p==null) return null;
558 return ref(p);
559 }
561 public JPackage _package() {
562 String name = fullName();
564 // this type is array
565 if (name.indexOf('[') != -1)
566 return JCodeModel.this._package("");
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 }
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 }
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 }
602 public boolean isInterface() {
603 return _class.isInterface();
604 }
606 public boolean isAbstract() {
607 return Modifier.isAbstract(_class.getModifiers());
608 }
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 }
618 public boolean isArray() {
619 return false;
620 }
622 public void declare(JFormatter f) {
623 }
625 public JTypeVar[] typeParams() {
626 // TODO: does JDK 1.5 reflection provides these information?
627 return super.typeParams();
628 }
630 protected JClass substituteParams(JTypeVar[] variables, List<JClass> bindings) {
631 // TODO: does JDK 1.5 reflection provides these information?
632 return this;
633 }
634 }
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;
646 static {
647 Map<Class<?>,Class<?>> m1 = new HashMap<Class<?>,Class<?>>();
648 Map<Class<?>,Class<?>> m2 = new HashMap<Class<?>,Class<?>>();
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);
660 for (Map.Entry<Class<?>, Class<?>> e : m1.entrySet())
661 m2.put(e.getValue(),e.getKey());
663 boxToPrimitive = Collections.unmodifiableMap(m1);
664 primitiveToBox = Collections.unmodifiableMap(m2);
666 }
667 }