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 2001-2002 (C) Intalio, Inc. All Rights Reserved.
42   *
43   * $Id$
44   */
45  package org.codehaus.modello.plugin.java.javasource;
46  
47  /*
48   * Copyright (c) 2004, Codehaus.org
49   *
50   * Permission is hereby granted, free of charge, to any person obtaining a copy of
51   * this software and associated documentation files (the "Software"), to deal in
52   * the Software without restriction, including without limitation the rights to
53   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
54   * of the Software, and to permit persons to whom the Software is furnished to do
55   * so, subject to the following conditions:
56   *
57   * The above copyright notice and this permission notice shall be included in all
58   * copies or substantial portions of the Software.
59   *
60   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
61   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
62   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
63   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
64   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
65   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
66   * SOFTWARE.
67   */
68  
69  import java.io.File;
70  import java.nio.file.Files;
71  import java.util.ArrayList;
72  import java.util.Collections;
73  import java.util.Enumeration;
74  import java.util.List;
75  
76  /**
77   * This class represents the basic Java "structure" for a Java
78   * source file. This is the base class for JClass and JInterface.
79   *
80   * This is a useful utility when creating in memory source code.
81   * The code in this package was modelled after the Java Reflection API
82   * as much as possible to reduce the learning curve.
83   *
84   * @author <a href="mailto:skopp@riege.de">Martin Skopp</a>
85   * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
86   * @version $Revision$ $Date$
87   */
88  public abstract class JStructure extends JType {
89  
90      /**
91       * The Id for Source control systems
92       * I needed to separate this line to prevent CVS from
93       * expanding it here! ;-)
94       */
95      static final String DEFAULT_HEADER = "$" + "Id$";
96  
97      /**
98       * The source control version for listed in the JavaDoc
99       * I needed to separate this line to prevent CVS from
100      * expanding it here! ;-)
101      */
102     static final String version = "$" + "Revision$ $" + "Date$";
103 
104     /**
105      * The source header
106      */
107     private JComment header = null;
108 
109     /**
110      * List of imported classes and packages
111      */
112     private List<String> imports = null;
113 
114     /**
115      * The set of interfaces implemented/extended by this JStructure
116      */
117     private List<String> interfaces = null;
118 
119     /**
120      * The Javadoc for this JStructure
121      */
122     private JDocComment jdc = null;
123 
124     /**
125      * The JModifiers for this JStructure, which allows us to
126      * change the resulting qualifiers
127      */
128     private JModifiers modifiers = null;
129 
130     /**
131      * The package to which this JStructure belongs
132      */
133     private final String packageName;
134 
135     private JAnnotations annotations = null;
136 
137     /**
138      * Creates a new JStructure with the given name.
139      *
140      * @param name the name of the JStructure.
141      * @throws java.lang.IllegalArgumentException when the given name
142      * is not a valid Class name.
143      */
144     protected JStructure(String name) throws IllegalArgumentException {
145         super(name);
146 
147         // -- verify name is a valid java class name
148         if (!isValidClassName(name)) {
149             String lname = getLocalName();
150             String err = "'" + lname + "' is ";
151             if (JNaming.isKeyword(lname)) err += "a reserved word and may not be used as " + " a class name.";
152             else err += "not a valid Java identifier.";
153 
154             throw new IllegalArgumentException(err);
155         }
156         this.packageName = getPackageFromClassName(name);
157         imports = new ArrayList<String>();
158         interfaces = new ArrayList<String>();
159         jdc = new JDocComment();
160         modifiers = new JModifiers();
161         // -- initialize default Java doc
162         jdc.addDescriptor(JDocDescriptor.createVersionDesc(version));
163     } // -- JStructure
164 
165     /**
166      * Adds the given JField to this JStructure.
167      * <p>
168      * This method is implemented by subclasses and
169      * should only accept the proper fields for the
170      * subclass otherwise an IllegalArgumentException
171      * will be thrown. For example a JInterface will
172      * only accept static fields.
173      * <p>
174      * @param jField, the JField to add
175      * @exception java.lang.IllegalArgumentException when the given
176      * JField has a name of an existing JField
177      */
178     public abstract void addField(JField jField) throws IllegalArgumentException;
179 
180     /**
181      * Adds the given JMember to this JStructure.
182      * <p>
183      * This method is implemented by subclasses and
184      * should only accept the proper types for the
185      * subclass otherwise an IllegalArgumentException
186      * will be thrown.
187      * <p>
188      * @param jMember the JMember to add to this JStructure.
189      * @throws java.lang.IllegalArgumentException when the given
190      * JMember has the same name of an existing JField
191      * or JMethod respectively.
192      */
193     public abstract void addMember(JMember jMember) throws IllegalArgumentException;
194 
195     /**
196      * Adds the given import to this JStructure
197      *
198      * @param className the className of the class to import.
199      */
200     public void addImport(String className) {
201         if (className == null) return;
202         if (className.length() == 0) return;
203 
204         // -- getPackageName
205         String pkgName = getPackageFromClassName(className);
206 
207         if (pkgName != null) {
208             if (pkgName.equals(this.packageName)) return;
209 
210             // -- XXX: Fix needed for this...
211             // -- This may cause issues if the current package
212             // -- defines any classes that have the same name
213             // -- name as the java.lang package.
214             if ("java.lang".equals(pkgName)) return;
215 
216             // -- for readabilty keep import list sorted, and make sure
217             // -- we do not include more than one of the same import
218             for (int i = 0; i < imports.size(); i++) {
219                 String imp = imports.get(i);
220                 if (imp.equals(className)) return;
221                 if (imp.compareTo(className) > 0) {
222                     imports.add(i, className);
223                     return;
224                 }
225             }
226             imports.add(className);
227         }
228     } // -- addImport
229 
230     /**
231      * Adds the given interface to the list of interfaces this
232      * JStructure inherits method declarations from, and either
233      * implements (JClass) or extends (JInterface).
234      *
235      * @param interfaceName the name of the interface to "inherit"
236      * method declarations from.
237      */
238     public void addInterface(String interfaceName) {
239         if (!interfaces.contains(interfaceName)) interfaces.add(interfaceName);
240     } // -- addInterface
241 
242     /**
243      * Adds the given interface to the list of interfaces this
244      * JStructure inherits method declarations from, and either
245      * implements (JClass) or extends (JInterface).
246      *
247      * @param jInterface the JInterface to inherit from.
248      */
249     public void addInterface(JInterface jInterface) {
250         if (jInterface == null) return;
251         String interfaceName = jInterface.getName();
252         if (!interfaces.contains(interfaceName)) {
253             interfaces.add(interfaceName);
254         }
255     } // -- addInterface
256 
257     /**
258      * Adds the given JMethodSignature to this JClass
259      *
260      * @param jMethodSig the JMethodSignature to add.
261      * @throws java.lang.IllegalArgumentException when the given
262      * JMethodSignature conflicts with an existing
263      * method signature.
264      */
265     /*
266         public void addMethod(JMethodSignature jMethodSig)
267             throws IllegalArgumentException
268         {
269             if (jMethodSig == null) {
270                 String err = "The JMethodSignature cannot be null.";
271                 throw new IllegalArgumentException(err);
272             }
273 
274             //-- XXXX: check method name and signatures *add later*
275 
276             //-- keep method list sorted for esthetics when printing
277             //-- START SORT :-)
278             boolean added = false;
279             short modifierVal = 0;
280             JModifiers modifiers = jMethodSig.getModifiers();
281             for (int i = 0; i < methods.size(); i++) {
282                 JMethodSignature tmp = (JMethodSignature) methods.elementAt(i);
283                 //-- first compare modifiers
284                 if (tmp.getModifiers().isProtected()) {
285                     if (!modifiers.isProtected()) {
286                         methods.insertElementAt(jMethodSig, i);
287                         added = true;
288                         break;
289                     }
290                 }
291                 //-- compare names
292                 if (jMethodSig.getName().compareTo(tmp.getName()) < 0) {
293                         methods.insertElementAt(jMethodSig, i);
294                         added = true;
295                         break;
296                 }
297             }
298             //-- END SORT
299             if (!added) methods.addElement(jMethodSig);
300 
301             //-- check parameter packages to make sure we have them
302             //-- in our import list
303 
304             String[] pkgNames = jMethodSig.getParameterClassNames();
305             for (int i = 0; i < pkgNames.length; i++) {
306                 addImport(pkgNames[i]);
307             }
308             //-- check return type to make sure it's included in the
309             //-- import list
310             JType jType = jMethodSig.getReturnType();
311             if (jType != null) {
312                 while (jType.isArray())
313                     jType = jType.getComponentType();
314 
315                 if   (!jType.isPrimitive())
316                      addImport(jType.getName());
317             }
318             //-- check exceptions
319             JClass[] exceptions = jMethodSig.getExceptions();
320             for (int i = 0; i < exceptions.length; i++) {
321                 addImport(exceptions[i].getName());
322             }
323         } //-- addMethod
324     */
325     /**
326      * Returns the field with the given name, or null if no field
327      * was found with the given name.
328      *
329      * @param name the name of the field to return.
330      * @return the field with the given name, or null if no field
331      * was found with the given name.
332      */
333     public abstract JField getField(String name);
334 
335     /**
336      * Returns an array of all the JFields of this JStructure
337      *
338      * @return an array of all the JFields of this JStructure
339      */
340     public abstract JField[] getFields();
341 
342     /**
343      * Returns the name of the file that this JStructure would be
344      * printed to, given a call to #print.
345      *
346      * @param destDir the destination directory. This may be null.
347      * @return the name of the file that this JInterface would be
348      * printed as, given a call to #print.
349      */
350     public String getFilename(String destDir) {
351 
352         String filename = getLocalName() + ".java";
353 
354         // -- Convert Java package to path string
355         String javaPackagePath = "";
356         if ((packageName != null) && (packageName.length() > 0)) {
357             javaPackagePath = packageName.replace('.', File.separatorChar);
358         }
359 
360         // -- Create fully qualified path (including 'destDir') to file
361         File pathFile;
362         if (destDir == null) pathFile = new File(javaPackagePath);
363         else pathFile = new File(destDir, javaPackagePath);
364         if (!pathFile.exists()) {
365             pathFile.mkdirs();
366         }
367 
368         // -- Prefix filename with path
369         if (pathFile.toString().length() > 0) filename = pathFile.toString() + File.separator + filename;
370 
371         return filename;
372     } // -- getFilename
373 
374     /**
375      * Returns the JComment header to display at the top of the source file
376      * for this JStructure, or null if no header was set.
377      *
378      * @return the JComment header or null if none exists.
379      */
380     public JComment getHeader() {
381         return this.header;
382     } // -- getHeader
383 
384     /**
385      * Returns an Enumeration of imported package and
386      * class names for this JStructure.
387      *
388      * @return the Enumeration of imports. May be empty.
389      */
390     public Enumeration<String> getImports() {
391         return Collections.enumeration(imports);
392     } // -- getImports
393 
394     /**
395      * Returns an Enumeration of interface names that this
396      * JStructure inherits from.
397      *
398      * @return the Enumeration of interface names for this
399      * JStructure. May be empty.
400      */
401     public Enumeration<String> getInterfaces() {
402         return Collections.enumeration(interfaces);
403     } // -- getInterfaces
404 
405     /**
406      * Returns the Java Doc comment for this JStructure
407      *
408      * @return the JDocComment for this JStructure
409      */
410     public JDocComment getJDocComment() {
411         return jdc;
412     } // -- getJDocComment
413 
414     /**
415      * Returns an array of all the JMethodSignatures of this JInterface.
416      *
417      * @return an array of all the JMethodSignatures of this JInterface.
418      */
419     /*
420         public JMethodSignature[] getMethods() {
421             JMethodSignature[] marray = new JMethodSignature[methods.size()];
422             methods.copyInto(marray);
423             return marray;
424         } //-- getMethods
425     */
426 
427     /**
428      * Returns the JMethodSignature with the given name,
429      * and occuring at or after the given starting index.
430      *
431      * @param name the name of the JMethodSignature to return.
432      * @param startIndex the starting index to begin searching
433      * from.
434      * @return the JMethodSignature, or null if not found.
435      */
436     /*
437         public JMethodSignature getMethod(String name, int startIndex) {
438             for (int i = startIndex; i < methods.size(); i++) {
439                 JMethodSignature jMethod = (JMethodSignature)methods.elementAt(i);
440                 if (jMethod.getName().equals(name)) return jMethod;
441             }
442             return null;
443         } //-- getMethod
444     */
445 
446     /**
447      * Returns the JMethodSignature at the given index.
448      *
449      * @param index the index of the JMethodSignature to return.
450      * @return the JMethodSignature at the given index.
451      */
452     /*
453        public JMethodSignature getMethod(int index) {
454            return (JMethodSignature)methods.elementAt(index);
455        } //-- getMethod
456     */
457 
458     /**
459      * Returns the JModifiers which allows the qualifiers to be changed.
460      *
461      * @return the JModifiers for this JStructure.
462      */
463     public JModifiers getModifiers() {
464         return modifiers;
465     } // -- getModifiers
466 
467     /**
468      * Returns the name of the package that this JStructure is a member
469      * of.
470      *
471      * @return the name of the package that this JStructure is a member
472      * of, or null if there is no current package name defined.
473      */
474     public String getPackageName() {
475         return this.packageName;
476     } // -- getPackageName
477 
478     /**
479      * Returns the name of the interface.
480      *
481      * @param stripPackage a boolean that when true indicates that only
482      * the local name (no package) should be returned.
483      * @return the name of the class.
484      */
485     public String getName(boolean stripPackage) {
486         String name = super.getName();
487         if (stripPackage) {
488             int period = name.lastIndexOf(".");
489             if (period > 0) name = name.substring(period + 1);
490         }
491         return name;
492     } // -- getName
493 
494     /**
495      * Returns true if the given classname exists in the imports
496      * of this JStructure
497      *
498      * @param classname the class name to check for
499      * @return true if the given classname exists in the imports list
500      */
501     public boolean hasImport(String classname) {
502         return imports.contains(classname);
503     } // -- hasImport
504 
505     public boolean removeImport(String className) {
506         boolean result = false;
507         if (className == null) return result;
508         if (className.length() == 0) return result;
509 
510         return imports.remove(className);
511     } // -- removeImport
512 
513     public boolean isAbstract() {
514         return modifiers.isAbstract();
515     }
516 
517     public static boolean isValidClassName(String name) {
518 
519         if (name == null) return false;
520 
521         // -- ignore package information, for now
522         int period = name.lastIndexOf(".");
523         if (period > 0) name = name.substring(period + 1);
524 
525         return JNaming.isValidJavaIdentifier(name);
526     } // -- isValidClassName
527 
528     /**
529      * Prints the source code for this JStructure in the current
530      * working directory. Sub-directories will be created if necessary
531      * for the package.
532      */
533     public void print() {
534         print((String) null, (String) null);
535     } // -- printSrouce
536 
537     /**
538      * Prints the source code for this JStructure to the destination
539      * directory. Sub-directories will be created if necessary for the
540      * package.
541      *
542      * @param destDir the destination directory
543      * @param lineSeparator the line separator to use at the end of each line.
544      * If null, then the default line separator for the runtime platform will
545      * be used.
546      */
547     public void print(String destDir, String lineSeparator) {
548 
549         //        String name = getLocalName();
550 
551         // -- open output file
552         String filename = getFilename(destDir);
553 
554         File file = new File(filename);
555         JSourceWriter jsw = null;
556         try {
557             jsw = new JSourceWriter(Files.newBufferedWriter(file.toPath()));
558         } catch (java.io.IOException ioe) {
559             System.out.println("unable to create class file: " + filename);
560             return;
561         }
562         if (lineSeparator == null) {
563             lineSeparator = System.getProperty("line.separator");
564         }
565         jsw.setLineSeparator(lineSeparator);
566         print(jsw);
567         jsw.close();
568     } // -- print
569 
570     /**
571      * Prints the source code for this JStructure to the given
572      * JSourceWriter.
573      *
574      * @param jsw the JSourceWriter to print to.
575      */
576     public abstract void print(JSourceWriter jsw);
577 
578     /**
579      * A utility method that prints the header to the given
580      * JSourceWriter
581      *
582      * @param jsw the JSourceWriter to print to.
583      */
584     public void printHeader(JSourceWriter jsw) {
585 
586         if (jsw == null) {
587             throw new IllegalArgumentException("argument 'jsw' should not be null.");
588         }
589 
590         // -- write class header
591         JComment header = getHeader();
592         if (header != null) header.print(jsw);
593         else {
594             jsw.writeln("/*");
595             jsw.writeln(" * " + DEFAULT_HEADER);
596             jsw.writeln(" */");
597         }
598         jsw.writeln();
599         jsw.flush();
600     } // -- printHeader
601 
602     /**
603      * A utility method that prints the imports to the given
604      * JSourceWriter
605      *
606      * @param jsw the JSourceWriter to print to.
607      */
608     public void printImportDeclarations(JSourceWriter jsw) {
609 
610         if (jsw == null) {
611             throw new IllegalArgumentException("argument 'jsw' should not be null.");
612         }
613 
614         // -- print imports
615         if (imports.size() > 0) {
616             jsw.writeln("  //---------------------------------/");
617             jsw.writeln(" //- Imported classes and packages -/");
618             jsw.writeln("//---------------------------------/");
619             jsw.writeln();
620             for (String imp : imports) {
621                 jsw.write("import ");
622                 jsw.write(imp);
623                 jsw.writeln(';');
624             }
625             jsw.writeln();
626             jsw.flush();
627         }
628     } // -- printImportDeclarations
629 
630     /**
631      * A utility method that prints the packageDeclaration to
632      * the given JSourceWriter
633      *
634      * @param jsw the JSourceWriter to print to.
635      */
636     public void printPackageDeclaration(JSourceWriter jsw) {
637 
638         if (jsw == null) {
639             throw new IllegalArgumentException("argument 'jsw' should not be null.");
640         }
641 
642         // -- print package name
643         if ((packageName != null) && (packageName.length() > 0)) {
644             jsw.write("package ");
645             jsw.write(packageName);
646             jsw.writeln(';');
647             jsw.writeln();
648         }
649         jsw.flush();
650     } // -- printPackageDeclaration
651 
652     /**
653      * Prints the source code for this JStructure to the given
654      * JSourceWriter.
655      *
656      * @param jsw the JSourceWriter to print to.
657      *
658      * public abstract void print(JSourceWriter jsw);
659      *
660      *
661      * StringBuilder buffer = new StringBuilder();
662      *
663      *
664      * printHeader();
665      * printPackageDeclaration();
666      * printImportDeclarations();
667      *
668      * //------------/
669      * //- Java Doc -/
670      * //------------/
671      *
672      * jdc.print(jsw);
673      *
674      * //-- print class information
675      * //-- we need to add some JavaDoc API adding comments
676      *
677      * buffer.setLength(0);
678      *
679      * if (modifiers.isPrivate()) {
680      * buffer.append("private ");
681      * }
682      * else if (modifiers.isPublic()) {
683      * buffer.append("public ");
684      * }
685      *
686      * if (modifiers.isAbstract()) {
687      * buffer.append("abstract ");
688      * }
689      *
690      * buffer.append("interface ");
691      * buffer.append(getLocalName());
692      * buffer.append(' ');
693      * if (interfaces.size() > 0) {
694      * boolean endl = false;
695      * if (interfaces.size() > 1) {
696      * jsw.writeln(buffer.toString());
697      * buffer.setLength(0);
698      * endl = true;
699      * }
700      * buffer.append("extends ");
701      * for (int i = 0; i < interfaces.size(); i++) {
702      * if (i > 0) buffer.append(", ");
703      * buffer.append(interfaces.elementAt(i));
704      * }
705      * if (endl) {
706      * jsw.writeln(buffer.toString());
707      * buffer.setLength(0);
708      * }
709      * else buffer.append(' ');
710      * }
711      *
712      * buffer.append('{');
713      * jsw.writeln(buffer.toString());
714      * buffer.setLength(0);
715      * jsw.writeln();
716      *
717      * jsw.indent();
718      *
719      * //-- print method signatures
720      *
721      * if (methods.size() > 0) {
722      * jsw.writeln();
723      * jsw.writeln("  //-----------/");
724      * jsw.writeln(" //- Methods -/");
725      * jsw.writeln("//-----------/");
726      * jsw.writeln();
727      * }
728      *
729      * for (int i = 0; i < methods.size(); i++) {
730      * JMethodSignature signature = (JMethodSignature) methods.elementAt(i);
731      * signature.print(jsw);
732      * jsw.writeln(';');
733      * }
734      *
735      * jsw.unindent();
736      * jsw.writeln('}');
737      * jsw.flush();
738      * jsw.close();
739      * } //-- printSource
740      */
741 
742     /**
743      * Sets the header comment for this JStructure
744      *
745      * @param comment the comment to display at the top of the source file
746      * when printed
747      */
748     public void setHeader(JComment comment) {
749         this.header = comment;
750     } // -- setHeader
751 
752     // ---------------------/
753     // - Protected Methods -/
754     // ---------------------/
755 
756     protected int getInterfaceCount() {
757         return interfaces.size();
758     }
759 
760     /**
761      * Prints the given source string to the JSourceWriter using the given prefix at
762      * the beginning of each new line.
763      *
764      * @param prefix the prefix for each new line.
765      * @param source the source code to print
766      * @param jsw the JSourceWriter to print to.
767      */
768     protected static void printlnWithPrefix(String prefix, String source, JSourceWriter jsw) {
769         jsw.write(prefix);
770         if (source == null) return;
771 
772         char[] chars = source.toCharArray();
773         int lastIdx = 0;
774         for (int i = 0; i < chars.length; i++) {
775             char ch = chars[i];
776             if (ch == '\n') {
777                 // -- free buffer
778                 jsw.write(chars, lastIdx, (i - lastIdx) + 1);
779                 lastIdx = i + 1;
780                 if (i < chars.length) {
781                     jsw.write(prefix);
782                 }
783             }
784         }
785         // -- free buffer
786         if (lastIdx < chars.length) {
787             jsw.write(chars, lastIdx, chars.length - lastIdx);
788         }
789         jsw.writeln();
790     } // -- printlnWithPrefix
791 
792     /**
793      * Returns the package name from the given class name
794      *
795      * @param className the className
796      * @return the package of the class, otherwise {@code null}
797      */
798     protected static String getPackageFromClassName(String className) {
799         int idx = -1;
800         if ((idx = className.lastIndexOf('.')) > 0) {
801             return className.substring(0, idx);
802         }
803         return null;
804     } // -- getPackageFromClassName
805 
806     /**
807      * @return the annotations
808      */
809     public JAnnotations getAnnotations() {
810         return annotations;
811     }
812 
813     /**
814      * @param annotation the annotation to append
815      */
816     public void appendAnnotation(String annotation) {
817         if (annotations == null) {
818             annotations = new JAnnotations();
819         }
820         annotations.appendAnnotation(annotation);
821     }
822 
823     /**
824      * @param annotations the annotations to set
825      */
826     public void setAnnotations(JAnnotations annotations) {
827         this.annotations = annotations;
828     }
829 } // -- JStructure