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