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 }