1 /*
   2  * Copyright (c) 1997, 2012, 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 
  26 package com.sun.codemodel.internal;
  27 
  28 import java.io.BufferedOutputStream;
  29 import java.io.BufferedWriter;
  30 import java.io.File;
  31 import java.io.IOException;
  32 import java.io.OutputStream;
  33 import java.io.PrintWriter;
  34 import java.io.Writer;
  35 import java.lang.annotation.Annotation;
  36 import java.util.ArrayList;
  37 import java.util.HashMap;
  38 import java.util.HashSet;
  39 import java.util.Iterator;
  40 import java.util.List;
  41 import java.util.Map;
  42 import java.util.Set;
  43 import java.util.TreeMap;
  44 import java.util.Collection;
  45 import java.util.Collections;
  46 
  47 
  48 /**
  49  * A Java package.
  50  */
  51 public 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 a given name is already defined as a class/interface
 339      */
 340     public boolean isDefined(String classLocalName) {
 341         Iterator<JDefinedClass> itr = classes();
 342         while (itr.hasNext()) {
 343             if ((itr.next()).name().equals(classLocalName))
 344                 return true;
 345         }
 346 
 347         return false;
 348     }
 349 
 350     /**
 351      * Checks if this package is the root, unnamed package.
 352      */
 353     public final boolean isUnnamed() { return name.length() == 0; }
 354 
 355     /**
 356      * Get the name of this package
 357      *
 358      * @return
 359      *          The name of this package, or the empty string if this is the
 360      *          null package. For example, this method returns strings like
 361      *          <code>"java.lang"</code>
 362      */
 363     public String name() {
 364         return name;
 365     }
 366 
 367     /**
 368      * Return the code model root object being used to create this package.
 369      */
 370     public final JCodeModel owner() { return owner; }
 371 
 372 
 373     public JAnnotationUse annotate(JClass clazz) {
 374         if(isUnnamed())
 375             throw new IllegalArgumentException("the root package cannot be annotated");
 376         if(annotations==null)
 377            annotations = new ArrayList<JAnnotationUse>();
 378         JAnnotationUse a = new JAnnotationUse(clazz);
 379         annotations.add(a);
 380         return a;
 381     }
 382 
 383     public JAnnotationUse annotate(Class<? extends Annotation> clazz) {
 384         return annotate(owner.ref(clazz));
 385     }
 386 
 387     public <W extends JAnnotationWriter> W annotate2(Class<W> clazz) {
 388         return TypedAnnotationWriter.create(clazz,this);
 389     }
 390 
 391     public Collection<JAnnotationUse> annotations() {
 392         if (annotations == null)
 393             annotations = new ArrayList<JAnnotationUse>();
 394         return Collections.unmodifiableList(annotations);
 395     }
 396 
 397     /**
 398      * Convert the package name to directory path equivalent
 399      */
 400     File toPath(File dir) {
 401         if (name == null) return dir;
 402         return new File(dir, name.replace('.', File.separatorChar));
 403     }
 404 
 405     public void declare(JFormatter f ) {
 406         if (name.length() != 0)
 407             f.p("package").p(name).p(';').nl();
 408     }
 409 
 410     public void generate(JFormatter f) {
 411         f.p(name);
 412     }
 413 
 414 
 415     void build( CodeWriter src, CodeWriter res ) throws IOException {
 416 
 417         // write classes
 418         for (JDefinedClass c : classes.values()) {
 419             if (c.isHidden())
 420                 continue;   // don't generate this file
 421 
 422             JFormatter f = createJavaSourceFileWriter(src, c.name());
 423             f.write(c);
 424             f.close();
 425         }
 426 
 427         // write package annotations
 428         if(annotations!=null || jdoc!=null) {
 429             JFormatter f = createJavaSourceFileWriter(src,"package-info");
 430 
 431             if (jdoc != null)
 432                 f.g(jdoc);
 433 
 434             // TODO: think about importing
 435             if (annotations != null){
 436                 for (JAnnotationUse a : annotations)
 437                     f.g(a).nl();
 438             }
 439             f.d(this);
 440 
 441             f.close();
 442         }
 443 
 444         // write resources
 445         for (JResourceFile rsrc : resources) {
 446             CodeWriter cw = rsrc.isResource() ? res : src;
 447             OutputStream os = new BufferedOutputStream(cw.openBinary(this, rsrc.name()));
 448             rsrc.build(os);
 449             os.close();
 450         }
 451     }
 452 
 453     /*package*/ int countArtifacts() {
 454         int r = 0;
 455         for (JDefinedClass c : classes.values()) {
 456             if (c.isHidden())
 457                 continue;   // don't generate this file
 458             r++;
 459         }
 460 
 461         if(annotations!=null || jdoc!=null) {
 462             r++;
 463         }
 464 
 465         r+= resources.size();
 466 
 467         return r;
 468     }
 469 
 470     private JFormatter createJavaSourceFileWriter(CodeWriter src, String className) throws IOException {
 471         Writer bw = new BufferedWriter(src.openSource(this,className+".java"));
 472         return new JFormatter(new PrintWriter(bw));
 473     }
 474 }