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