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 1999-2002 (C) Intalio, Inc. All Rights Reserved.
42   *
43   * $Id$
44   *
45   * Contributors:
46   * --------------
47   * Keith Visco (kvisco@intalio.com) - Original Author
48   * Martin Skopp (skopp@riege.de)    - Moved some core code into JStructure
49   *                                    and revised to extend JStructure
50   *
51   */
52  package org.codehaus.modello.plugin.java.javasource;
53  
54  /*
55   * Copyright (c) 2004, Codehaus.org
56   *
57   * Permission is hereby granted, free of charge, to any person obtaining a copy of
58   * this software and associated documentation files (the "Software"), to deal in
59   * the Software without restriction, including without limitation the rights to
60   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
61   * of the Software, and to permit persons to whom the Software is furnished to do
62   * so, subject to the following conditions:
63   *
64   * The above copyright notice and this permission notice shall be included in all
65   * copies or substantial portions of the Software.
66   *
67   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
72   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
73   * SOFTWARE.
74   */
75  
76  import java.util.ArrayList;
77  import java.util.Enumeration;
78  import java.util.LinkedHashMap;
79  import java.util.List;
80  import java.util.Map;
81  
82  /**
83   * A representation of the Java Source code for a Java Class. This is
84   * a useful utility when creating in memory source code.
85   * This package was modelled after the Java Reflection API
86   * as much as possible to reduce the learning curve.
87   *
88   * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
89   * @author <a href="mailto:skopp@riege.de">Martin Skopp</a>
90   * @version $Revision$ $Date$
91   */
92  public class JClass extends JStructure {
93  
94      /**
95       * The list of constructors for this JClass
96       */
97      private List<JConstructor> _constructors = null;
98  
99      /**
100      * The list of member variables (fields) of this JClass
101      */
102     private Map<String, JField> _fields = null;
103 
104     private List<JClass> _innerClasses = null;
105 
106     /**
107      * The list of methods of this JClass
108      */
109     private List<JMethod> _methods = null;
110 
111     /**
112      * The superclass for this JClass
113      */
114     private String _superClass = null;
115 
116     /**
117      * The source code for static initialization
118      **/
119     private JSourceCode _staticInitializer = new JSourceCode();
120 
121     /**
122      * Creates a new JClass with the given name
123      * @param name the name of the JClass to create
124      * @exception java.lang.IllegalArgumentException when the given name
125      * is not a valid Class name
126      **/
127     public JClass(String name) throws IllegalArgumentException {
128         super(name);
129         _constructors = new ArrayList<JConstructor>();
130         _fields = new LinkedHashMap<>();
131         _methods = new ArrayList<JMethod>();
132         _innerClasses = new ArrayList<JClass>();
133         // -- initialize default Java doc
134         getJDocComment().appendComment("Class " + getLocalName() + ".");
135     } // -- JClass
136 
137     /**
138      * Adds the given Constructor to this classes list of constructors.
139      * The constructor must have been created with this JClass'
140      * createConstructor.
141      *
142      * @param constructor a constructor
143      * @throws java.lang.IllegalArgumentException if {@code constructor} is {@code null} or invalid.
144      */
145     public void addConstructor(JConstructor constructor) throws IllegalArgumentException {
146         if (constructor == null) throw new IllegalArgumentException("Constructors cannot be null");
147 
148         if (constructor.getDeclaringClass() == this) {
149 
150             /** check signatures (add later) **/
151             if (!_constructors.contains(constructor)) {
152                 _constructors.add(constructor);
153             }
154         } else {
155             String err = "The given JConstructor was not created ";
156             err += "by this JClass";
157             throw new IllegalArgumentException(err);
158         }
159     }
160 
161     /**
162      * Adds the given JField to this JClass
163      *
164      * @param jField, the JField to add
165      * @exception java.lang.IllegalArgumentException when the given
166      * JField has a name of an existing JField
167      **/
168     public void addField(JField jField) throws IllegalArgumentException {
169         if (jField == null) {
170             throw new IllegalArgumentException("Class members cannot be null");
171         }
172 
173         String name = jField.getName();
174 
175         if (_fields.get(name) != null) {
176             String err = "duplicate name found: " + name;
177             throw new IllegalArgumentException(err);
178         }
179         _fields.put(name, jField);
180     } // -- addField
181 
182     /**
183      * Adds the given JMember to this JClass
184      *
185      * @param jMember, the JMember to add
186      * @exception java.lang.IllegalArgumentException when the given
187      * JMember has the same name of an existing JField
188      * or JMethod respectively, or if the JMember is of an
189      * unrecognized class.
190      **/
191     public void addMember(JMember jMember) throws IllegalArgumentException {
192         if (jMember instanceof JField) addField((JField) jMember);
193         else if (jMember instanceof JMethod) addMethod((JMethod) jMember);
194         else {
195             String error = null;
196             if (jMember == null) {
197                 error = "the argument 'jMember' must not be null.";
198             } else {
199                 error = "Cannot add JMember '" + jMember.getClass().getName() + "' to JClass, unrecognized type.";
200             }
201             throw new IllegalArgumentException(error);
202         }
203     } // -- addMember
204 
205     /**
206      * Adds the given JMethod to this JClass
207      *
208      * @param jMethod the JMethod to add
209      **/
210     public void addMethod(JMethod jMethod) {
211         addMethod(jMethod, true);
212     }
213 
214     /**
215      * Adds the given JMethod to this JClass
216      *
217      * @param jMethod, the JMethod to add
218      * @param importReturnType true if we add the importReturnType to
219      * the class import lists. It could be useful to set it to false when
220      * all types are fully qualified.
221      * @throws java.lang.IllegalArgumentException when the given
222      * JMethod has the same name of an existing JMethod.
223      **/
224     public void addMethod(JMethod jMethod, boolean importReturnType) throws IllegalArgumentException {
225         if (jMethod == null) {
226             throw new IllegalArgumentException("Class methods cannot be null");
227         }
228 
229         // -- check method name and signatures *add later*
230 
231         // -- keep method list sorted for esthetics when printing
232         // -- START SORT :-)
233         boolean added = false;
234         //        short modifierVal = 0;
235         JModifiers modifiers = jMethod.getModifiers();
236 
237         if (modifiers.isAbstract()) {
238             getModifiers().setAbstract(true);
239         }
240 
241         for (int i = 0; i < _methods.size(); i++) {
242             JMethod tmp = (JMethod) _methods.get(i);
243             // -- first compare modifiers
244             if (tmp.getModifiers().isPrivate()) {
245                 if (!modifiers.isPrivate()) {
246                     _methods.add(i, jMethod);
247                     added = true;
248                     break;
249                 }
250             }
251             // -- compare names
252             if (jMethod.getName().compareTo(tmp.getName()) < 0) {
253                 _methods.add(i, jMethod);
254                 added = true;
255                 break;
256             }
257         }
258         // -- END SORT
259         if (!added) _methods.add(jMethod);
260     } // -- addMethod
261 
262     /**
263      * Adds the given array of JMethods to this JClass
264      *
265      * @param jMethods, the JMethod[] to add
266      * @exception java.lang.IllegalArgumentException when any of the given
267      * JMethods has the same name of an existing JMethod.
268      **/
269     public void addMethods(JMethod[] jMethods) throws IllegalArgumentException {
270         for (JMethod jMethod : jMethods) {
271             addMethod(jMethod);
272         }
273     } // -- addMethods
274 
275     /**
276      * Creates a new JConstructor and adds it to this
277      * JClass.
278      *
279      * @return the newly created constructor
280      */
281     public JConstructor createConstructor() {
282         return createConstructor(null);
283     } // -- createConstructor
284 
285     /**
286      * Creates a new JConstructor and adds it to this
287      * JClass.
288      *
289      * @param params the parameters
290      * @return the newly created constructor
291      */
292     public JConstructor createConstructor(JParameter[] params) {
293         JConstructor cons = new JConstructor(this);
294         if (params != null) {
295             for (JParameter param : params) {
296                 cons.addParameter(param);
297             }
298         }
299         addConstructor(cons);
300         return cons;
301     } // -- createConstructor
302 
303     /**
304      * Creates and returns an inner-class for this JClass
305      *
306      * @param localname the name of the class (no package name)
307      * @return the new JClass
308      */
309     public JClass createInnerClass(String localname) {
310         if (localname == null) {
311             String err = "argument 'localname' must not be null.";
312             throw new IllegalArgumentException(err);
313         }
314         if (localname.indexOf('.') >= 0) {
315             String err = "The name of an inner-class must not contain a package name.";
316             throw new IllegalArgumentException(err);
317         }
318         String classname = getPackageName();
319         if (classname != null) {
320             classname = classname + "." + localname;
321         } else {
322             classname = localname;
323         }
324 
325         JClass innerClass = new JInnerClass(classname);
326         _innerClasses.add(innerClass);
327         return innerClass;
328     } // -- createInnerClass
329 
330     /**
331      * Returns the constructor at the specified index.
332      *
333      * @param index the index of the constructor to return
334      * @return the JConstructor at the specified index.
335      */
336     public JConstructor getConstructor(int index) {
337         return (JConstructor) _constructors.get(index);
338     } // -- getConstructor
339 
340     /**
341      * Returns the an array of the JConstructors contained within this JClass
342      *
343      * @return an array of JConstructor
344      */
345     public JConstructor[] getConstructors() {
346 
347         int size = _constructors.size();
348         JConstructor[] jcArray = new JConstructor[size];
349 
350         for (int i = 0; i < _constructors.size(); i++) {
351             jcArray[i] = _constructors.get(i);
352         }
353         return jcArray;
354     } // -- getConstructors
355 
356     /**
357      * Returns the member with the given name, or null if no member
358      * was found with the given name
359      * @param name the name of the member to return
360      * @return the member with the given name, or null if no member
361      * was found with the given name
362      **/
363     public JField getField(String name) {
364         return (JField) _fields.get(name);
365     } // -- getField
366 
367     /**
368      * Returns an array of all the JFields of this JClass
369      * @return an array of all the JFields of this JClass
370      **/
371     public JField[] getFields() {
372         return _fields.values().toArray(new JField[0]);
373     } // -- getFields
374 
375     /**
376      * Returns an array of JClass (the inner classes)
377      * contained within this JClass.
378      *
379      * @return an array of JClass contained within this JClass
380      */
381     public JClass[] getInnerClasses() {
382         return _innerClasses.toArray(new JClass[0]);
383     } // -- getInnerClasses;
384 
385     /**
386      * Returns an array of all the JMethods of this JClass
387      *
388      * @return an array of all the JMethods of this JClass
389      */
390     public JMethod[] getMethods() {
391         int size = _methods.size();
392         JMethod[] marray = new JMethod[size];
393 
394         for (int i = 0; i < _methods.size(); i++) {
395             marray[i] = _methods.get(i);
396         }
397         return marray;
398     } // -- getMethods
399 
400     /**
401      * Returns the first occurrence of the method with the
402      * given name, starting from the specified index.
403      *
404      * @param name the name of the method to look for
405      * @param startIndex the starting index to begin the search
406      * @return the method if found, otherwise null.
407      */
408     public JMethod getMethod(String name, int startIndex) {
409         for (int i = startIndex; i < _methods.size(); i++) {
410             JMethod jMethod = _methods.get(i);
411             if (jMethod.getName().equals(name)) return jMethod;
412         }
413         return null;
414     } // -- getMethod
415 
416     /**
417      * Returns the JMethod located at the specified index
418      *
419      * @param index the index of the JMethod to return.
420      * @return the JMethod
421      */
422     public JMethod getMethod(int index) {
423         return _methods.get(index);
424     } // -- getMethod
425 
426     /**
427      * Returns the JSourceCode for the static initializer
428      * of this JClass
429      *
430      * @return the JSourceCode for the static initializer
431      * of this JClass
432      */
433     public JSourceCode getStaticInitializationCode() {
434         return _staticInitializer;
435     } // -- getStaticInitializationCode
436 
437     /**
438      * Gets the super Class that this class extends
439      * @return superClass the super Class that this Class extends
440      */
441     public String getSuperClass() {
442         return _superClass;
443     } // -- getSuperClass
444 
445     /**
446      * Prints the source code for this JClass to the given JSourceWriter
447      *
448      * @param jsw the JSourceWriter to print to. [May not be null]
449      */
450     public void print(JSourceWriter jsw) {
451         print(jsw, false);
452     } // -- print
453 
454     /**
455      * Prints the source code for this JClass to the given JSourceWriter
456      *
457      * @param jsw the JSourceWriter to print to. [May not be null]
458      * @param classOnly whether the header, package and imports should be printed too
459      */
460     public void print(JSourceWriter jsw, boolean classOnly) {
461 
462         if (jsw == null) {
463             throw new IllegalArgumentException("argument 'jsw' should not be null.");
464         }
465 
466         StringBuilder buffer = new StringBuilder();
467 
468         if (!classOnly) {
469             printHeader(jsw);
470             printPackageDeclaration(jsw);
471 
472             // -- get imports from inner-classes
473             List<String> removeImports = null;
474             if (_innerClasses.size() > 0) {
475                 removeImports = new ArrayList<String>();
476                 for (JClass iClass : _innerClasses) {
477                     Enumeration<String> e = iClass.getImports();
478                     while (e.hasMoreElements()) {
479                         String classname = e.nextElement();
480                         if (!hasImport(classname)) {
481                             addImport(classname);
482                             removeImports.add(classname);
483                         }
484                     }
485                 }
486             }
487             printImportDeclarations(jsw);
488 
489             // -- remove imports from inner-classes, if necessary
490             if (removeImports != null) {
491                 for (int i = 0; i < removeImports.size(); i++) {
492                     removeImport(removeImports.get(i));
493                 }
494             }
495         }
496 
497         // ------------/
498         // - Java Doc -/
499         // ------------/
500 
501         getJDocComment().print(jsw);
502 
503         JAnnotations annotations = getAnnotations();
504         if (annotations != null) annotations.print(jsw);
505 
506         // -- print class information
507         // -- we need to add some JavaDoc API adding comments
508 
509         buffer.setLength(0);
510 
511         JModifiers modifiers = getModifiers();
512         if (modifiers.isPrivate()) {
513             buffer.append("private ");
514         } else if (modifiers.isPublic()) {
515             buffer.append("public ");
516         }
517 
518         if (modifiers.isAbstract()) {
519             buffer.append("abstract ");
520         }
521 
522         if (this instanceof JInnerClass && modifiers.isStatic()) {
523             buffer.append("static ");
524         }
525 
526         if (modifiers.isFinal()) {
527             buffer.append("final ");
528         }
529 
530         buffer.append("class ");
531         buffer.append(getLocalName());
532         jsw.writeln(buffer.toString());
533         buffer.setLength(0);
534         jsw.indent();
535 
536         if (_superClass != null) {
537             buffer.append("extends ");
538             buffer.append(_superClass);
539             jsw.writeln(buffer.toString());
540             buffer.setLength(0);
541         }
542 
543         if (getInterfaceCount() > 0) {
544             buffer.append("implements ");
545 
546             Enumeration<String> e = getInterfaces();
547             while (e.hasMoreElements()) {
548                 buffer.append(e.nextElement());
549                 if (e.hasMoreElements()) buffer.append(", ");
550             }
551 
552             jsw.writeln(buffer.toString());
553             buffer.setLength(0);
554         }
555 
556         jsw.unindent();
557 
558         jsw.writeln('{');
559 
560         jsw.indent();
561 
562         // -- declare members
563 
564         if (_fields.size() > 0) {
565             jsw.writeln();
566             jsw.writeln("  //--------------------------/");
567             jsw.writeln(" //- Class/Member Variables -/");
568             jsw.writeln("//--------------------------/");
569             jsw.writeln();
570         }
571 
572         for (JField jField : _fields.values()) {
573             // -- print Java comment
574             JDocComment comment = jField.getComment();
575             if (comment != null) comment.print(jsw);
576 
577             JAnnotations fieldAnnotations = jField.getAnnotations();
578             if (fieldAnnotations != null) fieldAnnotations.print(jsw);
579 
580             // -- print member
581             jsw.write(jField.getModifiers().toString());
582             jsw.write(' ');
583 
584             JType type = jField.getType();
585             String typeName = type.toString();
586             // -- for esthetics use short name in some cases
587             if (typeName.equals(toString())) {
588                 typeName = type.getLocalName();
589             }
590             jsw.write(typeName);
591             jsw.write(' ');
592             jsw.write(jField.getName());
593 
594             String init = jField.getInitString();
595             if (init != null) {
596                 jsw.write(" = ");
597                 jsw.write(init);
598             }
599 
600             jsw.writeln(';');
601             jsw.writeln();
602         }
603 
604         // ----------------------/
605         // - Static Initializer -/
606         // ----------------------/
607 
608         if (!_staticInitializer.isEmpty()) {
609             jsw.writeln();
610             jsw.writeln("static");
611             jsw.writeln("{");
612             _staticInitializer.print(jsw);
613             jsw.writeln("};");
614             jsw.writeln();
615         }
616 
617         // -- print constructors
618         if (_constructors.size() > 0) {
619             jsw.writeln();
620             jsw.writeln("  //----------------/");
621             jsw.writeln(" //- Constructors -/");
622             jsw.writeln("//----------------/");
623             jsw.writeln();
624         }
625         for (int i = 0; i < _constructors.size(); i++) {
626             JConstructor jConstructor = _constructors.get(i);
627             jConstructor.print(jsw);
628             jsw.writeln();
629         }
630 
631         // -- print methods
632         if (_methods.size() > 0) {
633             jsw.writeln();
634             jsw.writeln("  //-----------/");
635             jsw.writeln(" //- Methods -/");
636             jsw.writeln("//-----------/");
637             jsw.writeln();
638         }
639 
640         for (int i = 0; i < _methods.size(); i++) {
641             JMethod jMethod = _methods.get(i);
642             jMethod.print(jsw);
643             jsw.writeln();
644         }
645 
646         // -- print inner-classes
647         if (_innerClasses.size() > 0) {
648             jsw.writeln();
649             jsw.writeln("  //-----------------/");
650             jsw.writeln(" //- Inner Classes -/");
651             jsw.writeln("//-----------------/");
652             jsw.writeln();
653         }
654         for (int i = 0; i < _innerClasses.size(); i++) {
655             JClass jClass = _innerClasses.get(i);
656             jClass.print(jsw, true);
657             jsw.writeln();
658         }
659 
660         for (String sourceCodeEntry : sourceCodeEntries) {
661             jsw.writeln(sourceCodeEntry);
662         }
663 
664         jsw.unindent();
665 
666         jsw.writeln('}');
667         jsw.flush();
668     } // -- printSource
669 
670     private List<String> sourceCodeEntries = new ArrayList<String>();
671 
672     public void addSourceCode(String sourceCode) {
673         sourceCodeEntries.add(sourceCode);
674     }
675 
676     /**
677      * Removes the given constructor from this JClass
678      *
679      * @param constructor the JConstructor to remove
680      * @return true if the constructor was removed, otherwise false.
681      */
682     public boolean removeConstructor(JConstructor constructor) {
683         return _constructors.remove(constructor);
684     } // -- removeConstructor
685 
686     /**
687      * Removes the field with the given name from this JClass
688      *
689      * @param name the name of the field to remove
690      * @return the removed field
691      **/
692     public JField removeField(String name) {
693         if (name == null) return null;
694 
695         JField field = (JField) _fields.remove(name);
696 
697         // -- clean up imports
698         // -- NOT YET IMPLEMENTED
699         return field;
700     } // -- removeField
701 
702     /**
703      * Removes the given JField from this JClass
704      *
705      * @param jField, the JField to remove
706      * @return {{@code true} if and only if the field was successfully removed
707      **/
708     public boolean removeField(JField jField) {
709         if (jField == null) return false;
710 
711         Object field = _fields.get(jField.getName());
712         if (field == jField) {
713             _fields.remove(jField.getName());
714             return true;
715         }
716         // -- clean up imports
717         // -- NOT YET IMPLEMENTED
718         return false;
719     } // -- removeField
720 
721     /**
722      * Removes the given inner-class (JClass) from this JClass.
723      *
724      * @param jClass the JClass (inner-class) to remove.
725      * @return true if the JClass was removed, otherwise false.
726      */
727     public boolean removeInnerClass(JClass jClass) {
728         return _innerClasses.remove(jClass);
729     } // -- removeInnerClass
730 
731     /**
732      * Sets the super Class that this class extends
733      * @param superClass the super Class that this Class extends
734      */
735     public void setSuperClass(String superClass) {
736         _superClass = superClass;
737     } // -- setSuperClass
738 
739     final class JInnerClass extends JClass {
740         JInnerClass(String name) {
741             super(name);
742         }
743     } // -- JInnerClass
744 } // -- JClass