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@codehaus.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.codehaus.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   * Contributors:
44   * --------------
45   * Gary Shea (shea@gtsdesign.com)   - Original Author
46   * Keith Visco (kvisco@intalio.com) - Changed JCompElement references to
47   *                                    JStructure, some additional tweaking
48   *                                    to get it working with the current
49   *                                    Javasource package.
50   *
51   * $Id$
52   */
53  package org.codehaus.modello.plugin.java.javasource;
54  
55  /*
56   * Copyright (c) 2004, Codehaus.org
57   *
58   * Permission is hereby granted, free of charge, to any person obtaining a copy of
59   * this software and associated documentation files (the "Software"), to deal in
60   * the Software without restriction, including without limitation the rights to
61   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
62   * of the Software, and to permit persons to whom the Software is furnished to do
63   * so, subject to the following conditions:
64   *
65   * The above copyright notice and this permission notice shall be included in all
66   * copies or substantial portions of the Software.
67   *
68   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
69   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
70   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
71   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
72   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
73   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
74   * SOFTWARE.
75   */
76  
77  import java.io.File;
78  import java.nio.file.Files;
79  import java.util.ArrayList;
80  import java.util.Enumeration;
81  import java.util.List;
82  import java.util.SortedSet;
83  import java.util.TreeSet;
84  
85  /**
86   * A representation of the Java Source code for a Java compilation
87   * unit. This is
88   * a useful utility when creating in memory source code.
89   * This package was modelled after the Java Reflection API
90   * as much as possible to reduce the learning curve.
91   * @author <a href="mailto:shea@gtsdesign.com">Gary Shea</a>
92   * @version $Revision$ $Date$
93   **/
94  public class JCompUnit {
95  
96      /**
97       * The Id for Source control systems
98       * I needed to separate this line to prevent CVS from
99       * expanding it here! ;-)
100      **/
101     private static final String DEFAULT_HEADER = "$" + "Id$";
102 
103     private JComment header = null;
104 
105     /**
106      * The package for this JCompUnit
107      **/
108     private String packageName = null;
109 
110     /**
111      * The file to which this JCompUnit will be saved
112      **/
113     private String fileName = null;
114 
115     /**
116      * The set of top-level classes that live in this compilation unit.
117      **/
118     // private TypeList classes = null;
119     private List<JClass> classes = null;
120 
121     /**
122      * The set of top-level interfaces that live in this compilation unit.
123      **/
124     // private TypeList interfaces = null;
125     private List<JInterface> interfaces = null;
126 
127     /**
128      * Creates a new JCompUnit
129      * @param packageName the name of the package for this JCompUnit.
130      * If packageName is null or empty, no 'package' line will be generated.
131      * @param fileName the name of the file in which this JCompUnit
132      * will be stored
133      **/
134     public JCompUnit(String packageName, String fileName) {
135         this.packageName = packageName;
136         this.fileName = fileName;
137         init();
138     } // -- JCompUnit
139 
140     /**
141      * Creates a new JCompUnit with the given JClass (which must have
142      * been created with either a full class name or package/local
143      * name) as the public class.  Package and file name are taken
144      * from jClass.
145      * @param jClass the public class for this JCompUnit.
146      **/
147     public JCompUnit(JClass jClass) {
148         this.packageName = jClass.getPackageName();
149 
150         // The outer name is the package plus the simple name of the
151         // outermost enclosing class.  The file name is just the
152         // simple name of the outermost enclosing class, so the
153         // package name part must be stripped off.
154 
155         /*
156           Commented out until inner-class support has been added.
157           kvisco - 20021211
158 
159         String outer = jClass.getOuterName();
160         int lastDot = outer.lastIndexOf(".");
161         String filePrefix;
162         if (lastDot != -1) {
163             filePrefix = outer.substring (lastDot + 1);
164         } else {
165             filePrefix = outer;
166         }
167         */
168         String filePrefix = jClass.getLocalName();
169 
170         this.fileName = filePrefix + ".java";
171         init();
172         classes.add(jClass);
173     } // -- JCompUnit
174 
175     /**
176      * Creates a new JCompUnit with the given JInterface as public interface
177      * Package and file name are taken from jInterface.
178      * @param jInterface the public interface for this JCompUnit.
179      **/
180     public JCompUnit(JInterface jInterface) {
181         this.packageName = jInterface.getPackageName();
182         this.fileName = jInterface.getLocalName() + ".java";
183         init();
184         interfaces.add(jInterface);
185     } // -- JCompUnit
186 
187     private void init() {
188         classes = new ArrayList<JClass>();
189         interfaces = new ArrayList<JInterface>();
190     }
191 
192     /**
193      * Adds the given JStructure (either a JInterface or
194      * a JClass) to this JCompUnit.
195      *
196      * @param jStructure the JStructure to add
197      * @exception java.lang.IllegalArgumentException when the given
198      * JStructure has the same name of an existing JStructure
199      * or if the class of jStructure is unknown.
200      */
201     public void addStructure(JStructure jStructure) throws IllegalArgumentException {
202         if (jStructure instanceof JInterface) addInterface((JInterface) jStructure);
203         else if (jStructure instanceof JClass) addClass((JClass) jStructure);
204         else {
205             String err = "Unknown JStructure subclass '" + jStructure.getClass().getName() + "'.";
206             throw new IllegalArgumentException(err);
207         }
208     } // -- addStructure
209 
210     /**
211      * Adds a JClass which should be printed in this file.
212      *
213      * @param jClass the JClass to add
214      **/
215     public void addClass(JClass jClass) {
216         classes.add(jClass);
217     } // -- addClass
218 
219     /**
220      * Adds a JInterface which should be printed in this file.
221      *
222      * @param jInterface the JInterface to add
223      **/
224     public void addInterface(JInterface jInterface) {
225         interfaces.add(jInterface);
226     } // -- addInterface
227 
228     /**
229      * returns a array of String containing all import classes/packages,
230      * also imports within the same package of this object.
231      * @return a array of String containing all import classes/packages,
232      * also imports within the same package of this object.
233      */
234     public SortedSet<String> getImports() {
235 
236         SortedSet<String> allImports = new TreeSet<String>();
237 
238         // add imports from classes
239         for (JClass jClass : classes) {
240             Enumeration<String> e = jClass.getImports();
241             while (e.hasMoreElements()) {
242                 allImports.add(e.nextElement());
243             }
244         }
245 
246         for (JInterface jInterface : interfaces) {
247             Enumeration<String> e = jInterface.getImports();
248             while (e.hasMoreElements()) {
249                 allImports.add(e.nextElement());
250             }
251         }
252 
253         return allImports;
254     }
255 
256     /**
257      * Returns the name of the file that this JCompUnit would be
258      * printed as, given a call to #print.
259      *
260      * @param destDir the destination directory. This may be null.
261      * @return the name of the file that this JCompUnit would be
262      * printed as, given a call to #print.
263      **/
264     public String getFilename(String destDir) {
265 
266         String filename = new String(fileName);
267 
268         // -- Convert Java package to path string
269         String javaPackagePath = "";
270         if ((packageName != null) && (packageName.length() > 0)) {
271             javaPackagePath = packageName.replace('.', File.separatorChar);
272         }
273 
274         // -- Create fully qualified path (including 'destDir') to file
275         File pathFile;
276         if (destDir == null) pathFile = new File(javaPackagePath);
277         else pathFile = new File(destDir, javaPackagePath);
278         if (!pathFile.exists()) {
279             pathFile.mkdirs();
280         }
281 
282         // -- Prefix filename with path
283         if (pathFile.toString().length() > 0) filename = pathFile.toString() + File.separator + filename;
284 
285         return filename;
286     } // -- getFilename
287 
288     /**
289      * Returns the name of the package that this JCompUnit is a member of
290      * @return the name of the package that this JCompUnit is a member of,
291      * or null if there is no current package name defined
292      **/
293     public String getPackageName() {
294         return this.packageName;
295     } // -- getPackageName
296 
297     protected static String getPackageFromClassName(String className) {
298         int idx = -1;
299         if ((idx = className.lastIndexOf('.')) > 0) {
300             return className.substring(0, idx);
301         }
302         return null;
303     } // -- getPackageFromClassName
304 
305     /**
306      * Prints the source code for this JClass in the current directory
307      * with the default line seperator of the the runtime platform.
308      * @see #print(java.lang.String, java.lang.String)
309      **/
310     public void print() {
311         print(null, null);
312     } // -- print
313 
314     /**
315      * Prints the source code for this JClass
316      * with the default line seperator of the the runtime platform.
317      * @param destDir the destination directory to generate the file.
318      * @see #print(java.lang.String, java.lang.String)
319      **/
320     public void print(String destDir) {
321         print(destDir, null);
322     } // -- print
323 
324     /**
325      * Prints the source code for this JCompUnit.
326      * @param destDir the destination directory to generate the file.
327      * @param lineSeparator the line separator to use at the end of each line.
328      * If null, then the default line separator for the runtime platform will
329      * be used.
330      **/
331     public void print(String destDir, String lineSeparator) {
332 
333         // -- open output file
334         String filename = getFilename(destDir);
335 
336         File file = new File(filename);
337         JSourceWriter jsw = null;
338         try {
339             jsw = new JSourceWriter(Files.newBufferedWriter(file.toPath()));
340         } catch (java.io.IOException ioe) {
341             System.out.println("unable to create compilation unit file: " + filename);
342             return;
343         }
344 
345         if (lineSeparator == null) {
346             lineSeparator = System.getProperty("line.separator");
347         }
348         jsw.setLineSeparator(lineSeparator);
349         print(jsw);
350         jsw.flush();
351         jsw.close();
352     } // -- print
353 
354     /**
355      * Prints the source code for this JClass.
356      * @param jsw the JSourceWriter to print to.
357      **/
358     public void print(JSourceWriter jsw) {
359 
360         // Traverse the nested class and interface heirarchy and
361         // update the names to match the compilation unit.
362 
363         resolveNames();
364         StringBuilder buffer = new StringBuilder();
365 
366         // -- write file header
367         if (header != null) header.print(jsw);
368         else {
369             jsw.writeln("/*");
370             jsw.writeln(" * " + DEFAULT_HEADER);
371             jsw.writeln("*/");
372         }
373         jsw.writeln();
374         jsw.flush();
375 
376         // -- print package name
377         if ((packageName != null) && (packageName.length() > 0)) {
378 
379             buffer.setLength(0);
380             buffer.append("package ");
381             buffer.append(packageName);
382             buffer.append(';');
383             jsw.writeln(buffer.toString());
384             jsw.writeln();
385         }
386 
387         // -- print imports
388         jsw.writeln("  //---------------------------------------------/");
389         jsw.writeln(" //- Imported classes, interfaces and packages -/");
390         jsw.writeln("//---------------------------------------------/");
391         jsw.writeln();
392         SortedSet<String> allImports = getImports();
393         String compUnitPackage = getPackageName();
394         for (String importName : allImports) {
395             String importsPackage = JStructure.getPackageFromClassName(importName);
396             if (importsPackage != null && !importsPackage.equals(compUnitPackage)) {
397                 jsw.write("import ");
398                 jsw.write(importName);
399                 jsw.writeln(';');
400             }
401         }
402         jsw.writeln();
403 
404         // Print the public elements, interfaces first, then classes.
405         // There should only be one public element, but if there are
406         // more we let the compiler catch it.
407         printStructures(jsw, true);
408 
409         // Print the remaining non-public elements, interfaces first.
410         printStructures(jsw, false);
411 
412         jsw.flush();
413     } // -- print
414 
415     /**
416      * Print the source code for the contained JClass objects.
417      * @param jsw the JSourceWriter to print to.
418      * @param printPublic if true, print only public classes; if
419      * false, print only non-public classes.
420      **/
421     public final void printStructures(JSourceWriter jsw, boolean printPublic) {
422 
423         // -- print class information
424         // -- we need to add some JavaDoc API adding comments
425 
426         boolean isFirst = true;
427 
428         // SortedSet interfaceList = interfaces.sortedOnFullName();
429         for (JInterface jInterface : interfaces) {
430             if (jInterface.getModifiers().isPublic() == printPublic) {
431                 if (isFirst) {
432                     Header.print(jsw, printPublic);
433                     isFirst = false;
434                 }
435                 jInterface.print(jsw, true);
436                 jsw.writeln();
437             }
438         }
439 
440         // SortedSet classList = classes.sortedOnFullName();
441         for (JClass jClass : classes) {
442             if (jClass.getModifiers().isPublic() == printPublic) {
443                 if (isFirst) {
444                     Header.print(jsw, printPublic);
445                     isFirst = false;
446                 }
447                 jClass.print(jsw, true);
448                 jsw.writeln();
449             }
450         }
451     } // -- printElements(JSourceWriter, int)
452 
453     /**
454      * Sets the header comment for this JCompUnit
455      * @param comment the comment to display at the top of the source file
456      * when printed
457      **/
458     public void setHeader(JComment comment) {
459         this.header = comment;
460     } // -- setHeader
461 
462     // -------------------/
463     // - Private Methods -/
464     // -------------------/
465 
466     /**
467      * Update the names of nested classes and interfaces.
468      **/
469     private void resolveNames() {
470 
471         /*
472           Commented out until support for inner-classes is added
473           kvisco - 20021211
474 
475         for (int i = 0; i < classes.size(); i++) {
476             JClass jClass = (JClass) classes.get(i);
477             jClass.resolveNames(packageName, null);
478         }
479         for (int i = 0; i < interfaces.size(); i++) {
480             JInterface jInterface = (JInterface) interfaces.get(i);
481             jInterface.resolveNames(packageName, null);
482         }
483         */
484     } // -- resolveNames
485 } // -- JCompUnit
486 
487 /**
488  * Print the headers delineating the public and non-public elements of
489  * the compilation unit.
490  **/
491 class Header {
492 
493     private static String[] publicHeader = {
494         "  //-----------------------------/", " //-  Public Class / Interface -/", "//-----------------------------/",
495     };
496     private static String[] nonPublicHeader = {
497         "  //-------------------------------------/",
498         " //-  Non-Public Classes / Interfaces  -/",
499         "//-------------------------------------/",
500     };
501 
502     /**
503      * Print the specified header to the given Writer.
504      * @params JSourceWriter an open JSourceWriter
505      * @params boolean if true print the public header, otherwise
506      * print the non-public header.
507      **/
508     protected static void print(JSourceWriter jsw, boolean printPublic) {
509         String[] header = printPublic ? publicHeader : nonPublicHeader;
510         for (String aHeader : header) {
511             jsw.writeln(aHeader);
512         }
513         jsw.writeln();
514     }
515 } // -- Header