View Javadoc
1   /**
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Intalio, Inc.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Intalio, Inc. Exolab is a registered
23   *    trademark of Intalio, Inc.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2001-2002 (C) Intalio, Inc. All Rights Reserved.
42   */
43  package org.exolab.javasource;
44  
45  import java.io.File;
46  import java.io.FileWriter;
47  import java.io.IOException;
48  import java.util.Enumeration;
49  import java.util.Vector;
50  
51  /**
52   * This class represents the basic Java "structure" for a Java source file. This
53   * is the base class for JClass and JInterface. <br/> This is a useful utility
54   * when creating in memory source code. The code in this package was modelled
55   * after the Java Reflection API as much as possible to reduce the learning
56   * curve.
57   * 
58   * @author <a href="mailto:skopp AT riege DOT de">Martin Skopp</a>
59   * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
60   * @version $Revision$ $Date: 2005-12-13 14:58:48 -0700 (Tue, 13 Dec
61   *          2005) $
62   */
63  public abstract class JStructure extends JType implements JAnnotatedElement {
64  
65      /**
66       * The Id for Source control systems. <br/> Note: I needed to break the
67       * String into parts to prevent CVS from expanding it here!
68       */
69      private static final String DEFAULT_HEADER = "$" + "Id$";
70  
71      /**
72       * The source control version for listed in the JavaDoc <br/> Note: I needed
73       * to break the String into parts to prevent CVS from expanding it here!
74       */
75      private static final String DEFAULT_VERSION = "$" + "Revision$ $" + "Date$";
76  
77      /** A standard complaint for a bad parameter. */
78      private static final String JSW_SHOULD_NOT_BE_NULL = "argument 'jsw' should not be null.";
79  
80      /** The source header. */
81      private JComment _header;
82  
83      /** The package to which this JStructure belongs. */
84      private String _packageName;
85  
86      /** List of imported classes and packages. */
87      private Vector<String> _imports;
88  
89      /** The Javadoc for this JStructure. */
90      private JDocComment _jdc;
91  
92      /** Implementation of JAnnoatedElement to delagate to. */
93      private JAnnotatedElementHelper _annotatedElement;
94  
95      /**
96       * The JModifiers for this JStructure, which allows us to change the
97       * resulting qualifiers.
98       */
99      private JModifiers _modifiers;
100 
101     /** The set of interfaces implemented/extended by this JStructure. */
102     private Vector<String> _interfaces;
103 
104     /**
105      * Creates a new JStructure with the given name.
106      * 
107      * @param name
108      *            The name of the JStructure.
109      */
110     protected JStructure(final String name) {
111         super(name);
112 
113         // -- verify name is a valid java class name
114         if (!isValidClassName(name)) {
115             String lname = getLocalName();
116             String err = "'" + lname + "' is ";
117             if (JNaming.isKeyword(lname)) {
118                 err += "a reserved word and may not be used as  a class name.";
119             } else {
120                 err += "not a valid Java identifier.";
121             }
122             throw new IllegalArgumentException(err);
123         }
124 
125         _header = null;
126         _packageName = JNaming.getPackageFromClassName(name);
127         _imports = new Vector<String>();
128         _jdc = new JDocComment(JDocDescriptor.createVersionDesc(DEFAULT_VERSION));
129         _annotatedElement = new JAnnotatedElementHelper();
130         _modifiers = new JModifiers();
131         _interfaces = new Vector<String>();
132     }
133 
134     /**
135      * Test the provided name and return true if it is a valid class name.
136      * 
137      * @param classname
138      *            A class name to test.
139      * @return True if the provided class name is a valid class name.
140      */
141     private boolean isValidClassName(final String classname) {
142         if (classname == null) {
143             return false;
144         }
145 
146         String name = classname;
147         int beforeTypeName = name.indexOf("<");
148         if (beforeTypeName > 0) {
149             name = name.substring(0, beforeTypeName);
150         }
151 
152         // -- ignore package information, for now
153         name = JNaming.getLocalNameFromClassName(name);
154 
155         return JNaming.isValidJavaIdentifier(name);
156     }
157 
158     /**
159      * Returns the JComment header to display at the top of the source file for
160      * this JStructure, or null if no header was set.
161      * 
162      * @return The JComment header or null if none exists.
163      */
164     public final JComment getHeader() {
165         return _header;
166     }
167 
168     /**
169      * Sets the header comment for this JStructure.
170      * 
171      * @param comment
172      *            The comment to display at the top of the source file when
173      *            printed.
174      */
175     public final void setHeader(final JComment comment) {
176         _header = comment;
177     }
178 
179     /**
180      * Returns the name of the package that this JStructure is a member of.
181      * 
182      * @return The name of the package that this JStructure is a member of, or
183      *         null if there is no current package name defined.
184      */
185     public final String getPackageName() {
186         return _packageName;
187     }
188 
189     /**
190      * Returns an Enumeration of imported package and class names for this
191      * JStructure.
192      * 
193      * @return The Enumeration of imports. May be empty but will not be null.
194      */
195     public final Enumeration<String> getImports() {
196         return _imports.elements();
197     }
198 
199     /**
200      * Returns the amount of imports.
201      * 
202      * @return The amount of imports.
203      */
204     public final int getImportCount() {
205         return _imports.size();
206     }
207 
208     /**
209      * Returns true if the given classname exists in the imports of this
210      * JStructure.
211      * 
212      * @param classname
213      *            The class name to check for
214      * @return True if the given classname exists in the imports list.
215      */
216     public final boolean hasImport(final String classname) {
217         return _imports.contains(classname);
218     }
219 
220     /**
221      * Adds the given import to this JStructure. Note: You cannot import from
222      * the "default package," so imports with no package are ignored.
223      * 
224      * @param className
225      *            Name of the class to import.
226      */
227     public abstract void addImport(final String className);
228 
229     /**
230      * Adds the given import to this JStructure. Given class name should not be
231      * null or empty. <br/> Note: You cannot import from the "default package,"
232      * so imports with no package are ignored.
233      * 
234      * @param className
235      *            Name of the class to import.
236      */
237     protected final void addImportInternal(final String className) {
238         // -- getPackageName
239         String pkgName = JNaming.getPackageFromClassName(className);
240 
241         if (pkgName != null) {
242             if (pkgName.equals(_packageName) || pkgName.equals("java.lang")) {
243                 return;
244             }
245 
246             // -- for readabilty keep import list sorted, and make sure
247             // -- we do not include more than one of the same import
248             for (int i = 0; i < _imports.size(); i++) {
249                 String imp = _imports.elementAt(i);
250                 if (imp.equals(className)) {
251                     return;
252                 }
253                 if (imp.compareTo(className) > 0) {
254                     _imports.insertElementAt(className, i);
255                     return;
256                 }
257             }
258             _imports.addElement(className);
259         }
260     }
261 
262     /**
263      * Adds appropriate import for this JAnnotation.
264      * 
265      * @param annotation
266      *            A JAnnotation for which we want to add an import to this
267      *            JStructure.
268      */
269     protected final void addImport(final JAnnotation annotation) {
270         addImport(annotation.getAnnotationType().getName());
271     }
272 
273     /**
274      * Adds appropriate imports for each JAnnotation in the given Array.
275      * 
276      * @param annotations
277      *            An Array of JAnnotation; we want to add an import to this
278      *            JStructure for each JAnnotation in the Array.
279      */
280     protected final void addImport(final JAnnotation[] annotations) {
281         for (int i = 0; i < annotations.length; i++) {
282             addImport(annotations[i].getAnnotationType().getName());
283         }
284     }
285 
286     /**
287      * Remove the import of the given class name from this JStucture, returning
288      * true if the import was found and removed.
289      * 
290      * @param className
291      *            Name of the class to remove the import of.
292      * @return If the import was previously part of this JStructure, false
293      *         otherwise.
294      */
295     public final boolean removeImport(final String className) {
296         boolean result = false;
297         if (className == null) {
298             return result;
299         }
300         if (className.length() == 0) {
301             return result;
302         }
303 
304         result = _imports.removeElement(className);
305         return result;
306     }
307 
308     /**
309      * Returns the JavaDoc comment for this JStructure.
310      * 
311      * @return The JDocComment for this JStructure.
312      */
313     public final JDocComment getJDocComment() {
314         return _jdc;
315     }
316 
317     /**
318      * Returns the object managing the annotations for this JStructure.
319      * 
320      * @return The object managing the annotations for this JStructure.
321      */
322     protected final JAnnotatedElementHelper getAnnotatedElementHelper() {
323         return _annotatedElement;
324     }
325 
326     /**
327      * {@inheritDoc}
328      */
329     public final boolean hasAnnotations() {
330         return _annotatedElement.hasAnnotations();
331     }
332 
333     /**
334      * {@inheritDoc}
335      */
336     public final JAnnotation[] getAnnotations() {
337         return _annotatedElement.getAnnotations();
338     }
339 
340     /**
341      * {@inheritDoc}
342      */
343     public final JAnnotation getAnnotation(final JAnnotationType annotationType) {
344         return _annotatedElement.getAnnotation(annotationType);
345     }
346 
347     /**
348      * {@inheritDoc}
349      */
350     public final boolean isAnnotationPresent(
351             final JAnnotationType annotationType) {
352         return _annotatedElement.isAnnotationPresent(annotationType);
353     }
354 
355     /**
356      * {@inheritDoc}
357      */
358     public final void addAnnotation(final JAnnotation annotation) {
359         _annotatedElement.addAnnotation(annotation);
360         addImport(annotation);
361     }
362 
363     /**
364      * {@inheritDoc}
365      */
366     public final JAnnotation removeAnnotation(
367             final JAnnotationType annotationType) {
368         return _annotatedElement.removeAnnotation(annotationType);
369     }
370 
371     /**
372      * Returns the JModifiers, which allows the qualifiers to be changed.
373      * 
374      * @return The JModifiers for this JStructure.
375      */
376     public final JModifiers getModifiers() {
377         return _modifiers;
378     }
379 
380     /**
381      * Returns an Enumeration of interface names that this JStructure inherits
382      * from.
383      * 
384      * @return The Enumeration of interface names for this JStructure. May be
385      *         empty but will not be null.
386      */
387     public final Enumeration<String> getInterfaces() {
388         return _interfaces.elements();
389     }
390 
391     /**
392      * Return the count of the number of Interfaces that have been added to this
393      * JStructure.
394      * 
395      * @return The count of the number of Interfaces that have been added to
396      *         this JStructure.
397      */
398     public final int getInterfaceCount() {
399         return _interfaces.size();
400     }
401 
402     /**
403      * Adds the given interface to the list of interfaces this JStructure
404      * inherits method declarations from, and either implements (JClass) or
405      * extends (JInterface).
406      * 
407      * @param interfaceName
408      *            The name of the interface to "inherit" method declarations
409      *            from.
410      */
411     public final void addInterface(final String interfaceName) {
412         if (!_interfaces.contains(interfaceName)) {
413             _interfaces.addElement(interfaceName);
414         }
415     }
416 
417     /**
418      * Removes the given interface from the list of interfaces this
419      * {@link JStructure} has.
420      * 
421      * @param interfaceName
422      *            The name of the interface to be removed.
423      * @return true if {@link JStructure} implemented the interface and it was
424      *         removed, false otherwise.
425      */
426     public final boolean removeInterface(final String interfaceName) {
427         return _interfaces.remove(interfaceName);
428     }
429 
430     /**
431      * Returns the field with the given name, or null if no field was found with
432      * that name.
433      * 
434      * @param name
435      *            The name of the field to return.
436      * @return The field with the given name, or null if no field was found with
437      *         the given name.
438      */
439     public abstract JField getField(String name);
440 
441     /**
442      * Returns an array of all the JFields of this JStructure.
443      * 
444      * @return An array of all the JFields of this JStructure.
445      */
446     public abstract JField[] getFields();
447 
448     /**
449      * Adds the given JField to this JStructure. <br/> This method is
450      * implemented by subclasses and should only accept the proper fields for
451      * the subclass otherwise an IllegalArgumentException will be thrown. For
452      * example a JInterface will only accept static fields.
453      * 
454      * @param jField
455      *            The JField to add.
456      */
457     public abstract void addField(JField jField);
458 
459     /**
460      * Adds the given JMember to this JStructure. <br/> This method is
461      * implemented by subclasses and should only accept the proper types for the
462      * subclass otherwise an IllegalArgumentException will be thrown.
463      * 
464      * @param jMember
465      *            The JMember to add to this JStructure.
466      */
467     public abstract void addMember(JMember jMember);
468 
469     /**
470      * Returns the name of the file that this JStructure would be printed to,
471      * given a call to {@link #print(String, String)}.
472      * 
473      * @param destDir
474      *            the destination directory. This may be null.
475      * @return the name of the file that this JInterface would be printed to
476      */
477     public final String getFilename(final String destDir) {
478         String filename = getLocalName() + ".java";
479 
480         // -- Convert Java package to path string
481         String javaPackagePath = "";
482         if ((_packageName != null) && (_packageName.length() > 0)) {
483             javaPackagePath = _packageName.replace('.', File.separatorChar);
484         }
485 
486         // -- Create fully qualified path (including 'destDir') to file
487         File pathFile;
488         if (destDir == null) {
489             pathFile = new File(javaPackagePath);
490         } else {
491             pathFile = new File(destDir, javaPackagePath);
492         }
493         if (!pathFile.exists()) {
494             pathFile.mkdirs();
495         }
496 
497         // -- Prefix filename with path
498         if (pathFile.toString().length() > 0) {
499             filename = pathFile.toString() + File.separator + filename;
500         }
501 
502         return filename;
503     }
504 
505     /**
506      * Prints the source code for this JStructure to the destination directory.
507      * Subdirectories will be created if necessary for the package.
508      * 
509      * @param destDir
510      *            Directory name to use as the root directory for all output.
511      * @param lineSeparator
512      *            The line separator to use at the end of each line. If null,
513      *            then the default line separator for the runtime platform will
514      *            be used.
515      */
516     public final void print(final String destDir, final String lineSeparator) {
517         // -- open output file
518         String filename = getFilename(destDir);
519 
520         File file = new File(filename);
521         JSourceWriter jsw = null;
522         try {
523             jsw = new JSourceWriter(new FileWriter(file));
524         } catch (IOException ioe) {
525             System.out.println("unable to create class file: " + filename);
526             return;
527         }
528         if (lineSeparator == null) {
529             jsw.setLineSeparator(System.getProperty("line.separator"));
530         } else {
531             jsw.setLineSeparator(lineSeparator);
532         }
533         print(jsw);
534         jsw.close();
535     }
536 
537     /**
538      * Prints the source code for this JStructure to the given JSourceWriter.
539      * 
540      * @param jsw
541      *            The JSourceWriter to print to.
542      * @deprecated Please use the Velocity-template based approach instead.
543      */
544     public abstract void print(JSourceWriter jsw);
545 
546     /**
547      * A utility method that prints the header to the given JSourceWriter.
548      * 
549      * @param jsw
550      *            The JSourceWriter to print to.
551      */
552     public final void printHeader(final JSourceWriter jsw) {
553         if (jsw == null) {
554             throw new IllegalArgumentException(JSW_SHOULD_NOT_BE_NULL);
555         }
556 
557         // -- write class header
558         if (_header != null) {
559             _header.print(jsw);
560         } else {
561             jsw.writeln("/*");
562             jsw.writeln(" * " + DEFAULT_HEADER);
563             jsw.writeln(" */");
564         }
565         jsw.writeln();
566         jsw.flush();
567     }
568 
569     /**
570      * A utility method that prints the packageDeclaration to the given
571      * JSourceWriter.
572      * 
573      * @param jsw
574      *            The JSourceWriter to print to.
575      */
576     public final void printPackageDeclaration(final JSourceWriter jsw) {
577         if (jsw == null) {
578             throw new IllegalArgumentException(JSW_SHOULD_NOT_BE_NULL);
579         }
580 
581         // -- print package name
582         if ((_packageName != null) && (_packageName.length() > 0)) {
583             jsw.write("package ");
584             jsw.write(_packageName);
585             jsw.writeln(';');
586             jsw.writeln();
587         }
588         jsw.flush();
589     }
590 
591     /**
592      * A utility method that prints the imports to the given JSourceWriter.
593      * 
594      * @param jsw
595      *            The JSourceWriter to print to.
596      */
597     protected final void printImportDeclarations(final JSourceWriter jsw) {
598         if (jsw == null) {
599             throw new IllegalArgumentException(JSW_SHOULD_NOT_BE_NULL);
600         }
601 
602         // -- print imports
603         if (_imports.size() > 0) {
604             jsw.writeln("  //---------------------------------/");
605             jsw.writeln(" //- Imported classes and packages -/");
606             jsw.writeln("//---------------------------------/");
607             jsw.writeln();
608             Enumeration<String> enumeration = _imports.elements();
609             while (enumeration.hasMoreElements()) {
610                 jsw.write("import ");
611                 jsw.write(enumeration.nextElement());
612                 jsw.writeln(';');
613             }
614             jsw.writeln();
615             jsw.flush();
616         }
617     }
618 
619     /**
620      * {@inheritDoc} <br/> Returns the String representation of this JType.
621      */
622     public final String toString() {
623         return getName();
624     }
625 
626     //--------------------------------------------------------------------------
627 }