001: /*
002: * The contents of this file are subject to the terms
003: * of the Common Development and Distribution License
004: * (the "License"). You may not use this file except
005: * in compliance with the License.
006: *
007: * You can obtain a copy of the license at
008: * https://jwsdp.dev.java.net/CDDLv1.0.html
009: * See the License for the specific language governing
010: * permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL
013: * HEADER in each file and include the License file at
014: * https://jwsdp.dev.java.net/CDDLv1.0.html If applicable,
015: * add the following below this CDDL HEADER, with the
016: * fields enclosed by brackets "[]" replaced with your
017: * own identifying information: Portions Copyright [yyyy]
018: * [name of copyright owner]
019: */
020:
021: package com.sun.codemodel;
022:
023: import java.io.File;
024: import java.io.IOException;
025: import java.io.PrintStream;
026: import java.lang.reflect.Modifier;
027: import java.util.ArrayList;
028: import java.util.Collections;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.List;
032: import java.util.Map;
033:
034: import com.sun.codemodel.writer.FileCodeWriter;
035: import com.sun.codemodel.writer.ProgressCodeWriter;
036:
037: /**
038: * Root of the code DOM.
039: *
040: * <p>
041: * Here's your typical CodeModel application.
042: *
043: * <pre>
044: * JCodeModel cm = new JCodeModel();
045: *
046: * // generate source code by populating the 'cm' tree.
047: * cm._class(...);
048: * ...
049: *
050: * // write them out
051: * cm.build(new File("."));
052: * </pre>
053: *
054: * <p>
055: * Every CodeModel node is always owned by one {@link JCodeModel} object
056: * at any given time (which can be often accesesd by the <tt>owner()</tt> method.)
057: *
058: * As such, when you generate Java code, most of the operation works
059: * in a top-down fashion. For example, you create a class from {@link JCodeModel},
060: * which gives you a {@link JDefinedClass}. Then you invoke a method on it
061: * to generate a new method, which gives you {@link JMethod}, and so on.
062: *
063: * There are a few exceptions to this, most notably building {@link JExpression}s,
064: * but generally you work with CodeModel in a top-down fashion.
065: *
066: * Because of this design, most of the CodeModel classes aren't directly instanciable.
067: *
068: *
069: * <h2>Where to go from here?</h2>
070: * <p>
071: * Most of the time you'd want to populate new type definitions in a {@link JCodeModel}.
072: * See {@link #_class(String, ClassType)}.
073: */
074: public final class JCodeModel {
075:
076: /** The packages that this JCodeWriter contains. */
077: private HashMap<String, JPackage> packages = new HashMap<String, JPackage>();
078:
079: /** All JReferencedClasses are pooled here. */
080: private final HashMap<Class, JReferencedClass> refClasses = new HashMap<Class, JReferencedClass>();
081:
082: /** Obtains a reference to the special "null" type. */
083: public final JNullType NULL = new JNullType(this );
084: // primitive types
085: public final JPrimitiveType VOID = new JPrimitiveType(this , "void",
086: Void.class);
087: public final JPrimitiveType BOOLEAN = new JPrimitiveType(this ,
088: "boolean", Boolean.class);
089: public final JPrimitiveType BYTE = new JPrimitiveType(this , "byte",
090: Byte.class);
091: public final JPrimitiveType SHORT = new JPrimitiveType(this ,
092: "short", Short.class);
093: public final JPrimitiveType CHAR = new JPrimitiveType(this , "char",
094: Character.class);
095: public final JPrimitiveType INT = new JPrimitiveType(this , "int",
096: Integer.class);
097: public final JPrimitiveType FLOAT = new JPrimitiveType(this ,
098: "float", Float.class);
099: public final JPrimitiveType LONG = new JPrimitiveType(this , "long",
100: Long.class);
101: public final JPrimitiveType DOUBLE = new JPrimitiveType(this ,
102: "double", Double.class);
103:
104: /**
105: * If the flag is true, we will consider two classes "Foo" and "foo"
106: * as a collision.
107: */
108: protected static final boolean isCaseSensitiveFileSystem = getFileSystemCaseSensitivity();
109:
110: private static boolean getFileSystemCaseSensitivity() {
111: try {
112: // let the system property override, in case the user really
113: // wants to override.
114: if (System
115: .getProperty("com.sun.codemodel.FileSystemCaseSensitive") != null)
116: return true;
117: } catch (Exception e) {
118: }
119:
120: // on Unix, it's case sensitive.
121: return (File.separatorChar == '/');
122: }
123:
124: public JCodeModel() {
125: }
126:
127: /**
128: * Add a package to the list of packages to be generated
129: *
130: * @param name
131: * Name of the package. Use "" to indicate the root package.
132: *
133: * @return Newly generated package
134: */
135: public JPackage _package(String name) {
136: JPackage p = packages.get(name);
137: if (p == null) {
138: p = new JPackage(name, this );
139: packages.put(name, p);
140: }
141: return p;
142: }
143:
144: public final JPackage rootPackage() {
145: return _package("");
146: }
147:
148: /**
149: * Returns an iterator that walks the packages defined using this code
150: * writer.
151: */
152: public Iterator<JPackage> packages() {
153: return packages.values().iterator();
154: }
155:
156: /**
157: * Creates a new generated class.
158: *
159: * @exception JClassAlreadyExistsException
160: * When the specified class/interface was already created.
161: */
162: public JDefinedClass _class(String fullyqualifiedName)
163: throws JClassAlreadyExistsException {
164: return _class(fullyqualifiedName, ClassType.CLASS);
165: }
166:
167: /**
168: * Creates a dummy, unknown {@link JClass} that represents a given name.
169: *
170: * <p>
171: * This method is useful when the code generation needs to include the user-specified
172: * class that may or may not exist, and only thing known about it is a class name.
173: */
174: public JClass directClass(String name) {
175: return new JDirectClass(this , name);
176: }
177:
178: /**
179: * Creates a new generated class.
180: *
181: * @exception JClassAlreadyExistsException
182: * When the specified class/interface was already created.
183: */
184: public JDefinedClass _class(String fullyqualifiedName, ClassType t)
185: throws JClassAlreadyExistsException {
186: int idx = fullyqualifiedName.lastIndexOf('.');
187: if (idx < 0)
188: return rootPackage()._class(fullyqualifiedName);
189: else
190: return _package(fullyqualifiedName.substring(0, idx))
191: ._class(JMod.PUBLIC,
192: fullyqualifiedName.substring(idx + 1), t);
193: }
194:
195: /**
196: * Gets a reference to the already created generated class.
197: *
198: * @return null
199: * If the class is not yet created.
200: * @see JPackage#_getClass(String)
201: */
202: public JDefinedClass _getClass(String fullyQualifiedName) {
203: int idx = fullyQualifiedName.lastIndexOf('.');
204: if (idx < 0)
205: return rootPackage()._getClass(fullyQualifiedName);
206: else
207: return _package(fullyQualifiedName.substring(0, idx))
208: ._getClass(fullyQualifiedName.substring(idx + 1));
209: }
210:
211: /**
212: * Creates a new anonymous class.
213: *
214: * @deprecated
215: * The naming convention doesn't match the rest of the CodeModel.
216: * Use {@link #anonymousClass(JClass)} instead.
217: */
218: public JDefinedClass newAnonymousClass(JClass baseType) {
219: return new JAnonymousClass(baseType);
220: }
221:
222: /**
223: * Creates a new anonymous class.
224: */
225: public JDefinedClass anonymousClass(JClass baseType) {
226: return new JAnonymousClass(baseType);
227: }
228:
229: public JDefinedClass anonymousClass(Class baseType) {
230: return anonymousClass(ref(baseType));
231: }
232:
233: /**
234: * Generates Java source code.
235: * A convenience method for <code>build(destDir,destDir,System.out)</code>.
236: *
237: * @param destDir
238: * source files are generated into this directory.
239: * @param status
240: * if non-null, progress indication will be sent to this stream.
241: */
242: public void build(File destDir, PrintStream status)
243: 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)
259: throws IOException {
260: CodeWriter src = new FileCodeWriter(srcDir);
261: CodeWriter res = new FileCodeWriter(resourceDir);
262: if (status != null) {
263: src = new ProgressCodeWriter(src, status);
264: res = new ProgressCodeWriter(res, status);
265: }
266: build(src, res);
267: }
268:
269: /**
270: * A convenience method for <code>build(destDir,System.out)</code>.
271: */
272: public void build(File destDir) throws IOException {
273: build(destDir, System.out);
274: }
275:
276: /**
277: * A convenience method for <code>build(srcDir,resourceDir,System.out)</code>.
278: */
279: public void build(File srcDir, File resourceDir) throws IOException {
280: build(srcDir, resourceDir, System.out);
281: }
282:
283: /**
284: * A convenience method for <code>build(out,out)</code>.
285: */
286: public void build(CodeWriter out) throws IOException {
287: build(out, out);
288: }
289:
290: /**
291: * Generates Java source code.
292: */
293: public void build(CodeWriter source, CodeWriter resource)
294: throws IOException {
295: JPackage[] pkgs = packages.values().toArray(
296: new JPackage[packages.size()]);
297: // avoid concurrent modification exception
298: for (JPackage pkg : pkgs)
299: pkg.build(source, resource);
300: source.close();
301: resource.close();
302: }
303:
304: /**
305: * Returns the number of files to be generated if
306: * {@link #build} is invoked now.
307: */
308: public int countArtifacts() {
309: int r = 0;
310: JPackage[] pkgs = packages.values().toArray(
311: new JPackage[packages.size()]);
312: // avoid concurrent modification exception
313: for (JPackage pkg : pkgs)
314: r += pkg.countArtifacts();
315: return r;
316: }
317:
318: /**
319: * Obtains a reference to an existing class from its Class object.
320: *
321: * <p>
322: * The parameter may not be primitive.
323: *
324: * @see #_ref(Class) for the version that handles more cases.
325: */
326: public JClass ref(Class clazz) {
327: JReferencedClass jrc = (JReferencedClass) refClasses.get(clazz);
328: if (jrc == null) {
329: if (clazz.isPrimitive())
330: throw new IllegalArgumentException(clazz
331: + " is a primitive");
332: if (clazz.isArray()) {
333: return new JArrayClass(this , _ref(clazz
334: .getComponentType()));
335: } else {
336: jrc = new JReferencedClass(clazz);
337: refClasses.put(clazz, jrc);
338: }
339: }
340: return jrc;
341: }
342:
343: public JType _ref(Class c) {
344: if (c.isPrimitive())
345: return JType.parse(this , c.getName());
346: else
347: return ref(c);
348: }
349:
350: /**
351: * Obtains a reference to an existing class from its fully-qualified
352: * class name.
353: *
354: * <p>
355: * First, this method attempts to load the class of the given name.
356: * If that fails, we assume that the class is derived straight from
357: * {@link Object}, and return a {@link JClass}.
358: */
359: public JClass ref(String fullyQualifiedClassName) {
360: try {
361: // try the context class loader first
362: return ref(Thread.currentThread().getContextClassLoader()
363: .loadClass(fullyQualifiedClassName));
364: } catch (ClassNotFoundException e) {
365: // fall through
366: }
367: // then the default mechanism.
368: try {
369: return ref(Class.forName(fullyQualifiedClassName));
370: } catch (ClassNotFoundException e1) {
371: // fall through
372: }
373:
374: // assume it's not visible to us.
375: return new JDirectClass(this , fullyQualifiedClassName);
376: }
377:
378: /**
379: * Cached for {@link #wildcard()}.
380: */
381: private JClass wildcard;
382:
383: /**
384: * Gets a {@link JClass} representation for "?",
385: * which is equivalent to "? extends Object".
386: */
387: public JClass wildcard() {
388: if (wildcard == null)
389: wildcard = ref(Object.class).wildcard();
390: return wildcard;
391: }
392:
393: /**
394: * Obtains a type object from a type name.
395: *
396: * <p>
397: * This method handles primitive types, arrays, and existing {@link Class}es.
398: *
399: * @exception ClassNotFoundException
400: * If the specified type is not found.
401: */
402: public JType parseType(String name) throws ClassNotFoundException {
403: // array
404: if (name.endsWith("[]"))
405: return parseType(name.substring(0, name.length() - 2))
406: .array();
407:
408: // try primitive type
409: try {
410: return JType.parse(this , name);
411: } catch (IllegalArgumentException e) {
412: ;
413: }
414:
415: // existing class
416: return new TypeNameParser(name).parseTypeName();
417: }
418:
419: private final class TypeNameParser {
420: private final String s;
421: private int idx;
422:
423: public TypeNameParser(String s) {
424: this .s = s;
425: }
426:
427: /**
428: * Parses a type name token T (which can be potentially of the form Tr&ly;T1,T2,...>,
429: * or "? extends/super T".)
430: *
431: * @return the index of the character next to T.
432: */
433: JClass parseTypeName() throws ClassNotFoundException {
434: int start = idx;
435:
436: if (s.charAt(idx) == '?') {
437: // wildcard
438: idx++;
439: ws();
440: String head = s.substring(idx);
441: if (head.startsWith("extends")) {
442: idx += 7;
443: ws();
444: return parseTypeName().wildcard();
445: } else if (head.startsWith("super")) {
446: throw new UnsupportedOperationException(
447: "? super T not implemented");
448: } else {
449: // not supported
450: throw new IllegalArgumentException(
451: "only extends/super can follow ?, but found "
452: + s.substring(idx));
453: }
454: }
455:
456: while (idx < s.length()) {
457: char ch = s.charAt(idx);
458: if (Character.isJavaIdentifierStart(ch)
459: || Character.isJavaIdentifierPart(ch)
460: || ch == '.')
461: idx++;
462: else
463: break;
464: }
465:
466: JClass clazz = ref(s.substring(start, idx));
467:
468: return parseSuffix(clazz);
469: }
470:
471: /**
472: * Parses additional left-associative suffixes, like type arguments
473: * and array specifiers.
474: */
475: private JClass parseSuffix(JClass clazz)
476: throws ClassNotFoundException {
477: if (idx == s.length())
478: return clazz; // hit EOL
479:
480: char ch = s.charAt(idx);
481:
482: if (ch == '<')
483: return parseSuffix(parseArguments(clazz));
484:
485: if (ch == '[') {
486: if (s.charAt(idx + 1) == ']') {
487: idx += 2;
488: return parseSuffix(clazz.array());
489: }
490: throw new IllegalArgumentException(
491: "Expected ']' but found "
492: + s.substring(idx + 1));
493: }
494:
495: return clazz;
496: }
497:
498: /**
499: * Skips whitespaces
500: */
501: private void ws() {
502: while (Character.isWhitespace(s.charAt(idx))
503: && idx < s.length())
504: idx++;
505: }
506:
507: /**
508: * Parses '<T1,T2,...,Tn>'
509: *
510: * @return the index of the character next to '>'
511: */
512: private JClass parseArguments(JClass rawType)
513: throws ClassNotFoundException {
514: if (s.charAt(idx) != '<')
515: throw new IllegalArgumentException();
516: idx++;
517:
518: List<JClass> args = new ArrayList<JClass>();
519:
520: while (true) {
521: args.add(parseTypeName());
522: if (idx == s.length())
523: throw new IllegalArgumentException(
524: "Missing '>' in " + s);
525: char ch = s.charAt(idx);
526: if (ch == '>')
527: return rawType.narrow(args.toArray(new JClass[args
528: .size()]));
529:
530: if (ch != ',')
531: throw new IllegalArgumentException(s);
532: idx++;
533: }
534:
535: }
536: }
537:
538: /**
539: * References to existing classes.
540: *
541: * <p>
542: * JReferencedClass is kept in a pool so that they are shared.
543: * There is one pool for each JCodeModel object.
544: *
545: * <p>
546: * It is impossible to cache JReferencedClass globally only because
547: * there is the _package() method, which obtains the owner JPackage
548: * object, which is scoped to JCodeModel.
549: */
550: private class JReferencedClass extends JClass implements
551: JDeclaration {
552: private final Class _class;
553:
554: JReferencedClass(Class _clazz) {
555: super (JCodeModel.this );
556: this ._class = _clazz;
557: assert !_class.isArray();
558: }
559:
560: public String name() {
561: return _class.getSimpleName().replace('$', '.');
562: }
563:
564: public String fullName() {
565: return _class.getName().replace('$', '.');
566: }
567:
568: public String binaryName() {
569: return _class.getName();
570: }
571:
572: public JClass outer() {
573: Class p = _class.getDeclaringClass();
574: if (p == null)
575: return null;
576: return ref(p);
577: }
578:
579: public JPackage _package() {
580: String name = fullName();
581:
582: // this type is array
583: if (name.indexOf('[') != -1)
584: return JCodeModel.this ._package("");
585:
586: // other normal case
587: int idx = name.lastIndexOf('.');
588: if (idx < 0)
589: return JCodeModel.this ._package("");
590: else
591: return JCodeModel.this ._package(name.substring(0, idx));
592: }
593:
594: public JClass _extends() {
595: Class sp = _class.getSuperclass();
596: if (sp == null) {
597: if (isInterface())
598: return owner().ref(Object.class);
599: return null;
600: } else
601: return ref(sp);
602: }
603:
604: public Iterator<JClass> _implements () {
605: final Class[] interfaces = _class.getInterfaces();
606: return new Iterator<JClass>() {
607: private int idx = 0;
608:
609: public boolean hasNext() {
610: return idx < interfaces.length;
611: }
612:
613: public JClass next() {
614: return JCodeModel.this .ref(interfaces[idx++]);
615: }
616:
617: public void remove() {
618: throw new UnsupportedOperationException();
619: }
620: };
621: }
622:
623: public boolean isInterface() {
624: return _class.isInterface();
625: }
626:
627: public boolean isAbstract() {
628: return Modifier.isAbstract(_class.getModifiers());
629: }
630:
631: public JPrimitiveType getPrimitiveType() {
632: Class v = boxToPrimitive.get(_class);
633: if (v != null)
634: return JType.parse(JCodeModel.this , v.getName());
635: else
636: return null;
637: }
638:
639: public boolean isArray() {
640: return false;
641: }
642:
643: public void declare(JFormatter f) {
644: }
645:
646: public JTypeVar[] typeParams() {
647: // TODO: does JDK 1.5 reflection provides these information?
648: return super .typeParams();
649: }
650:
651: protected JClass substituteParams(JTypeVar[] variables,
652: List<JClass> bindings) {
653: // TODO: does JDK 1.5 reflection provides these information?
654: return this ;
655: }
656: }
657:
658: /**
659: * Conversion from primitive type {@link Class} (such as {@link Integer#TYPE}
660: * to its boxed type (such as <tt>Integer.class</tt>)
661: */
662: public static final Map<Class, Class> primitiveToBox;
663: /**
664: * The reverse look up for {@link #primitiveToBox}
665: */
666: public static final Map<Class, Class> boxToPrimitive;
667:
668: static {
669: Map<Class, Class> m1 = new HashMap<Class, Class>();
670: Map<Class, Class> m2 = new HashMap<Class, Class>();
671:
672: m1.put(Boolean.class, Boolean.TYPE);
673: m1.put(Byte.class, Byte.TYPE);
674: m1.put(Character.class, Character.TYPE);
675: m1.put(Double.class, Double.TYPE);
676: m1.put(Float.class, Float.TYPE);
677: m1.put(Integer.class, Integer.TYPE);
678: m1.put(Long.class, Long.TYPE);
679: m1.put(Short.class, Short.TYPE);
680: m1.put(Void.class, Void.TYPE);
681:
682: for (Map.Entry<Class, Class> e : m1.entrySet())
683: m2.put(e.getValue(), e.getKey());
684:
685: boxToPrimitive = Collections.unmodifiableMap(m1);
686: primitiveToBox = Collections.unmodifiableMap(m2);
687:
688: }
689: }
|