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 }