1/*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.codemodel.internal;
27
28import java.io.BufferedOutputStream;
29import java.io.BufferedWriter;
30import java.io.File;
31import java.io.IOException;
32import java.io.OutputStream;
33import java.io.PrintWriter;
34import java.io.Writer;
35import java.lang.annotation.Annotation;
36import java.util.ArrayList;
37import java.util.HashMap;
38import java.util.HashSet;
39import java.util.Iterator;
40import java.util.List;
41import java.util.Map;
42import java.util.Set;
43import java.util.TreeMap;
44import java.util.Collection;
45import java.util.Collections;
46
47
48/**
49 * A Java package.
50 */
51public final class JPackage implements JDeclaration, JGenerable, JClassContainer, JAnnotatable, Comparable<JPackage>, JDocCommentable {
52
53    /**
54     * Name of the package.
55     * May be the empty string for the root package.
56     */
57    private String name;
58
59    private final JCodeModel owner;
60
61    /**
62     * List of classes contained within this package keyed by their name.
63     */
64    private final Map<String,JDefinedClass> classes = new TreeMap<String,JDefinedClass>();
65
66    /**
67     * List of resources files inside this package.
68     */
69    private final Set<JResourceFile> resources = new HashSet<JResourceFile>();
70
71    /**
72     * All {@link JClass}s in this package keyed the upper case class name.
73     *
74     * This field is non-null only on Windows, to detect
75     * "Foo" and "foo" as a collision.
76     */
77    private final Map<String,JDefinedClass> upperCaseClassMap;
78
79    /**
80     * Lazily created list of package annotations.
81     */
82    private List<JAnnotationUse> annotations = null;
83
84    /**
85     * package javadoc.
86     */
87    private JDocComment jdoc = null;
88
89    /**
90     * JPackage constructor
91     *
92     * @param name
93     *        Name of package
94     *
95     * @param  cw  The code writer being used to create this package
96     *
97     * @throws IllegalArgumentException
98     *         If each part of the package name is not a valid identifier
99     */
100    JPackage(String name, JCodeModel cw) {
101        this.owner = cw;
102        if (name.equals(".")) {
103            String msg = "Package name . is not allowed";
104            throw new IllegalArgumentException(msg);
105        }
106
107        if(JCodeModel.isCaseSensitiveFileSystem)
108            upperCaseClassMap = null;
109        else
110            upperCaseClassMap = new HashMap<String,JDefinedClass>();
111
112        this.name = name;
113    }
114
115
116    public JClassContainer parentContainer() {
117        return parent();
118    }
119
120    /**
121     * Gets the parent package, or null if this class is the root package.
122     */
123    public JPackage parent() {
124        if(name.length()==0)    return null;
125
126        int idx = name.lastIndexOf('.');
127        return owner._package(name.substring(0,idx));
128    }
129
130    public boolean isClass() { return false; }
131    public boolean isPackage() { return true; }
132    public JPackage getPackage() { return this; }
133
134    /**
135     * Add a class to this package.
136     *
137     * @param mods
138     *        Modifiers for this class declaration
139     *
140     * @param name
141     *        Name of class to be added to this package
142     *
143     * @return Newly generated class
144     *
145     * @exception JClassAlreadyExistsException
146     *      When the specified class/interface was already created.
147     */
148    public JDefinedClass _class(int mods, String name) throws JClassAlreadyExistsException {
149        return _class(mods,name,ClassType.CLASS);
150    }
151
152    /**
153     * {@inheritDoc}
154     * @deprecated
155     */
156    public JDefinedClass _class( int mods, String name, boolean isInterface ) throws JClassAlreadyExistsException {
157        return _class(mods,name, isInterface?ClassType.INTERFACE:ClassType.CLASS );
158    }
159
160    public JDefinedClass _class( int mods, String name, ClassType classTypeVal ) throws JClassAlreadyExistsException {
161        if(classes.containsKey(name))
162            throw new JClassAlreadyExistsException(classes.get(name));
163        else {
164            // XXX problems caught in the NC constructor
165            JDefinedClass c = new JDefinedClass(this, mods, name, classTypeVal);
166
167            if( upperCaseClassMap!=null ) {
168                JDefinedClass dc = upperCaseClassMap.get(name.toUpperCase());
169                if(dc!=null)
170                    throw new JClassAlreadyExistsException(dc);
171                upperCaseClassMap.put(name.toUpperCase(),c);
172            }
173            classes.put(name,c);
174            return c;
175        }
176    }
177
178        /**
179         * Adds a public class to this package.
180         */
181    public JDefinedClass _class(String name) throws JClassAlreadyExistsException {
182                return _class( JMod.PUBLIC, name );
183        }
184
185    /**
186     * Gets a reference to the already created {@link JDefinedClass}.
187     *
188     * @return null
189     *      If the class is not yet created.
190     */
191    public JDefinedClass _getClass(String name) {
192        if(classes.containsKey(name))
193            return classes.get(name);
194        else
195            return null;
196    }
197
198    /**
199     * Order is based on the lexicological order of the package name.
200     */
201    public int compareTo(JPackage that) {
202        return this.name.compareTo(that.name);
203    }
204
205    /**
206     * Add an interface to this package.
207     *
208     * @param mods
209     *        Modifiers for this interface declaration
210     *
211     * @param name
212     *        Name of interface to be added to this package
213     *
214     * @return Newly generated interface
215     */
216    public JDefinedClass _interface(int mods, String name) throws JClassAlreadyExistsException {
217        return _class(mods,name,ClassType.INTERFACE);
218    }
219
220    /**
221     * Adds a public interface to this package.
222     */
223    public JDefinedClass _interface(String name) throws JClassAlreadyExistsException {
224        return _interface(JMod.PUBLIC, name);
225    }
226
227    /**
228     * Add an annotationType Declaration to this package
229     * @param name
230     *      Name of the annotation Type declaration to be added to this package
231     * @return
232     *      newly created Annotation Type Declaration
233     * @exception JClassAlreadyExistsException
234     *      When the specified class/interface was already created.
235
236     */
237    public JDefinedClass _annotationTypeDeclaration(String name) throws JClassAlreadyExistsException {
238        return _class (JMod.PUBLIC,name,ClassType.ANNOTATION_TYPE_DECL);
239    }
240
241    /**
242     * Add a public enum to this package
243     * @param name
244     *      Name of the enum to be added to this package
245     * @return
246     *      newly created Enum
247     * @exception JClassAlreadyExistsException
248     *      When the specified class/interface was already created.
249
250     */
251    public JDefinedClass _enum (String name) throws JClassAlreadyExistsException {
252        return _class (JMod.PUBLIC,name,ClassType.ENUM);
253    }
254    /**
255     * Adds a new resource file to this package.
256     */
257    public JResourceFile addResourceFile(JResourceFile rsrc) {
258        resources.add(rsrc);
259        return rsrc;
260    }
261
262    /**
263     * Checks if a resource of the given name exists.
264     */
265    public boolean hasResourceFile(String name) {
266        for (JResourceFile r : resources)
267            if (r.name().equals(name))
268                return true;
269        return false;
270    }
271
272    /**
273     * Iterates all resource files in this package.
274     */
275    public Iterator<JResourceFile> propertyFiles() {
276        return resources.iterator();
277    }
278
279    /**
280     * Creates, if necessary, and returns the package javadoc for this
281     * JDefinedClass.
282     *
283     * @return JDocComment containing javadocs for this class
284     */
285    public JDocComment javadoc() {
286        if (jdoc == null)
287            jdoc = new JDocComment(owner());
288        return jdoc;
289    }
290
291    /**
292     * Removes a class from this package.
293     */
294    public void remove(JClass c) {
295        if (c._package() != this)
296            throw new IllegalArgumentException(
297                "the specified class is not a member of this package," + " or it is a referenced class");
298
299        // note that c may not be a member of classes.
300        // this happens when someone is trying to remove a non generated class
301        classes.remove(c.name());
302        if (upperCaseClassMap != null)
303            upperCaseClassMap.remove(c.name().toUpperCase());
304    }
305
306    /**
307     * Reference a class within this package.
308     */
309    public JClass ref(String name) throws ClassNotFoundException {
310        if (name.indexOf('.') >= 0)
311            throw new IllegalArgumentException("JClass name contains '.': " + name);
312
313        String n = "";
314        if (!isUnnamed())
315            n = this.name + '.';
316        n += name;
317
318        return owner.ref(Class.forName(n));
319    }
320
321    /**
322     * Gets a reference to a sub package of this package.
323     */
324    public JPackage subPackage( String pkg ) {
325        if(isUnnamed())     return owner()._package(pkg);
326        else                return owner()._package(name+'.'+pkg);
327    }
328
329    /**
330     * Returns an iterator that walks the top-level classes defined in this
331     * package.
332     */
333    public Iterator<JDefinedClass> classes() {
334        return classes.values().iterator();
335    }
336
337    /**
338     * Checks if this package contains any classes.
339     * @return {@code true} if this package contains any classes
340     *         or {@code false} otherwise.
341     */
342    public boolean hasClasses() {
343        return !classes.isEmpty();
344    }
345
346    /**
347     * Checks if a given name is already defined as a class/interface
348     */
349    public boolean isDefined(String classLocalName) {
350        Iterator<JDefinedClass> itr = classes();
351        while (itr.hasNext()) {
352            if ((itr.next()).name().equals(classLocalName))
353                return true;
354        }
355
356        return false;
357    }
358
359    /**
360     * Checks if this package is the root, unnamed package.
361     */
362    public final boolean isUnnamed() { return name.length() == 0; }
363
364    /**
365     * Get the name of this package
366     *
367     * @return
368     *          The name of this package, or the empty string if this is the
369     *          null package. For example, this method returns strings like
370     *          <code>"java.lang"</code>
371     */
372    public String name() {
373        return name;
374    }
375
376    /**
377     * Return the code model root object being used to create this package.
378     */
379    public final JCodeModel owner() { return owner; }
380
381
382    public JAnnotationUse annotate(JClass clazz) {
383        if(isUnnamed())
384            throw new IllegalArgumentException("the root package cannot be annotated");
385        if(annotations==null)
386           annotations = new ArrayList<JAnnotationUse>();
387        JAnnotationUse a = new JAnnotationUse(clazz);
388        annotations.add(a);
389        return a;
390    }
391
392    public JAnnotationUse annotate(Class<? extends Annotation> clazz) {
393        return annotate(owner.ref(clazz));
394    }
395
396    public <W extends JAnnotationWriter> W annotate2(Class<W> clazz) {
397        return TypedAnnotationWriter.create(clazz,this);
398    }
399
400    public boolean removeAnnotation(JAnnotationUse annotation) {
401        return this.annotations.remove(annotation);
402    }
403
404    public Collection<JAnnotationUse> annotations() {
405        if (annotations == null)
406            annotations = new ArrayList<JAnnotationUse>();
407        return Collections.unmodifiableList(annotations);
408    }
409
410    /**
411     * Convert the package name to directory path equivalent
412     */
413    File toPath(File dir) {
414        if (name == null) return dir;
415        return new File(dir, name.replace('.', File.separatorChar));
416    }
417
418    public void declare(JFormatter f ) {
419        if (name.length() != 0)
420            f.p("package").p(name).p(';').nl();
421    }
422
423    public void generate(JFormatter f) {
424        f.p(name);
425    }
426
427
428    void build( CodeWriter src, CodeWriter res ) throws IOException {
429
430        // write classes
431        for (JDefinedClass c : classes.values()) {
432            if (c.isHidden())
433                continue;   // don't generate this file
434
435            JFormatter f = createJavaSourceFileWriter(src, c.name());
436            f.write(c);
437            f.close();
438        }
439
440        // write package annotations
441        if(annotations!=null || jdoc!=null) {
442            JFormatter f = createJavaSourceFileWriter(src,"package-info");
443
444            if (jdoc != null)
445                f.g(jdoc);
446
447            // TODO: think about importing
448            if (annotations != null){
449                for (JAnnotationUse a : annotations)
450                    f.g(a).nl();
451            }
452            f.d(this);
453
454            f.close();
455        }
456
457        // write resources
458        for (JResourceFile rsrc : resources) {
459            CodeWriter cw = rsrc.isResource() ? res : src;
460            OutputStream os = new BufferedOutputStream(cw.openBinary(this, rsrc.name()));
461            rsrc.build(os);
462            os.close();
463        }
464    }
465
466    /*package*/ int countArtifacts() {
467        int r = 0;
468        for (JDefinedClass c : classes.values()) {
469            if (c.isHidden())
470                continue;   // don't generate this file
471            r++;
472        }
473
474        if(annotations!=null || jdoc!=null) {
475            r++;
476        }
477
478        r+= resources.size();
479
480        return r;
481    }
482
483    private JFormatter createJavaSourceFileWriter(CodeWriter src, String className) throws IOException {
484        Writer bw = new BufferedWriter(src.openSource(this,className+".java"));
485        return new JFormatter(new PrintWriter(bw));
486    }
487}
488