View Javadoc
1   /*
2    * Copyright 2006 Ralf Joachim
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    * 
7    * http://www.apache.org/licenses/LICENSE-2.0
8    * 
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  package org.exolab.javasource;
15  
16  import java.util.ArrayList;
17  import java.util.Enumeration;
18  import java.util.List;
19  import java.util.LinkedHashMap;
20  import java.util.Map;
21  import java.util.Vector;
22  
23  import org.exolab.castor.builder.SourceGenerator;
24  
25  /**
26   * A abstract base class for representations of the Java Source code for a Java Class.
27   *
28   * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a>
29   * @version $Revision: 6668 $ $Date: 2005-05-08 12:32:06 -0600 (Sun, 08 May 2005) $
30   * @since 1.1
31   */
32  public abstract class AbstractJClass extends JStructure {
33  
34    /** The source code for static initialization. */
35    private final JSourceCode _staticInitializer = new JSourceCode();
36  
37    /** The list of member variables (fields) of this JClass. */
38    private final Map<String, JField> _fields = new LinkedHashMap<String, JField>();
39  
40    /** The list of member constants of this {@link JClass}. */
41    private final Map<String, JConstant> _constants = new LinkedHashMap<String, JConstant>();
42  
43    /** The list of constructors for this JClass. */
44    private final Vector<JConstructor> _constructors = new Vector<JConstructor>();
45  
46    /** The list of methods of this JClass. */
47    private final Vector<JMethod> _methods = new Vector<JMethod>();
48  
49    /** A collection of inner classes for this JClass. */
50    private Vector<JClass> _innerClasses;
51  
52    private final Vector<String> _sourceCodeEntries = new Vector<String>();
53  
54    /**
55     * Returns a collection of (complete) source code fragments.
56     * 
57     * @return A collection of (complete) source code fragments.
58     */
59    public String[] getSourceCodeEntries() {
60      return _sourceCodeEntries.toArray(new String[_sourceCodeEntries.size()]);
61    }
62  
63    /**
64     * Creates a new AbstractJClass with the given name.
65     *
66     * @param name The name of the AbstractJClass to create.
67     */
68    protected AbstractJClass(final String name) {
69      this(name, false);
70    }
71  
72    /**
73     * Creates a new AbstractJClass with the given name.
74     *
75     * @param name The name of the AbstractJClass to create.
76     */
77    protected AbstractJClass(final String name, boolean useOldFieldNaming) {
78      super(name);
79      _innerClasses = null;
80  
81      if (useOldFieldNaming) {
82        // -- initialize default Java doc
83        getJDocComment().appendComment("Class " + getLocalName() + ".");
84      }
85    }
86  
87    // --------------------------------------------------------------------------
88  
89    /**
90     * Returns the JSourceCode for the static initializer of this JClass.
91     *
92     * @return The JSourceCode for the static initializer of this JClass.
93     */
94    public final JSourceCode getStaticInitializationCode() {
95      return _staticInitializer;
96    }
97  
98    /**
99     * {@inheritDoc}
100    */
101   public final JField getField(final String name) {
102     return _fields.get(name);
103   }
104 
105   /**
106    * {@inheritDoc}
107    */
108   public final JConstant getConstant(final String name) {
109     return _constants.get(name);
110   }
111 
112   /**
113    * {@inheritDoc}
114    */
115   public final JField[] getFields() {
116     return _fields.values().toArray(new JField[_fields.size()]);
117   }
118 
119   /**
120    * {@inheritDoc}
121    */
122   public final JConstant[] getConstants() {
123     return _constants.values().toArray(new JConstant[_constants.size()]);
124   }
125 
126   /**
127    * Returns the amount of fields.
128    * 
129    * @return The amount of fields.
130    */
131   public final int getFieldCount() {
132     return _fields.size();
133   }
134 
135   /**
136    * Returns the amount of constants.
137    * 
138    * @return The amount of constants.
139    */
140   public final int getConstantCount() {
141     return _constants.size();
142   }
143 
144   /**
145    * {@inheritDoc}
146    */
147   public final void addField(final JField jField) {
148     if (jField == null) {
149       throw new IllegalArgumentException("Class members cannot be null");
150     }
151 
152     String name = jField.getName();
153 
154     if (_fields.get(name) != null) {
155       String nameToCompare = (name.startsWith("_")) ? name.substring(1) : name;
156       nameToCompare = nameToCompare.substring(0, 1).toUpperCase() + nameToCompare.substring(1);
157       if (JNaming.isReservedByCastor(nameToCompare)) {
158         String warn = "'" + nameToCompare + "' might conflict with a field name used"
159             + " by Castor.  If you get a complaint\nabout a duplicate name, you will"
160             + " need to use a mapping file or change the name\nof the conflicting"
161             + " schema element.";
162         System.out.println(warn);
163       }
164 
165       String err = "Duplicate name found as a class member: " + name;
166       throw new IllegalArgumentException(err);
167     }
168     _fields.put(name, jField);
169   }
170 
171   public final void addConstant(final JConstant jConstant) {
172     if (jConstant == null) {
173       throw new IllegalArgumentException("Class constants cannot be null");
174     }
175 
176     String name = jConstant.getName();
177 
178     if (_constants.get(name) != null) {
179       String nameToCompare = (name.startsWith("_")) ? name.substring(1) : name;
180       nameToCompare = nameToCompare.substring(0, 1).toUpperCase() + nameToCompare.substring(1);
181       if (JNaming.isReservedByCastor(nameToCompare)) {
182         String warn = "'" + nameToCompare + "' might conflict with a constant name used"
183             + " by Castor.  If you get a complaint\nabout a duplicate name, you will"
184             + " need to use a mapping file or change the name\nof the conflicting"
185             + " schema element.";
186         System.out.println(warn);
187       }
188 
189       String err = "Duplicate name found as a class member: " + name;
190       throw new IllegalArgumentException(err);
191     }
192     _constants.put(name, jConstant);
193   }
194 
195   /**
196    * Removes the field with the given name from this JClass.
197    *
198    * @param name The name of the field to remove.
199    * @return The JField if it was found and removed.
200    */
201   public final JField removeField(final String name) {
202     if (name == null) {
203       return null;
204     }
205 
206     JField field = _fields.remove(name);
207 
208     // -- clean up imports
209     // -- NOT YET IMPLEMENTED
210     return field;
211   }
212 
213   /**
214    * Removes the constant with the given name from this {@link JClass}.
215    *
216    * @param name The name of the constant to remove.
217    * @return The JConstant if it was found and removed.
218    */
219   public final JConstant removeConstant(final String name) {
220     if (name == null) {
221       return null;
222     }
223 
224     JConstant constant = _constants.remove(name);
225 
226     // -- clean up imports
227     // -- NOT YET IMPLEMENTED
228     return constant;
229   }
230 
231   /**
232    * Removes the given JField from this JClass.
233    *
234    * @param jField The JField to remove.
235    * @return true if the field was found and removed.
236    */
237   public final boolean removeField(final JField jField) {
238     if (jField == null) {
239       return false;
240     }
241 
242     Object field = _fields.get(jField.getName());
243     if (field == jField) {
244       _fields.remove(jField.getName());
245       return true;
246     }
247     // -- clean up imports
248     // -- NOT YET IMPLEMENTED
249     return false;
250   }
251 
252   /**
253    * Removes the given {@link JConstant} from this {@link JClass}.
254    *
255    * @param jConstant The {@link JConstant} to remove.
256    * @return true if the constant was found and removed.
257    */
258   public final boolean removeConstant(final JConstant jConstant) {
259     if (jConstant == null) {
260       return false;
261     }
262 
263     Object constant = _constants.get(jConstant.getName());
264     if (constant == jConstant) {
265       _constants.remove(jConstant.getName());
266       return true;
267     }
268     // -- clean up imports
269     // -- NOT YET IMPLEMENTED
270     return false;
271   }
272 
273   /**
274    * Creates a new JConstructor and adds it to this JClass.
275    *
276    * @return The newly created constructor.
277    */
278   public final JConstructor createConstructor() {
279     return createConstructor(null);
280   }
281 
282   /**
283    * Creates a new JConstructor and adds it to this JClass.
284    *
285    * @param params A list of parameters for this constructor.
286    * @return The newly created constructor.
287    */
288   public final JConstructor createConstructor(final JParameter[] params) {
289     JConstructor cons = new JConstructor(this);
290     if (params != null) {
291       for (JParameter param : params) {
292         cons.addParameter(param);
293       }
294     }
295     addConstructor(cons);
296     return cons;
297   }
298 
299   /**
300    * Returns the constructor at the specified index.
301    *
302    * @param index The index of the constructor to return.
303    * @return The JConstructor at the specified index.
304    */
305   public final JConstructor getConstructor(final int index) {
306     return _constructors.elementAt(index);
307   }
308 
309   /**
310    * Returns the an array of the JConstructors contained within this JClass.
311    *
312    * @return An array of JConstructor.
313    */
314   public final JConstructor[] getConstructors() {
315     int size = _constructors.size();
316     JConstructor[] jcArray = new JConstructor[size];
317 
318     for (int i = 0; i < _constructors.size(); i++) {
319       jcArray[i] = _constructors.elementAt(i);
320     }
321     return jcArray;
322   }
323 
324   public final int getContructorsCount() {
325     return _constructors.size();
326   }
327 
328   /**
329    * Adds the given Constructor to this classes list of constructors. The constructor must have been
330    * created with this JClass' createConstructor.
331    *
332    * @param constructor The constructor to add.
333    */
334   public void addConstructor(final JConstructor constructor) {
335     if (constructor == null) {
336       throw new IllegalArgumentException("Constructors cannot be null");
337     }
338 
339     if (constructor.getDeclaringClass() == this) {
340 
341       /** check signatures (add later) **/
342       if (!_constructors.contains(constructor)) {
343         _constructors.add(constructor);
344       }
345     } else {
346       String err = "The given JConstructor was not created by this JClass";
347       throw new IllegalArgumentException(err);
348     }
349   }
350 
351   /**
352    * Removes the given constructor from this JClass.
353    *
354    * @param constructor The JConstructor to remove.
355    * @return true if the constructor was removed, otherwise false.
356    */
357   public final boolean removeConstructor(final JConstructor constructor) {
358     return _constructors.remove(constructor);
359   }
360 
361   /**
362    * Returns an array of all the JMethods of this JClass.
363    *
364    * @return An array of all the JMethods of this JClass.
365    */
366   public final JMethod[] getMethods() {
367     return _methods.toArray(new JMethod[_methods.size()]);
368   }
369 
370   /**
371    * Returns the first occurance of the method with the given name, starting from the specified
372    * index.
373    *
374    * @param name The name of the method to look for.
375    * @param startIndex The starting index to begin the search.
376    * @return The method if found, otherwise null.
377    */
378   public final JMethod getMethod(final String name, final int startIndex) {
379     for (int i = startIndex; i < _methods.size(); i++) {
380       JMethod jMethod = _methods.elementAt(i);
381       if (jMethod.getName().equals(name)) {
382         return jMethod;
383       }
384     }
385     return null;
386   }
387 
388   /**
389    * Returns the JMethod located at the specified index.
390    *
391    * @param index The index of the JMethod to return.
392    * @return The JMethod.
393    */
394   public final JMethod getMethod(final int index) {
395     return _methods.elementAt(index);
396   }
397 
398   public final int getMethodCount() {
399     return _methods.size();
400   }
401 
402   /**
403    * Adds the given JMethod to this JClass.
404    *
405    * @param jMethod The JMethod to add.
406    * @param importReturnType true if we add the importReturnType to the class import lists. It could
407    *        be useful to set it to false when all types are fully qualified.
408    */
409   public final void addMethod(final JMethod jMethod, final boolean importReturnType) {
410     if (jMethod == null) {
411       throw new IllegalArgumentException("Class methods cannot be null");
412     }
413 
414     // -- check method name and signatures *add later*
415 
416     // -- keep method list sorted for esthetics when printing
417     // -- START SORT :-)
418     boolean added = false;
419     JModifiers modifiers = jMethod.getModifiers();
420 
421     if (modifiers.isAbstract()) {
422       getModifiers().setAbstract(true);
423     }
424 
425     for (int i = 0; i < _methods.size(); i++) {
426       JMethod tmp = _methods.elementAt(i);
427       // -- first compare modifiers
428       if (tmp.getModifiers().isPrivate()) {
429         if (!modifiers.isPrivate()) {
430           _methods.insertElementAt(jMethod, i);
431           added = true;
432           break;
433         }
434       }
435       // -- compare names
436       if (jMethod.getName().compareTo(tmp.getName()) < 0) {
437         _methods.insertElementAt(jMethod, i);
438         added = true;
439         break;
440       }
441     }
442     // -- END SORT
443     if (!added) {
444       _methods.add(jMethod);
445     }
446 
447   }
448 
449   /**
450    * Adds the given JMethod to this JClass.
451    *
452    * @param jMethod The JMethod to add.
453    */
454   public final void addMethod(final JMethod jMethod) {
455     addMethod(jMethod, true);
456   }
457 
458   /**
459    * Adds the given array of JMethods to this JClass.
460    *
461    * @param jMethods The JMethod[] to add.
462    */
463   public final void addMethods(final JMethod[] jMethods) {
464     for (JMethod jMethod : jMethods) {
465       addMethod(jMethod);
466     }
467   }
468 
469   /**
470    * Removes the given method from this JClass.
471    *
472    * @param method The JMethod to remove.
473    * @return true if the method was removed, otherwise false.
474    */
475   public final boolean removeMethod(final JMethod method) {
476     return _methods.remove(method);
477   }
478 
479   /**
480    * Creates and returns an inner-class for this JClass.
481    *
482    * @param localname The name of the class (no package name).
483    * @return the new JClass.
484    */
485   public final JClass createInnerClass(final String localname) {
486     if (localname == null) {
487       String err = "argument 'localname' must not be null.";
488       throw new IllegalArgumentException(err);
489     }
490     if (localname.indexOf('.') >= 0) {
491       String err = "The name of an inner-class must not contain a package name.";
492       throw new IllegalArgumentException(err);
493     }
494     String classname = getPackageName();
495     if (classname != null) {
496       classname = classname + '.' + localname;
497     } else {
498       classname = localname;
499     }
500 
501     JClass innerClass = new JInnerClass(classname);
502     if (_innerClasses == null) {
503       _innerClasses = new Vector<JClass>();
504     }
505     _innerClasses.add(innerClass);
506     return innerClass;
507 
508   }
509 
510   /**
511    * Returns an array of JClass (the inner classes) contained within this JClass.
512    *
513    * @return An array of JClass contained within this JClass.
514    */
515   public final JClass[] getInnerClasses() {
516     return null != _innerClasses ? _innerClasses.toArray(new JClass[_innerClasses.size()])
517         : new JClass[0];
518   }
519 
520   public final int getInnerClassCount() {
521     return null != _innerClasses ? _innerClasses.size() : 0;
522   }
523 
524   /**
525    * Removes the given inner-class (JClass) from this JClass.
526    *
527    * @param jClass The JClass (inner-class) to remove.
528    * @return true if the JClass was removed, otherwise false.
529    */
530   public final boolean removeInnerClass(final JClass jClass) {
531     return null != _innerClasses ? _innerClasses.remove(jClass) : false;
532   }
533 
534   // --------------------------------------------------------------------------
535 
536   /**
537    * {@inheritDoc}
538    * 
539    * @deprecated Please use the Velocity-template based approach instead.
540    * @see SourceGenerator#setJClassPrinterType(String)
541    */
542   public final void print(final JSourceWriter jsw) {
543     print(jsw, false);
544   }
545 
546   /**
547    * Prints the source code for this JClass to the given JSourceWriter.
548    *
549    * @param classOnly If true, the file header, package declaration, and imports are not printed.
550    * @param jsw The JSourceWriter to print to. Must not be null.
551    * 
552    * @deprecated Please use the Velocity-template based approach instead.
553    * @see SourceGenerator#setJClassPrinterType(String)
554    */
555   public abstract void print(final JSourceWriter jsw, final boolean classOnly);
556 
557   /**
558    * Writes to the JSourceWriter the headers for this class file. Headers include the
559    * comment-header, the package declaration, and the imports.
560    * 
561    * @param jsw The JSourceWriter to be used.
562    */
563   protected final void printClassHeaders(final JSourceWriter jsw) {
564     printHeader(jsw);
565     printPackageDeclaration(jsw);
566 
567     // -- get imports from inner-classes
568     List<String> removeImports = null;
569     if ((_innerClasses != null) && (_innerClasses.size() > 0)) {
570       removeImports = new ArrayList<>();
571       for (JClass iClass : _innerClasses) {
572         Enumeration<String> enumeration = iClass.getImports();
573         while (enumeration.hasMoreElements()) {
574           String classname = enumeration.nextElement();
575 
576           int paramTypeIndex = classname.indexOf("<Object>");
577           if (paramTypeIndex != -1) {
578             classname = classname.substring(0, paramTypeIndex - 1);
579           }
580           if (!hasImport(classname)) {
581             addImport(classname);
582             removeImports.add(classname);
583           }
584         }
585       }
586     }
587 
588     printImportDeclarations(jsw);
589 
590     // -- remove imports from inner-classes, if necessary
591     if (removeImports != null) {
592       for (String imp : removeImports) {
593         removeImport(imp);
594       }
595     }
596   }
597 
598   /**
599    * Writes to the {@link JSourceWriter} the constant definitions of this class.
600    * 
601    * @param jsw The JSourceWriter to be used.
602    */
603   protected final void printConstantDefinitions(final JSourceWriter jsw) {
604     for (JConstant constant : _constants.values()) {
605       printAbstractJField(jsw, constant);
606     }
607   }
608 
609   /**
610    * Prints an {@link AbstractJField} instance to the given {@link JSourceWriter}.
611    * 
612    * @param jsw The {@link JSourceWriter} to print to.
613    * @param field The field to print.
614    */
615   private void printAbstractJField(final JSourceWriter jsw, final AbstractJField field) {
616     // -- print Java comment
617     JDocComment comment = field.getComment();
618     if (comment != null && comment.getLength() > 0) {
619       comment.print(jsw);
620     }
621 
622     // -- print Annotations
623     field.printAnnotations(jsw);
624 
625     // -- print member
626     jsw.write(field.getModifiers().toString());
627     jsw.write(' ');
628 
629     JType type = field.getType();
630     String typeName = type.toString();
631     // -- for esthetics use short name in some cases
632     if (typeName.equals(toString())) {
633       typeName = type.getLocalName();
634     }
635     jsw.write(typeName);
636     jsw.write(' ');
637     jsw.write(field.getName());
638 
639     String init = field.getInitString();
640     if (init != null && !field.isDateTime()) {
641       jsw.write(" = ");
642       jsw.write(init);
643     }
644 
645     jsw.writeln(';');
646     jsw.writeln();
647   }
648 
649   /**
650    * Writes to the JSourceWriter the member variables of this class.
651    * 
652    * @param jsw The JSourceWriter to be used.
653    */
654   protected final void printMemberVariables(final JSourceWriter jsw) {
655     for (JField field : _fields.values()) {
656       printAbstractJField(jsw, field);
657     }
658   }
659 
660   /**
661    * Writes to the JSourceWriter any static initialization used by this class.
662    * 
663    * @param jsw The JSourceWriter to be used.
664    */
665   protected final void printStaticInitializers(final JSourceWriter jsw) {
666     if (!_staticInitializer.isEmpty()) {
667       jsw.writeln();
668       jsw.writeln("static {");
669       jsw.writeln(_staticInitializer.toString());
670       jsw.writeln("}");
671       jsw.writeln();
672     }
673   }
674 
675   /**
676    * Writes to the JSourceWriter all constructors for this class.
677    * 
678    * @param jsw The JSourceWriter to be used.
679    */
680   protected final void printConstructors(final JSourceWriter jsw) {
681     for (JConstructor jConstructor : _constructors) {
682       jConstructor.print(jsw);
683       jsw.writeln();
684     }
685   }
686 
687   /**
688    * Writes to the JSourceWriter all methods belonging to this class.
689    * 
690    * @param jsw The JSourceWriter to be used.
691    */
692   protected final void printMethods(final JSourceWriter jsw) {
693     for (JMethod jMethod : _methods) {
694       jMethod.print(jsw);
695       jsw.writeln();
696     }
697   }
698 
699   protected final void printSourceCodeFragments(final JSourceWriter sourceWriter) {
700     for (String sourceCode : _sourceCodeEntries) {
701       sourceWriter.writeln(sourceCode);
702       sourceWriter.writeln();
703     }
704 
705   }
706 
707   /**
708    * Writes to the JSourceWriter all inner classes belonging to this class.
709    * 
710    * @param jsw The JSourceWriter to be used.
711    */
712   protected final void printInnerClasses(final JSourceWriter jsw) {
713     if (_innerClasses != null && !_innerClasses.isEmpty()) {
714       for (JClass jClass : _innerClasses) {
715         jClass.print(jsw, true);
716         jsw.writeln();
717       }
718     }
719   }
720 
721   /**
722    * Adds a complete source code fragment (method) to this class.
723    * 
724    * @param sourceCode The complete source code fragment to be added.
725    */
726   public void addSourceCode(final String sourceCode) {
727     _sourceCodeEntries.add(sourceCode);
728   }
729 
730   public final int getSourceCodeEntryCount() {
731     return _sourceCodeEntries.size();
732   }
733 
734 }