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.io.IOException;
48 import java.util.Enumeration;
49 import java.util.Vector;
50
51 /**
52 * This class represents the basic Java "structure" for a Java source file. This
53 * is the base class for JClass and JInterface. <br/> This is a useful utility
54 * when creating in memory source code. The code in this package was modelled
55 * after the Java Reflection API as much as possible to reduce the learning
56 * curve.
57 *
58 * @author <a href="mailto:skopp AT riege DOT de">Martin Skopp</a>
59 * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
60 * @version $Revision$ $Date: 2005-12-13 14:58:48 -0700 (Tue, 13 Dec
61 * 2005) $
62 */
63 public abstract class JStructure extends JType implements JAnnotatedElement {
64
65 /**
66 * The Id for Source control systems. <br/> Note: I needed to break the
67 * String into parts to prevent CVS from expanding it here!
68 */
69 private static final String DEFAULT_HEADER = "$" + "Id$";
70
71 /**
72 * The source control version for listed in the JavaDoc <br/> Note: I needed
73 * to break the String into parts to prevent CVS from expanding it here!
74 */
75 private static final String DEFAULT_VERSION = "$" + "Revision$ $" + "Date$";
76
77 /** A standard complaint for a bad parameter. */
78 private static final String JSW_SHOULD_NOT_BE_NULL = "argument 'jsw' should not be null.";
79
80 /** The source header. */
81 private JComment _header;
82
83 /** The package to which this JStructure belongs. */
84 private String _packageName;
85
86 /** List of imported classes and packages. */
87 private Vector<String> _imports;
88
89 /** The Javadoc for this JStructure. */
90 private JDocComment _jdc;
91
92 /** Implementation of JAnnoatedElement to delagate to. */
93 private JAnnotatedElementHelper _annotatedElement;
94
95 /**
96 * The JModifiers for this JStructure, which allows us to change the
97 * resulting qualifiers.
98 */
99 private JModifiers _modifiers;
100
101 /** The set of interfaces implemented/extended by this JStructure. */
102 private Vector<String> _interfaces;
103
104 /**
105 * Creates a new JStructure with the given name.
106 *
107 * @param name
108 * The name of the JStructure.
109 */
110 protected JStructure(final String name) {
111 super(name);
112
113 // -- verify name is a valid java class name
114 if (!isValidClassName(name)) {
115 String lname = getLocalName();
116 String err = "'" + lname + "' is ";
117 if (JNaming.isKeyword(lname)) {
118 err += "a reserved word and may not be used as a class name.";
119 } else {
120 err += "not a valid Java identifier.";
121 }
122 throw new IllegalArgumentException(err);
123 }
124
125 _header = null;
126 _packageName = JNaming.getPackageFromClassName(name);
127 _imports = new Vector<String>();
128 _jdc = new JDocComment(JDocDescriptor.createVersionDesc(DEFAULT_VERSION));
129 _annotatedElement = new JAnnotatedElementHelper();
130 _modifiers = new JModifiers();
131 _interfaces = new Vector<String>();
132 }
133
134 /**
135 * Test the provided name and return true if it is a valid class name.
136 *
137 * @param classname
138 * A class name to test.
139 * @return True if the provided class name is a valid class name.
140 */
141 private boolean isValidClassName(final String classname) {
142 if (classname == null) {
143 return false;
144 }
145
146 String name = classname;
147 int beforeTypeName = name.indexOf("<");
148 if (beforeTypeName > 0) {
149 name = name.substring(0, beforeTypeName);
150 }
151
152 // -- ignore package information, for now
153 name = JNaming.getLocalNameFromClassName(name);
154
155 return JNaming.isValidJavaIdentifier(name);
156 }
157
158 /**
159 * Returns the JComment header to display at the top of the source file for
160 * this JStructure, or null if no header was set.
161 *
162 * @return The JComment header or null if none exists.
163 */
164 public final JComment getHeader() {
165 return _header;
166 }
167
168 /**
169 * Sets the header comment for this JStructure.
170 *
171 * @param comment
172 * The comment to display at the top of the source file when
173 * printed.
174 */
175 public final void setHeader(final JComment comment) {
176 _header = comment;
177 }
178
179 /**
180 * Returns the name of the package that this JStructure is a member of.
181 *
182 * @return The name of the package that this JStructure is a member of, or
183 * null if there is no current package name defined.
184 */
185 public final String getPackageName() {
186 return _packageName;
187 }
188
189 /**
190 * Returns an Enumeration of imported package and class names for this
191 * JStructure.
192 *
193 * @return The Enumeration of imports. May be empty but will not be null.
194 */
195 public final Enumeration<String> getImports() {
196 return _imports.elements();
197 }
198
199 /**
200 * Returns the amount of imports.
201 *
202 * @return The amount of imports.
203 */
204 public final int getImportCount() {
205 return _imports.size();
206 }
207
208 /**
209 * Returns true if the given classname exists in the imports of this
210 * JStructure.
211 *
212 * @param classname
213 * The class name to check for
214 * @return True if the given classname exists in the imports list.
215 */
216 public final boolean hasImport(final String classname) {
217 return _imports.contains(classname);
218 }
219
220 /**
221 * Adds the given import to this JStructure. Note: You cannot import from
222 * the "default package," so imports with no package are ignored.
223 *
224 * @param className
225 * Name of the class to import.
226 */
227 public abstract void addImport(final String className);
228
229 /**
230 * Adds the given import to this JStructure. Given class name should not be
231 * null or empty. <br/> Note: You cannot import from the "default package,"
232 * so imports with no package are ignored.
233 *
234 * @param className
235 * Name of the class to import.
236 */
237 protected final void addImportInternal(final String className) {
238 // -- getPackageName
239 String pkgName = JNaming.getPackageFromClassName(className);
240
241 if (pkgName != null) {
242 if (pkgName.equals(_packageName) || pkgName.equals("java.lang")) {
243 return;
244 }
245
246 // -- for readabilty keep import list sorted, and make sure
247 // -- we do not include more than one of the same import
248 for (int i = 0; i < _imports.size(); i++) {
249 String imp = _imports.elementAt(i);
250 if (imp.equals(className)) {
251 return;
252 }
253 if (imp.compareTo(className) > 0) {
254 _imports.insertElementAt(className, i);
255 return;
256 }
257 }
258 _imports.addElement(className);
259 }
260 }
261
262 /**
263 * Adds appropriate import for this JAnnotation.
264 *
265 * @param annotation
266 * A JAnnotation for which we want to add an import to this
267 * JStructure.
268 */
269 protected final void addImport(final JAnnotation annotation) {
270 addImport(annotation.getAnnotationType().getName());
271 }
272
273 /**
274 * Adds appropriate imports for each JAnnotation in the given Array.
275 *
276 * @param annotations
277 * An Array of JAnnotation; we want to add an import to this
278 * JStructure for each JAnnotation in the Array.
279 */
280 protected final void addImport(final JAnnotation[] annotations) {
281 for (int i = 0; i < annotations.length; i++) {
282 addImport(annotations[i].getAnnotationType().getName());
283 }
284 }
285
286 /**
287 * Remove the import of the given class name from this JStucture, returning
288 * true if the import was found and removed.
289 *
290 * @param className
291 * Name of the class to remove the import of.
292 * @return If the import was previously part of this JStructure, false
293 * otherwise.
294 */
295 public final boolean removeImport(final String className) {
296 boolean result = false;
297 if (className == null) {
298 return result;
299 }
300 if (className.length() == 0) {
301 return result;
302 }
303
304 result = _imports.removeElement(className);
305 return result;
306 }
307
308 /**
309 * Returns the JavaDoc comment for this JStructure.
310 *
311 * @return The JDocComment for this JStructure.
312 */
313 public final JDocComment getJDocComment() {
314 return _jdc;
315 }
316
317 /**
318 * Returns the object managing the annotations for this JStructure.
319 *
320 * @return The object managing the annotations for this JStructure.
321 */
322 protected final JAnnotatedElementHelper getAnnotatedElementHelper() {
323 return _annotatedElement;
324 }
325
326 /**
327 * {@inheritDoc}
328 */
329 public final boolean hasAnnotations() {
330 return _annotatedElement.hasAnnotations();
331 }
332
333 /**
334 * {@inheritDoc}
335 */
336 public final JAnnotation[] getAnnotations() {
337 return _annotatedElement.getAnnotations();
338 }
339
340 /**
341 * {@inheritDoc}
342 */
343 public final JAnnotation getAnnotation(final JAnnotationType annotationType) {
344 return _annotatedElement.getAnnotation(annotationType);
345 }
346
347 /**
348 * {@inheritDoc}
349 */
350 public final boolean isAnnotationPresent(
351 final JAnnotationType annotationType) {
352 return _annotatedElement.isAnnotationPresent(annotationType);
353 }
354
355 /**
356 * {@inheritDoc}
357 */
358 public final void addAnnotation(final JAnnotation annotation) {
359 _annotatedElement.addAnnotation(annotation);
360 addImport(annotation);
361 }
362
363 /**
364 * {@inheritDoc}
365 */
366 public final JAnnotation removeAnnotation(
367 final JAnnotationType annotationType) {
368 return _annotatedElement.removeAnnotation(annotationType);
369 }
370
371 /**
372 * Returns the JModifiers, which allows the qualifiers to be changed.
373 *
374 * @return The JModifiers for this JStructure.
375 */
376 public final JModifiers getModifiers() {
377 return _modifiers;
378 }
379
380 /**
381 * Returns an Enumeration of interface names that this JStructure inherits
382 * from.
383 *
384 * @return The Enumeration of interface names for this JStructure. May be
385 * empty but will not be null.
386 */
387 public final Enumeration<String> getInterfaces() {
388 return _interfaces.elements();
389 }
390
391 /**
392 * Return the count of the number of Interfaces that have been added to this
393 * JStructure.
394 *
395 * @return The count of the number of Interfaces that have been added to
396 * this JStructure.
397 */
398 public final int getInterfaceCount() {
399 return _interfaces.size();
400 }
401
402 /**
403 * Adds the given interface to the list of interfaces this JStructure
404 * inherits method declarations from, and either implements (JClass) or
405 * extends (JInterface).
406 *
407 * @param interfaceName
408 * The name of the interface to "inherit" method declarations
409 * from.
410 */
411 public final void addInterface(final String interfaceName) {
412 if (!_interfaces.contains(interfaceName)) {
413 _interfaces.addElement(interfaceName);
414 }
415 }
416
417 /**
418 * Removes the given interface from the list of interfaces this
419 * {@link JStructure} has.
420 *
421 * @param interfaceName
422 * The name of the interface to be removed.
423 * @return true if {@link JStructure} implemented the interface and it was
424 * removed, false otherwise.
425 */
426 public final boolean removeInterface(final String interfaceName) {
427 return _interfaces.remove(interfaceName);
428 }
429
430 /**
431 * Returns the field with the given name, or null if no field was found with
432 * that name.
433 *
434 * @param name
435 * The name of the field to return.
436 * @return The field with the given name, or null if no field was found with
437 * the given name.
438 */
439 public abstract JField getField(String name);
440
441 /**
442 * Returns an array of all the JFields of this JStructure.
443 *
444 * @return An array of all the JFields of this JStructure.
445 */
446 public abstract JField[] getFields();
447
448 /**
449 * Adds the given JField to this JStructure. <br/> This method is
450 * implemented by subclasses and should only accept the proper fields for
451 * the subclass otherwise an IllegalArgumentException will be thrown. For
452 * example a JInterface will only accept static fields.
453 *
454 * @param jField
455 * The JField to add.
456 */
457 public abstract void addField(JField jField);
458
459 /**
460 * Adds the given JMember to this JStructure. <br/> This method is
461 * implemented by subclasses and should only accept the proper types for the
462 * subclass otherwise an IllegalArgumentException will be thrown.
463 *
464 * @param jMember
465 * The JMember to add to this JStructure.
466 */
467 public abstract void addMember(JMember jMember);
468
469 /**
470 * Returns the name of the file that this JStructure would be printed to,
471 * given a call to {@link #print(String, String)}.
472 *
473 * @param destDir
474 * the destination directory. This may be null.
475 * @return the name of the file that this JInterface would be printed to
476 */
477 public final String getFilename(final String destDir) {
478 String filename = getLocalName() + ".java";
479
480 // -- Convert Java package to path string
481 String javaPackagePath = "";
482 if ((_packageName != null) && (_packageName.length() > 0)) {
483 javaPackagePath = _packageName.replace('.', File.separatorChar);
484 }
485
486 // -- Create fully qualified path (including 'destDir') to file
487 File pathFile;
488 if (destDir == null) {
489 pathFile = new File(javaPackagePath);
490 } else {
491 pathFile = new File(destDir, javaPackagePath);
492 }
493 if (!pathFile.exists()) {
494 pathFile.mkdirs();
495 }
496
497 // -- Prefix filename with path
498 if (pathFile.toString().length() > 0) {
499 filename = pathFile.toString() + File.separator + filename;
500 }
501
502 return filename;
503 }
504
505 /**
506 * Prints the source code for this JStructure to the destination directory.
507 * Subdirectories will be created if necessary for the package.
508 *
509 * @param destDir
510 * Directory name to use as the root directory for all output.
511 * @param lineSeparator
512 * The line separator to use at the end of each line. If null,
513 * then the default line separator for the runtime platform will
514 * be used.
515 */
516 public final void print(final String destDir, final String lineSeparator) {
517 // -- open output file
518 String filename = getFilename(destDir);
519
520 File file = new File(filename);
521 JSourceWriter jsw = null;
522 try {
523 jsw = new JSourceWriter(new FileWriter(file));
524 } catch (IOException ioe) {
525 System.out.println("unable to create class file: " + filename);
526 return;
527 }
528 if (lineSeparator == null) {
529 jsw.setLineSeparator(System.getProperty("line.separator"));
530 } else {
531 jsw.setLineSeparator(lineSeparator);
532 }
533 print(jsw);
534 jsw.close();
535 }
536
537 /**
538 * Prints the source code for this JStructure to the given JSourceWriter.
539 *
540 * @param jsw
541 * The JSourceWriter to print to.
542 * @deprecated Please use the Velocity-template based approach instead.
543 */
544 public abstract void print(JSourceWriter jsw);
545
546 /**
547 * A utility method that prints the header to the given JSourceWriter.
548 *
549 * @param jsw
550 * The JSourceWriter to print to.
551 */
552 public final void printHeader(final JSourceWriter jsw) {
553 if (jsw == null) {
554 throw new IllegalArgumentException(JSW_SHOULD_NOT_BE_NULL);
555 }
556
557 // -- write class header
558 if (_header != null) {
559 _header.print(jsw);
560 } else {
561 jsw.writeln("/*");
562 jsw.writeln(" * " + DEFAULT_HEADER);
563 jsw.writeln(" */");
564 }
565 jsw.writeln();
566 jsw.flush();
567 }
568
569 /**
570 * A utility method that prints the packageDeclaration to the given
571 * JSourceWriter.
572 *
573 * @param jsw
574 * The JSourceWriter to print to.
575 */
576 public final void printPackageDeclaration(final JSourceWriter jsw) {
577 if (jsw == null) {
578 throw new IllegalArgumentException(JSW_SHOULD_NOT_BE_NULL);
579 }
580
581 // -- print package name
582 if ((_packageName != null) && (_packageName.length() > 0)) {
583 jsw.write("package ");
584 jsw.write(_packageName);
585 jsw.writeln(';');
586 jsw.writeln();
587 }
588 jsw.flush();
589 }
590
591 /**
592 * A utility method that prints the imports to the given JSourceWriter.
593 *
594 * @param jsw
595 * The JSourceWriter to print to.
596 */
597 protected final void printImportDeclarations(final JSourceWriter jsw) {
598 if (jsw == null) {
599 throw new IllegalArgumentException(JSW_SHOULD_NOT_BE_NULL);
600 }
601
602 // -- print imports
603 if (_imports.size() > 0) {
604 jsw.writeln(" //---------------------------------/");
605 jsw.writeln(" //- Imported classes and packages -/");
606 jsw.writeln("//---------------------------------/");
607 jsw.writeln();
608 Enumeration<String> enumeration = _imports.elements();
609 while (enumeration.hasMoreElements()) {
610 jsw.write("import ");
611 jsw.write(enumeration.nextElement());
612 jsw.writeln(';');
613 }
614 jsw.writeln();
615 jsw.flush();
616 }
617 }
618
619 /**
620 * {@inheritDoc} <br/> Returns the String representation of this JType.
621 */
622 public final String toString() {
623 return getName();
624 }
625
626 //--------------------------------------------------------------------------
627 }