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.util.Enumeration;
38  import java.util.SortedSet;
39  import java.util.TreeSet;
40  import java.util.Vector;
41  
42  /**
43   * A representation of the Java Source code for a Java compilation unit. This is a useful utility
44   * when creating in memory source code. This package was modelled after the Java Reflection API as
45   * much as possible to reduce the learning curve.
46   *
47   * @author <a href="mailto:shea AT gtsdesign DOT com">Gary Shea</a>
48   * @version $Revision$ $Date: 2005-03-05 06:42:06 -0700 (Sat, 05 Mar 2005) $
49   */
50  public final class JCompUnit {
51  
52    /**
53     * Initial size for {@link StringBuilder} instances.
54     */
55    private static final int INITIAL_STRING_BUILDER_SIZE = 32;
56  
57    /**
58     * The Id for Source control systems I needed to break the String to prevent CVS from expanding it
59     * here! ;-)
60     */
61    private static final String DEFAULT_HEADER = "$" + "Id$";
62  
63    /** Public header. */
64    private static final String[] PUBLIC_HEADER = {"  //-----------------------------/",
65        " //-  Public Class / Interface -/", "//-----------------------------/",};
66  
67    /** Private header. */
68    private static final String[] NON_PUBLIC_HEADER = {"  //-------------------------------------/",
69        " //-  Non-Public Classes / Interfaces  -/", "//-------------------------------------/",};
70  
71    /** JavaDoc comment for this compilation unit. */
72    private JComment _header = null;
73  
74    /** The package for this JCompUnit. */
75    private final String _packageName;
76  
77    /** The file to which this JCompUnit will be written. */
78    private final String _fileName;
79  
80    /** The set of top-level classes that live in this compilation unit. */
81    private final Vector<JClass> _classes = new Vector<JClass>();
82  
83    /** The set of top-level interfaces that live in this compilation unit. */
84    private final Vector<JInterface> _interfaces = new Vector<JInterface>();
85  
86    /**
87     * Creates a new JCompUnit.
88     *
89     * @param packageName The name of the package for this JCompUnit. If packageName is null or empty,
90     *        no 'package' line will be generated.
91     * @param fileName The name of the file to which this JCompUnit will be written.
92     */
93    public JCompUnit(final String packageName, final String fileName) {
94      _packageName = packageName;
95      _fileName = fileName;
96    }
97  
98    /**
99     * Creates a new JCompUnit with the given JClass (which must have been created with either a full
100    * class name or package/local name) as the public class. Package and file name are taken from
101    * jClass.
102    *
103    * @param jClass The public class for this JCompUnit.
104    */
105   public JCompUnit(final JClass jClass) {
106     this(jClass.getPackageName(), jClass.getLocalName() + ".java");
107 
108     // The outer name is the package plus the simple name of the
109     // outermost enclosing class. The file name is just the
110     // simple name of the outermost enclosing class, so the
111     // package name part must be stripped off.
112 
113     // Commented out until inner-class support has been added.
114     // kvisco - 20021211
115     //
116     // String outer = jClass.getOuterName();
117     // int lastDot = outer.lastIndexOf(".");
118     // String filePrefix;
119     // if (lastDot != -1) {
120     // filePrefix = outer.substring (lastDot + 1);
121     // } else {
122     // filePrefix = outer;
123     // }
124 
125     _classes.add(jClass);
126   }
127 
128   /**
129    * Creates a new JCompUnit with the given JInterface as public interface. Package and file name
130    * are taken from jInterface.
131    *
132    * @param jInterface The public interface for this JCompUnit.
133    */
134   public JCompUnit(final JInterface jInterface) {
135     this(jInterface.getPackageName(), jInterface.getLocalName() + ".java");
136     _interfaces.add(jInterface);
137   }
138 
139   /**
140    * Sets the header comment for this JCompUnit.
141    *
142    * @param comment The comment to display at the top of the source file when printed.
143    */
144   public void setHeader(final JComment comment) {
145     _header = comment;
146   }
147 
148   /**
149    * Adds the given JStructure (either a JInterface or a JClass) to this JCompUnit.
150    *
151    * @param jStructure The JStructure to add.
152    */
153   public void addStructure(final JStructure jStructure) {
154     if (jStructure instanceof JInterface) {
155       addInterface((JInterface) jStructure);
156     } else if (jStructure instanceof JClass) {
157       addClass((JClass) jStructure);
158     } else {
159       String err = "Unknown JStructure subclass '" + jStructure.getClass().getName() + "'.";
160       throw new IllegalArgumentException(err);
161     }
162   }
163 
164   /**
165    * Adds a JClass to be printed in this file.
166    *
167    * @param jClass The JClass to be printed in this file.
168    */
169   public void addClass(final JClass jClass) {
170     _classes.add(jClass);
171   }
172 
173   /**
174    * Adds a JInterface to be printed in this file.
175    *
176    * @param jInterface The JInterface to be printed in this file.
177    */
178   public void addInterface(final JInterface jInterface) {
179     _interfaces.add(jInterface);
180   }
181 
182   /**
183    * Returns a array of String containing all imported classes/packages, also imports within the
184    * same package of this object.
185    *
186    * @return A array of String containing all import classes/packages, also imports within the same
187    *         package of this object.
188    */
189   public SortedSet<String> getImports() {
190     SortedSet<String> allImports = new TreeSet<String>();
191 
192     // add imports from classes
193     for (JClass jClass : _classes) {
194       Enumeration<String> enumeration = jClass.getImports();
195       while (enumeration.hasMoreElements()) {
196         allImports.add(enumeration.nextElement());
197       }
198     }
199 
200     for (JInterface jInterface : _interfaces) {
201       Enumeration<String> enumeration = jInterface.getImports();
202       while (enumeration.hasMoreElements()) {
203         allImports.add(enumeration.nextElement());
204       }
205     }
206 
207     return allImports;
208   }
209 
210   /**
211    * Returns the name of the file that this JCompUnit would be printed to, given a call to
212    * {@link #print(String, String)}, or if destDir is null, a call to {@link #print()}.
213    *
214    * @param destDir The destination directory. This may be null.
215    * @return The name of the file that this JCompUnit would be printed to.
216    */
217   public String getFilename(final String destDir) {
218     // -- Convert Java package to path string
219     String javaPackagePath = "";
220     if (_packageName != null && !_packageName.isEmpty()) {
221       javaPackagePath = _packageName.replace('.', File.separatorChar);
222     }
223 
224     // -- Create fully qualified path (including 'destDir') to file
225     File pathFile;
226     if (destDir == null) {
227       pathFile = new File(javaPackagePath);
228     } else {
229       pathFile = new File(destDir, javaPackagePath);
230     }
231     if (!pathFile.exists()) {
232       pathFile.mkdirs();
233     }
234 
235     // -- Prefix filename with path
236     String filename = _fileName;
237     final String pathStr = pathFile.toString();
238     if (!pathStr.isEmpty()) {
239       filename = pathStr + File.separator + filename;
240     }
241 
242     return filename;
243   }
244 
245   /**
246    * Returns the name of the package that this JCompUnit is a member of.
247    *
248    * @return The name of the package that this JCompUnit is a member of, or null if there is no
249    *         current package name defined.
250    */
251   public String getPackageName() {
252     return _packageName;
253   }
254 
255   /**
256    * Prints the source code for this JClass in the current directory with the default line seperator
257    * of the the runtime platform.
258    *
259    * @see #print(String, String)
260    */
261   public void print() {
262     print(null, null);
263   }
264 
265   /**
266    * Prints the source code for this JClass with the default line seperator of the the runtime
267    * platform.
268    *
269    * @param destDir The destination directory to use as the root directory for source generation.
270    * 
271    * @see #print(String, String)
272    */
273   public void print(final String destDir) {
274     print(destDir, null);
275   }
276 
277   /**
278    * Prints the source code for this JCompUnit using the provided root directory and line separator.
279    *
280    * @param destDir The destination directory to use as the root directory for source generation.
281    * @param lineSeparator The line separator to use at the end of each line. If null, then the
282    *        default line separator for the runtime platform will be used.
283    */
284   public void print(final String destDir, final String lineSeparator) {
285     // -- open output file
286     String filename = getFilename(destDir);
287 
288     File file = new File(filename);
289     JSourceWriter jsw = null;
290     try {
291       jsw = new JSourceWriter(new FileWriter(file));
292     } catch (java.io.IOException ioe) {
293       System.out.println("unable to create compilation unit file: " + filename);
294       return;
295     }
296 
297     if (lineSeparator == null) {
298       jsw.setLineSeparator(System.getProperty("line.separator"));
299     } else {
300       jsw.setLineSeparator(lineSeparator);
301     }
302     print(jsw);
303     jsw.flush();
304     jsw.close();
305   }
306 
307   /**
308    * Prints the source code for this JClass to the provided JSourceWriter.
309    *
310    * @param jsw The JSourceWriter to print to.
311    */
312   public void print(final JSourceWriter jsw) {
313     // Traverse the nested class and interface hiararchy and
314     // update the names to match the compilation unit.
315 
316     // -- write file header
317     if (_header != null) {
318       _header.print(jsw);
319     } else {
320       jsw.writeln("/*");
321       jsw.writeln(" * " + DEFAULT_HEADER);
322       jsw.writeln("*/");
323     }
324     jsw.writeln();
325     jsw.flush();
326 
327     // -- print package name
328     if ((_packageName != null) && (_packageName.length() > 0)) {
329       String buffer = new StringBuilder(INITIAL_STRING_BUILDER_SIZE).append("package ")
330           .append(_packageName).append(';').toString();
331       jsw.writeln(buffer);
332       jsw.writeln();
333     }
334 
335     // -- print imports
336     jsw.writeln("  //---------------------------------------------/");
337     jsw.writeln(" //- Imported classes, interfaces and packages -/");
338     jsw.writeln("//---------------------------------------------/");
339     jsw.writeln();
340     SortedSet<String> allImports = getImports();
341     String compUnitPackage = getPackageName();
342     for (String importName : allImports) {
343       String importsPackage = JNaming.getPackageFromClassName(importName);
344       if (importsPackage != null && !importsPackage.equals(compUnitPackage)) {
345         jsw.write("import ");
346         jsw.write(importName);
347         jsw.writeln(';');
348       }
349     }
350     jsw.writeln();
351 
352     // Print the public elements, interfaces first, then classes.
353     // There should only be one public element, but if there are
354     // more we let the compiler catch it.
355     printStructures(jsw, true);
356 
357     // Print the remaining non-public elements, interfaces first.
358     printStructures(jsw, false);
359 
360     jsw.flush();
361   }
362 
363   /**
364    * Print the source code for the contained JClass objects.
365    *
366    * @param jsw The JSourceWriter to print to.
367    * @param printPublic If true, print only public classes; if false, print only non-public classes.
368    */
369   public void printStructures(final JSourceWriter jsw, final boolean printPublic) {
370     // -- print class information
371     // -- we need to add some JavaDoc API adding comments
372 
373     boolean isFirst = true;
374 
375     // SortedSet interfaceList = interfaces.sortedOnFullName();
376     for (JInterface jInterface : _interfaces) {
377       if (jInterface.getModifiers().isPublic() == printPublic) {
378         if (isFirst) {
379           String[] header = printPublic ? PUBLIC_HEADER : NON_PUBLIC_HEADER;
380           for (String headerEntry : header) {
381             jsw.writeln(headerEntry);
382           }
383           jsw.writeln();
384           isFirst = false;
385         }
386         jInterface.print(jsw, true);
387         jsw.writeln();
388       }
389     }
390 
391     // SortedSet classList = classes.sortedOnFullName();
392     for (JClass jClass : _classes) {
393       if (jClass.getModifiers().isPublic() == printPublic) {
394         if (isFirst) {
395           String[] header = printPublic ? PUBLIC_HEADER : NON_PUBLIC_HEADER;
396           for (int j = 0; j < header.length; ++j) {
397             jsw.writeln(header[j]);
398           }
399           jsw.writeln();
400           isFirst = false;
401         }
402         jClass.print(jsw, true);
403         jsw.writeln();
404       }
405     }
406   }
407 
408 }