View Javadoc
1   /**
2    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3    *
4    * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
5    * Portions Copyright 2013-2015 Philip Helger
6    *
7    * The contents of this file are subject to the terms of either the GNU
8    * General Public License Version 2 only ("GPL") or the Common Development
9    * and Distribution License("CDDL") (collectively, the "License").  You
10   * may not use this file except in compliance with the License.  You can
11   * obtain a copy of the License at
12   * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
13   * or packager/legal/LICENSE.txt.  See the License for the specific
14   * language governing permissions and limitations under the License.
15   *
16   * When distributing the software, include this License Header Notice in each
17   * file and include the License file at packager/legal/LICENSE.txt.
18   *
19   * GPL Classpath Exception:
20   * Oracle designates this particular file as subject to the "Classpath"
21   * exception as provided by Oracle in the GPL Version 2 section of the License
22   * file that accompanied this code.
23   *
24   * Modifications:
25   * If applicable, add the following below the License Header, with the fields
26   * enclosed by brackets [] replaced by your own identifying information:
27   * "Portions Copyright [year] [name of copyright owner]"
28   *
29   * Contributor(s):
30   * If you wish your version of this file to be governed by only the CDDL or
31   * only the GPL Version 2, indicate your decision by adding "[Contributor]
32   * elects to include this software in this distribution under the [CDDL or GPL
33   * Version 2] license."  If you don't indicate a single choice of license, a
34   * recipient has the option to distribute your version of this file under
35   * either the CDDL, the GPL Version 2 or to extend the choice of license to
36   * its licensees as provided above.  However, if you add GPL Version 2 code
37   * and therefore, elected the GPL Version 2 license, then the option applies
38   * only if the new code is made subject to such option by the copyright
39   * holder.
40   */
41  package com.helger.jcodemodel;
42  
43  import java.io.BufferedOutputStream;
44  import java.io.BufferedWriter;
45  import java.io.File;
46  import java.io.IOException;
47  import java.io.OutputStream;
48  import java.io.PrintWriter;
49  import java.io.Writer;
50  import java.lang.annotation.Annotation;
51  import java.util.ArrayList;
52  import java.util.Collection;
53  import java.util.Collections;
54  import java.util.HashMap;
55  import java.util.HashSet;
56  import java.util.Iterator;
57  import java.util.List;
58  import java.util.Map;
59  import java.util.Set;
60  import java.util.TreeMap;
61  
62  import javax.annotation.Nonnull;
63  import javax.annotation.Nullable;
64  
65  import com.helger.jcodemodel.util.JCValueEnforcer;
66  
67  /**
68   * A Java package.
69   */
70  public class JPackage implements IJDeclaration, IJGenerable, IJClassContainer <JDefinedClass>, IJAnnotatable, Comparable <JPackage>, IJDocCommentable
71  {
72    /**
73     * Name of the package. May be the empty string for the root package.
74     */
75    private final String m_sName;
76  
77    private final JCodeModel m_aOwner;
78  
79    /**
80     * List of classes contained within this package keyed by their name.
81     */
82    private final Map <String, JDefinedClass> m_aClasses = new TreeMap <String, JDefinedClass> ();
83  
84    /**
85     * List of resources files inside this package.
86     */
87    private final Set <AbstractJResourceFile> m_aResources = new HashSet <AbstractJResourceFile> ();
88  
89    /**
90     * All {@link AbstractJClass}s in this package keyed the upper case class
91     * name. This field is non-null only on Windows, to detect "Foo" and "foo" as
92     * a collision.
93     */
94    private final Map <String, JDefinedClass> m_aUpperCaseClassMap;
95  
96    /**
97     * Lazily created list of package annotations.
98     */
99    private List <JAnnotationUse> m_aAnnotations;
100 
101   /**
102    * package javadoc.
103    */
104   private JDocComment m_aJavaDoc;
105 
106   /**
107    * JPackage constructor
108    *
109    * @param sName
110    *        Name of package. May not be <code>null</code> but empty.
111    * @param aOwner
112    *        The code writer being used to create this package
113    * @throws IllegalArgumentException
114    *         If each part of the package name is not a valid identifier
115    */
116   protected JPackage (@Nonnull final String sName, @Nonnull final JCodeModel aOwner)
117   {
118     JCValueEnforcer.notNull (sName, "Name");
119     if (sName.equals ("."))
120       throw new IllegalArgumentException ("Package name . is not allowed");
121     JCValueEnforcer.notNull (aOwner, "CodeModel");
122 
123     m_aOwner = aOwner;
124     m_sName = sName;
125     if (m_aOwner.isCaseSensitiveFileSystem)
126       m_aUpperCaseClassMap = null;
127     else
128       m_aUpperCaseClassMap = new HashMap <String, JDefinedClass> ();
129   }
130 
131   @Nullable
132   public IJClassContainer <?> parentContainer ()
133   {
134     return parent ();
135   }
136 
137   /**
138    * @return the parent package, or <code>null</code> if this class is the root
139    *         package.
140    */
141   @Nullable
142   public JPackage parent ()
143   {
144     if (isUnnamed ())
145       return null;
146 
147     final int idx = m_sName.lastIndexOf ('.');
148     if (idx < 0)
149       return null;
150     return m_aOwner._package (m_sName.substring (0, idx));
151   }
152 
153   public boolean isClass ()
154   {
155     return false;
156   }
157 
158   public boolean isPackage ()
159   {
160     return true;
161   }
162 
163   @Nonnull
164   public JPackage getPackage ()
165   {
166     return this;
167   }
168 
169   /**
170    * Add a class to this package.
171    *
172    * @param nMods
173    *        Modifiers for this class declaration
174    * @param sName
175    *        Name of class to be added to this package
176    * @return Newly generated class
177    * @exception JClassAlreadyExistsException
178    *            When the specified class/interface was already created.
179    */
180   @Nonnull
181   public JDefinedClass _class (final int nMods, @Nonnull final String sName) throws JClassAlreadyExistsException
182   {
183     return _class (nMods, sName, EClassType.CLASS);
184   }
185 
186   @Nonnull
187   public JDefinedClass _class (final int nMods,
188                                @Nonnull final String sName,
189                                @Nonnull final EClassType eClassType) throws JClassAlreadyExistsException
190   {
191     if (m_aClasses.containsKey (sName))
192       throw new JClassAlreadyExistsException (m_aClasses.get (sName));
193 
194     // XXX problems caught in the NC constructor
195     final JDefinedClass c = new JDefinedClass (this, nMods, sName, eClassType);
196 
197     if (m_aUpperCaseClassMap != null)
198     {
199       final String sUpperName = sName.toUpperCase ();
200       final JDefinedClass dc = m_aUpperCaseClassMap.get (sUpperName);
201       if (dc != null)
202         throw new JClassAlreadyExistsException (dc);
203       m_aUpperCaseClassMap.put (sUpperName, c);
204     }
205     m_aClasses.put (sName, c);
206     return c;
207   }
208 
209   /**
210    * Adds a public class to this package.
211    */
212   @Nonnull
213   public JDefinedClass _class (@Nonnull final String sName) throws JClassAlreadyExistsException
214   {
215     return _class (JMod.PUBLIC, sName);
216   }
217 
218   /**
219    * Gets a reference to the already created {@link JDefinedClass}.
220    *
221    * @param sName
222    *        Class name to search
223    * @return <code>null</code> if the class is not yet created.
224    */
225   @Nullable
226   public JDefinedClass _getClass (@Nullable final String sName)
227   {
228     return m_aClasses.get (sName);
229   }
230 
231   /**
232    * Order is based on the lexicographic order of the package name.
233    *
234    * @param aOther
235    *        Other package to compare to
236    */
237   public int compareTo (@Nonnull final JPackage aOther)
238   {
239     return m_sName.compareTo (aOther.m_sName);
240   }
241 
242   /**
243    * Add an interface to this package.
244    *
245    * @param nMods
246    *        Modifiers for this interface declaration
247    * @param sName
248    *        Name of interface to be added to this package
249    * @return Newly generated interface
250    */
251   @Nonnull
252   public JDefinedClass _interface (final int nMods, @Nonnull final String sName) throws JClassAlreadyExistsException
253   {
254     return _class (nMods, sName, EClassType.INTERFACE);
255   }
256 
257   /**
258    * Adds a public interface to this package.
259    */
260   @Nonnull
261   public JDefinedClass _interface (@Nonnull final String sName) throws JClassAlreadyExistsException
262   {
263     return _interface (JMod.PUBLIC, sName);
264   }
265 
266   /**
267    * Add a annotationType Declaration to this package
268    *
269    * @param mods
270    *        Modifiers for this annotationType declaration
271    * @param name
272    *        Name of the annotation Type declaration to be added to this package
273    * @return newly created Annotation Type Declaration
274    * @exception JClassAlreadyExistsException
275    *            When the specified class/interface was already created.
276    */
277   @Nonnull
278   public JDefinedClass _annotationTypeDeclaration (final int mods,
279                                                    @Nonnull final String name) throws JClassAlreadyExistsException
280   {
281     return _class (mods, name, EClassType.ANNOTATION_TYPE_DECL);
282   }
283 
284   /**
285    * Add a public annotationType Declaration to this package
286    *
287    * @param name
288    *        Name of the annotation Type declaration to be added to this package
289    * @return newly created Annotation Type Declaration
290    * @exception JClassAlreadyExistsException
291    *            When the specified class/interface was already created.
292    */
293   @Nonnull
294   public JDefinedClass _annotationTypeDeclaration (@Nonnull final String name) throws JClassAlreadyExistsException
295   {
296     return _annotationTypeDeclaration (JMod.PUBLIC, name);
297   }
298 
299   /**
300    * Add a enum to this package
301    *
302    * @param mods
303    *        Modifiers for this enum declaration
304    * @param name
305    *        Name of the enum to be added to this package
306    * @return newly created Enum
307    * @exception JClassAlreadyExistsException
308    *            When the specified class/interface was already created.
309    */
310   @Nonnull
311   public JDefinedClass _enum (final int mods, @Nonnull final String name) throws JClassAlreadyExistsException
312   {
313     return _class (mods, name, EClassType.ENUM);
314   }
315 
316   /**
317    * Add a public enum to this package
318    *
319    * @param name
320    *        Name of the enum to be added to this package
321    * @return newly created Enum
322    * @exception JClassAlreadyExistsException
323    *            When the specified class/interface was already created.
324    */
325   @Nonnull
326   public JDefinedClass _enum (@Nonnull final String name) throws JClassAlreadyExistsException
327   {
328     return _enum (JMod.PUBLIC, name);
329   }
330 
331   /**
332    * Adds a new resource file to this package.
333    *
334    * @param rsrc
335    *        Resource file to add
336    * @return Parameter resource file
337    */
338   @Nonnull
339   public AbstractJResourceFile addResourceFile (@Nonnull final AbstractJResourceFile rsrc)
340   {
341     JCValueEnforcer.notNull (rsrc, "ResourceFile");
342     m_aResources.add (rsrc);
343     return rsrc;
344   }
345 
346   /**
347    * Checks if a resource of the given name exists.
348    *
349    * @param name
350    *        Filename to check
351    * @return <code>true</code> if contained
352    */
353   public boolean hasResourceFile (@Nullable final String name)
354   {
355     for (final AbstractJResourceFile r : m_aResources)
356       if (r.name ().equals (name))
357         return true;
358     return false;
359   }
360 
361   /**
362    * Iterates all resource files in this package.
363    *
364    * @return Iterator
365    */
366   @Nonnull
367   public Iterator <AbstractJResourceFile> propertyFiles ()
368   {
369     return m_aResources.iterator ();
370   }
371 
372   /**
373    * Creates, if necessary, and returns the package javadoc for this
374    * JDefinedClass.
375    *
376    * @return {@link JDocComment} containing javadocs for this class
377    */
378   @Nonnull
379   public JDocComment javadoc ()
380   {
381     if (m_aJavaDoc == null)
382       m_aJavaDoc = new JDocComment (owner ());
383     return m_aJavaDoc;
384   }
385 
386   /**
387    * Removes a class from this package.
388    *
389    * @param c
390    *        Class to be removed
391    */
392   public void remove (@Nonnull final AbstractJClass c)
393   {
394     if (c._package () != this)
395       throw new IllegalArgumentException ("the specified class is not a member of this package," +
396                                           " or it is a referenced class");
397 
398     // note that c may not be a member of classes.
399     // this happens when someone is trying to remove a non generated class
400     m_aClasses.remove (c.name ());
401     if (m_aUpperCaseClassMap != null)
402       m_aUpperCaseClassMap.remove (c.name ().toUpperCase ());
403   }
404 
405   /**
406    * Reference a class within this package.
407    *
408    * @param sClassLocalName
409    *        Local class name to reference
410    * @return The referenced class
411    * @throws ClassNotFoundException
412    *         If the provided class does not exist
413    */
414   @Nonnull
415   public AbstractJClass ref (@Nonnull final String sClassLocalName) throws ClassNotFoundException
416   {
417     if (sClassLocalName.indexOf ('.') >= 0)
418       throw new IllegalArgumentException ("JClass name contains '.': " + sClassLocalName);
419 
420     String sFQCN;
421     if (isUnnamed ())
422       sFQCN = "";
423     else
424       sFQCN = m_sName + '.';
425     sFQCN += sClassLocalName;
426 
427     return m_aOwner.ref (Class.forName (sFQCN));
428   }
429 
430   /**
431    * Gets a reference to a sub package of this package.
432    *
433    * @param sSubPackageName
434    *        Name of the sub-package
435    * @return New sub-package
436    */
437   @Nonnull
438   public JPackage subPackage (@Nonnull final String sSubPackageName)
439   {
440     if (isUnnamed ())
441       return owner ()._package (sSubPackageName);
442     return owner ()._package (m_sName + '.' + sSubPackageName);
443   }
444 
445   /**
446    * @return the top-level classes defined in this package.
447    */
448   @Nonnull
449   public Collection <JDefinedClass> classes ()
450   {
451     return m_aClasses.values ();
452   }
453 
454   /**
455    * Checks if a given name is already defined as a class/interface
456    *
457    * @param classLocalName
458    *        Class local name
459    * @return <code>true</code> if contained in this package
460    */
461   public boolean isDefined (@Nullable final String classLocalName)
462   {
463     for (final JDefinedClass clazz : m_aClasses.values ())
464       if (clazz.name ().equals (classLocalName))
465         return true;
466     return false;
467   }
468 
469   /**
470    * Checks if this package is the root, unnamed package.
471    *
472    * @return <code>true</code> if this is the root package
473    */
474   public final boolean isUnnamed ()
475   {
476     return m_sName.length () == 0;
477   }
478 
479   /**
480    * Get the name of this package
481    *
482    * @return The name of this package, or the empty string if this is the null
483    *         package. For example, this method returns strings like
484    *         <code>"java.lang"</code>
485    */
486   @Nonnull
487   public String name ()
488   {
489     return m_sName;
490   }
491 
492   /**
493    * @return the code model root object being used to create this package.
494    */
495   @Nonnull
496   public final JCodeModel owner ()
497   {
498     return m_aOwner;
499   }
500 
501   @Nonnull
502   public JAnnotationUse annotate (@Nonnull final AbstractJClass clazz)
503   {
504     if (isUnnamed ())
505       throw new IllegalArgumentException ("the root package cannot be annotated");
506 
507     if (m_aAnnotations == null)
508       m_aAnnotations = new ArrayList <JAnnotationUse> ();
509 
510     final JAnnotationUse a = new JAnnotationUse (clazz);
511     m_aAnnotations.add (a);
512     return a;
513   }
514 
515   @Nonnull
516   public JAnnotationUse annotate (@Nonnull final Class <? extends Annotation> clazz)
517   {
518     return annotate (m_aOwner.ref (clazz));
519   }
520 
521   @Nonnull
522   public <W extends IJAnnotationWriter <?>> W annotate2 (@Nonnull final Class <W> clazz)
523   {
524     return TypedAnnotationWriter.create (clazz, this);
525   }
526 
527   @Nonnull
528   public Collection <JAnnotationUse> annotations ()
529   {
530     if (m_aAnnotations == null)
531       m_aAnnotations = new ArrayList <JAnnotationUse> ();
532     return Collections.unmodifiableList (m_aAnnotations);
533   }
534 
535   /**
536    * Convert the package name to directory path equivalent
537    */
538   @Nonnull
539   File toPath (@Nonnull final File dir)
540   {
541     if (m_sName == null)
542       return dir;
543     return new File (dir, m_sName.replace ('.', File.separatorChar));
544   }
545 
546   public void declare (@Nonnull final JFormatter f)
547   {
548     if (m_sName.length () != 0)
549       f.print ("package").print (m_sName).print (';').newline ();
550   }
551 
552   public void generate (@Nonnull final JFormatter f)
553   {
554     f.print (m_sName);
555   }
556 
557   void build (final AbstractCodeWriter src, final AbstractCodeWriter res) throws IOException
558   {
559     // write classes
560     for (final JDefinedClass c : m_aClasses.values ())
561     {
562       if (c.isHidden ())
563       {
564         // don't generate this file
565         continue;
566       }
567 
568       final JFormatter f = _createJavaSourceFileWriter (src, c.name ());
569       f.write (c);
570       f.close ();
571     }
572 
573     // write package annotations
574     if (m_aAnnotations != null || m_aJavaDoc != null)
575     {
576       final JFormatter f = _createJavaSourceFileWriter (src, "package-info");
577 
578       if (m_aJavaDoc != null)
579         f.generable (m_aJavaDoc);
580 
581       // TODO: think about importing
582       if (m_aAnnotations != null)
583       {
584         for (final JAnnotationUse a : m_aAnnotations)
585           f.generable (a).newline ();
586       }
587       f.declaration (this);
588 
589       f.close ();
590     }
591 
592     // write resources
593     for (final AbstractJResourceFile rsrc : m_aResources)
594     {
595       final AbstractCodeWriter cw = rsrc.isResource () ? res : src;
596       final OutputStream os = new BufferedOutputStream (cw.openBinary (this, rsrc.name ()));
597       try
598       {
599         rsrc.build (os);
600       }
601       finally
602       {
603         os.close ();
604       }
605     }
606   }
607 
608   boolean buildsErrorTypeRefs ()
609   {
610     // check classes
611     for (final JDefinedClass c : m_aClasses.values ())
612     {
613       if (c.isHidden ())
614       {
615         // don't check this file
616         continue;
617       }
618 
619       if (JFormatter.containsErrorTypes (c))
620         return true;
621     }
622     return false;
623   }
624 
625   /* package */int countArtifacts ()
626   {
627     int ret = 0;
628     for (final JDefinedClass c : m_aClasses.values ())
629     {
630       if (c.isHidden ())
631       {
632         // don't generate this file
633         continue;
634       }
635       ret++;
636     }
637 
638     if (m_aAnnotations != null || m_aJavaDoc != null)
639     {
640       // package-info
641       ret++;
642     }
643 
644     ret += m_aResources.size ();
645 
646     return ret;
647   }
648 
649   @Nonnull
650   private JFormatter _createJavaSourceFileWriter (@Nonnull final AbstractCodeWriter src,
651                                                   @Nonnull final String className) throws IOException
652   {
653     final Writer bw = new BufferedWriter (src.openSource (this, className + ".java"));
654     return new JFormatter (new PrintWriter (bw));
655   }
656 }