001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.codemodel.internal;
027:
028: import java.io.BufferedOutputStream;
029: import java.io.BufferedWriter;
030: import java.io.File;
031: import java.io.IOException;
032: import java.io.OutputStream;
033: import java.io.PrintWriter;
034: import java.io.Writer;
035: import java.lang.annotation.Annotation;
036: import java.util.ArrayList;
037: import java.util.HashMap;
038: import java.util.HashSet;
039: import java.util.Iterator;
040: import java.util.List;
041: import java.util.Map;
042: import java.util.Set;
043: import java.util.TreeMap;
044:
045: /**
046: * A Java package.
047: */
048: public final class JPackage implements JDeclaration, JGenerable,
049: JClassContainer, JAnnotatable, Comparable<JPackage> {
050:
051: /**
052: * Name of the package.
053: * May be the empty string for the root package.
054: */
055: private String name;
056:
057: private final JCodeModel owner;
058:
059: /**
060: * List of classes contained within this package keyed by their name.
061: */
062: private final Map<String, JDefinedClass> classes = new TreeMap<String, JDefinedClass>();
063:
064: /**
065: * List of resources files inside this package.
066: */
067: private final Set<JResourceFile> resources = new HashSet<JResourceFile>();
068:
069: /**
070: * All {@link JClass}s in this package keyed the upper case class name.
071: *
072: * This field is non-null only on Windows, to detect
073: * "Foo" and "foo" as a collision.
074: */
075: private final Map<String, JDefinedClass> upperCaseClassMap;
076:
077: /**
078: * Lazily created list of package annotations.
079: */
080: private List<JAnnotationUse> annotations = null;
081:
082: /**
083: * package javadoc.
084: */
085: private JDocComment jdoc = null;
086:
087: /**
088: * JPackage constructor
089: *
090: * @param name
091: * Name of package
092: *
093: * @param cw The code writer being used to create this package
094: *
095: * @throws IllegalArgumentException
096: * If each part of the package name is not a valid identifier
097: */
098: JPackage(String name, JCodeModel cw) {
099: this .owner = cw;
100: if (name.equals(".")) {
101: String msg = "JPackage name . is not allowed";
102: throw new IllegalArgumentException(msg);
103: }
104:
105: int dots = 1;
106: for (int i = 0; i < name.length(); i++) {
107: char c = name.charAt(i);
108: if (c == '.') {
109: dots++;
110: continue;
111: }
112: if (dots > 1) {
113: String msg = "JPackage name " + name
114: + " missing identifier";
115: throw new IllegalArgumentException(msg);
116: } else if (dots == 1 && !Character.isJavaIdentifierStart(c)) {
117: String msg = "JPackage name " + name
118: + " contains illegal "
119: + "character for beginning of identifier: " + c;
120: throw new IllegalArgumentException(msg);
121: } else if (!Character.isJavaIdentifierPart(c)) {
122: String msg = "JPackage name " + name
123: + "contains illegal " + "character: " + c;
124: throw new IllegalArgumentException(msg);
125: }
126: dots = 0;
127: }
128: if (!name.trim().equals("") && dots != 0) {
129: String msg = "JPackage name not allowed to end with .";
130: throw new IllegalArgumentException(msg);
131: }
132:
133: if (JCodeModel.isCaseSensitiveFileSystem)
134: upperCaseClassMap = null;
135: else
136: upperCaseClassMap = new HashMap<String, JDefinedClass>();
137:
138: this .name = name;
139: }
140:
141: public JClassContainer parentContainer() {
142: return parent();
143: }
144:
145: /**
146: * Gets the parent package, or null if this class is the root package.
147: */
148: public JPackage parent() {
149: if (name.length() == 0)
150: return null;
151:
152: int idx = name.lastIndexOf('.');
153: return owner._package(name.substring(0, idx));
154: }
155:
156: public boolean isClass() {
157: return false;
158: }
159:
160: public boolean isPackage() {
161: return true;
162: }
163:
164: public JPackage getPackage() {
165: return this ;
166: }
167:
168: /**
169: * Add a class to this package.
170: *
171: * @param mods
172: * Modifiers for this class declaration
173: *
174: * @param name
175: * Name of class to be added to this package
176: *
177: * @return Newly generated class
178: *
179: * @exception JClassAlreadyExistsException
180: * When the specified class/interface was already created.
181: */
182: public JDefinedClass _class(int mods, String name)
183: throws JClassAlreadyExistsException {
184: return _class(mods, name, ClassType.CLASS);
185: }
186:
187: /**
188: * {@inheritDoc}
189: * @deprecated
190: */
191: public JDefinedClass _class(int mods, String name,
192: boolean isInterface) throws JClassAlreadyExistsException {
193: return _class(mods, name, isInterface ? ClassType.INTERFACE
194: : ClassType.CLASS);
195: }
196:
197: public JDefinedClass _class(int mods, String name,
198: ClassType classTypeVal) throws JClassAlreadyExistsException {
199: if (classes.containsKey(name))
200: throw new JClassAlreadyExistsException(classes.get(name));
201: else {
202: // XXX problems caught in the NC constructor
203: JDefinedClass c = new JDefinedClass(this , mods, name,
204: classTypeVal);
205:
206: if (upperCaseClassMap != null) {
207: JDefinedClass dc = upperCaseClassMap.get(name
208: .toUpperCase());
209: if (dc != null)
210: throw new JClassAlreadyExistsException(dc);
211: upperCaseClassMap.put(name.toUpperCase(), c);
212: }
213: classes.put(name, c);
214: return c;
215: }
216: }
217:
218: /**
219: * Adds a public class to this package.
220: */
221: public JDefinedClass _class(String name)
222: throws JClassAlreadyExistsException {
223: return _class(JMod.PUBLIC, name);
224: }
225:
226: /**
227: * Gets a reference to the already created {@link JDefinedClass}.
228: *
229: * @return null
230: * If the class is not yet created.
231: */
232: public JDefinedClass _getClass(String name) {
233: if (classes.containsKey(name))
234: return classes.get(name);
235: else
236: return null;
237: }
238:
239: /**
240: * Order is based on the lexicological order of the package name.
241: */
242: public int compareTo(JPackage that) {
243: return this .name.compareTo(that.name);
244: }
245:
246: /**
247: * Add an interface to this package.
248: *
249: * @param mods
250: * Modifiers for this interface declaration
251: *
252: * @param name
253: * Name of interface to be added to this package
254: *
255: * @return Newly generated interface
256: */
257: public JDefinedClass _interface(int mods, String name)
258: throws JClassAlreadyExistsException {
259: return _class(mods, name, ClassType.INTERFACE);
260: }
261:
262: /**
263: * Adds a public interface to this package.
264: */
265: public JDefinedClass _interface(String name)
266: throws JClassAlreadyExistsException {
267: return _interface(JMod.PUBLIC, name);
268: }
269:
270: /**
271: * Add an annotationType Declaration to this package
272: * @param name
273: * Name of the annotation Type declaration to be added to this package
274: * @return
275: * newly created Annotation Type Declaration
276: * @exception JClassAlreadyExistsException
277: * When the specified class/interface was already created.
278:
279: */
280: public JDefinedClass _annotationTypeDeclaration(String name)
281: throws JClassAlreadyExistsException {
282: return _class(JMod.PUBLIC, name, ClassType.ANNOTATION_TYPE_DECL);
283: }
284:
285: /**
286: * Add a public enum to this package
287: * @param name
288: * Name of the enum to be added to this package
289: * @return
290: * newly created Enum
291: * @exception JClassAlreadyExistsException
292: * When the specified class/interface was already created.
293:
294: */
295: public JDefinedClass _enum(String name)
296: throws JClassAlreadyExistsException {
297: return _class(JMod.PUBLIC, name, ClassType.ENUM);
298: }
299:
300: /**
301: * Adds a new resource file to this package.
302: */
303: public JResourceFile addResourceFile(JResourceFile rsrc) {
304: resources.add(rsrc);
305: return rsrc;
306: }
307:
308: /**
309: * Checks if a resource of the given name exists.
310: */
311: public boolean hasResourceFile(String name) {
312: for (JResourceFile r : resources)
313: if (r.name().equals(name))
314: return true;
315: return false;
316: }
317:
318: /**
319: * Iterates all resource files in this package.
320: */
321: public Iterator propertyFiles() {
322: return resources.iterator();
323: }
324:
325: /**
326: * Creates, if necessary, and returns the package javadoc for this
327: * JDefinedClass.
328: *
329: * @return JDocComment containing javadocs for this class
330: */
331: public JDocComment javadoc() {
332: if (jdoc == null)
333: jdoc = new JDocComment(owner());
334: return jdoc;
335: }
336:
337: /**
338: * Removes a class from this package.
339: */
340: public void remove(JClass c) {
341: if (c._package() != this )
342: throw new IllegalArgumentException(
343: "the specified class is not a member of this package,"
344: + " or it is a referenced class");
345:
346: // note that c may not be a member of classes.
347: // this happens when someone is trying to remove a non generated class
348: classes.remove(c.name());
349: if (upperCaseClassMap != null)
350: upperCaseClassMap.remove(c.name().toUpperCase());
351: }
352:
353: /**
354: * Reference a class within this package.
355: */
356: public JClass ref(String name) throws ClassNotFoundException {
357: if (name.indexOf('.') >= 0)
358: throw new IllegalArgumentException(
359: "JClass name contains '.': " + name);
360:
361: String n = "";
362: if (!isUnnamed())
363: n = this .name + '.';
364: n += name;
365:
366: return owner.ref(Class.forName(n));
367: }
368:
369: /**
370: * Gets a reference to a sub package of this package.
371: */
372: public JPackage subPackage(String pkg) {
373: if (isUnnamed())
374: return owner()._package(pkg);
375: else
376: return owner()._package(name + '.' + pkg);
377: }
378:
379: /**
380: * Returns an iterator that walks the top-level classes defined in this
381: * package.
382: */
383: public Iterator<JDefinedClass> classes() {
384: return classes.values().iterator();
385: }
386:
387: /**
388: * Checks if a given name is already defined as a class/interface
389: */
390: public boolean isDefined(String classLocalName) {
391: Iterator itr = classes();
392: while (itr.hasNext()) {
393: if (((JClass) itr.next()).name().equals(classLocalName))
394: return true;
395: }
396:
397: return false;
398: }
399:
400: /**
401: * Checks if this package is the root, unnamed package.
402: */
403: public final boolean isUnnamed() {
404: return name.length() == 0;
405: }
406:
407: /**
408: * Get the name of this package
409: *
410: * @return
411: * The name of this package, or the empty string if this is the
412: * null package. For example, this method returns strings like
413: * <code>"java.lang"</code>
414: */
415: public String name() {
416: return name;
417: }
418:
419: /**
420: * Return the code model root object being used to create this package.
421: */
422: public final JCodeModel owner() {
423: return owner;
424: }
425:
426: public JAnnotationUse annotate(JClass clazz) {
427: if (isUnnamed())
428: throw new IllegalArgumentException(
429: "the root package cannot be annotated");
430: if (annotations == null)
431: annotations = new ArrayList<JAnnotationUse>();
432: JAnnotationUse a = new JAnnotationUse(clazz);
433: annotations.add(a);
434: return a;
435: }
436:
437: public JAnnotationUse annotate(Class<? extends Annotation> clazz) {
438: return annotate(owner.ref(clazz));
439: }
440:
441: public <W extends JAnnotationWriter> W annotate2(Class<W> clazz) {
442: return TypedAnnotationWriter.create(clazz, this );
443: }
444:
445: /**
446: * Convert the package name to directory path equivalent
447: */
448: File toPath(File dir) {
449: if (name == null)
450: return dir;
451: return new File(dir, name.replace('.', File.separatorChar));
452: }
453:
454: public void declare(JFormatter f) {
455: if (name.length() != 0)
456: f.p("package").p(name).p(';').nl();
457: }
458:
459: public void generate(JFormatter f) {
460: f.p(name);
461: }
462:
463: void build(CodeWriter src, CodeWriter res) throws IOException {
464:
465: // write classes
466: for (JDefinedClass c : classes.values()) {
467: if (c.isHidden())
468: continue; // don't generate this file
469:
470: JFormatter f = createJavaSourceFileWriter(src, c.name());
471: f.write(c);
472: f.close();
473: }
474:
475: // write package annotations
476: if (annotations != null || jdoc != null) {
477: JFormatter f = createJavaSourceFileWriter(src,
478: "package-info");
479:
480: if (jdoc != null)
481: f.g(jdoc);
482:
483: // TODO: think about importing
484: if (annotations != null) {
485: for (JAnnotationUse a : annotations)
486: f.g(a).nl();
487: }
488: f.d(this );
489:
490: f.close();
491: }
492:
493: // write resources
494: for (JResourceFile rsrc : resources) {
495: CodeWriter cw = rsrc.isResource() ? res : src;
496: OutputStream os = new BufferedOutputStream(cw.openBinary(
497: this , rsrc.name()));
498: rsrc.build(os);
499: os.close();
500: }
501: }
502:
503: /*package*/int countArtifacts() {
504: int r = 0;
505: for (JDefinedClass c : classes.values()) {
506: if (c.isHidden())
507: continue; // don't generate this file
508: r++;
509: }
510:
511: if (annotations != null || jdoc != null) {
512: r++;
513: }
514:
515: r += resources.size();
516:
517: return r;
518: }
519:
520: private JFormatter createJavaSourceFileWriter(CodeWriter src,
521: String className) throws IOException {
522: Writer bw = new BufferedWriter(src.openSource(this , className
523: + ".java"));
524: return new JFormatter(new PrintWriter(bw));
525: }
526: }
|