1 /*
   2  * Copyright (c) 1997, 2010, 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.File;
  29 import java.io.IOException;
  30 import java.io.PrintStream;
  31 import java.lang.reflect.Modifier;
  32 import java.util.ArrayList;
  33 import java.util.Collections;
  34 import java.util.HashMap;
  35 import java.util.Iterator;
  36 import java.util.List;
  37 import java.util.Map;
  38 
  39 import com.sun.codemodel.internal.writer.FileCodeWriter;
  40 import com.sun.codemodel.internal.writer.ProgressCodeWriter;
  41 
  42 
  43 /**
  44  * Root of the code DOM.
  45  *
  46  * <p>
  47  * Here's your typical CodeModel application.
  48  *
  49  * <pre>
  50  * JCodeModel cm = new JCodeModel();
  51  *
  52  * // generate source code by populating the 'cm' tree.
  53  * cm._class(...);
  54  * ...
  55  *
  56  * // write them out
  57  * cm.build(new File("."));
  58  * </pre>
  59  *
  60  * <p>
  61  * Every CodeModel node is always owned by one {@link JCodeModel} object
  62  * at any given time (which can be often accesesd by the <tt>owner()</tt> method.)
  63  *
  64  * As such, when you generate Java code, most of the operation works
  65  * in a top-down fashion. For example, you create a class from {@link JCodeModel},
  66  * which gives you a {@link JDefinedClass}. Then you invoke a method on it
  67  * to generate a new method, which gives you {@link JMethod}, and so on.
  68  *
  69  * There are a few exceptions to this, most notably building {@link JExpression}s,
  70  * but generally you work with CodeModel in a top-down fashion.
  71  *
  72  * Because of this design, most of the CodeModel classes aren't directly instanciable.
  73  *
  74  *
  75  * <h2>Where to go from here?</h2>
  76  * <p>
  77  * Most of the time you'd want to populate new type definitions in a {@link JCodeModel}.
  78  * See {@link #_class(String, ClassType)}.
  79  */
  80 public final class JCodeModel {
  81 
  82     /** The packages that this JCodeWriter contains. */
  83     private HashMap<String,JPackage> packages = new HashMap<String,JPackage>();
  84 
  85     /** All JReferencedClasses are pooled here. */
  86     private final HashMap<Class<?>,JReferencedClass> refClasses = new HashMap<Class<?>,JReferencedClass>();
  87 
  88 
  89     /** Obtains a reference to the special "null" type. */
  90     public final JNullType NULL = new JNullType(this);
  91     // primitive types
  92     public final JPrimitiveType VOID    = new JPrimitiveType(this,"void",   Void.class);
  93     public final JPrimitiveType BOOLEAN = new JPrimitiveType(this,"boolean",Boolean.class);
  94     public final JPrimitiveType BYTE    = new JPrimitiveType(this,"byte",   Byte.class);
  95     public final JPrimitiveType SHORT   = new JPrimitiveType(this,"short",  Short.class);
  96     public final JPrimitiveType CHAR    = new JPrimitiveType(this,"char",   Character.class);
  97     public final JPrimitiveType INT     = new JPrimitiveType(this,"int",    Integer.class);
  98     public final JPrimitiveType FLOAT   = new JPrimitiveType(this,"float",  Float.class);
  99     public final JPrimitiveType LONG    = new JPrimitiveType(this,"long",   Long.class);
 100     public final JPrimitiveType DOUBLE  = new JPrimitiveType(this,"double", Double.class);
 101 
 102     /**
 103      * If the flag is true, we will consider two classes "Foo" and "foo"
 104      * as a collision.
 105      */
 106     protected static final boolean isCaseSensitiveFileSystem = getFileSystemCaseSensitivity();
 107 
 108     private static boolean getFileSystemCaseSensitivity() {
 109         try {
 110             // let the system property override, in case the user really
 111             // wants to override.
 112             if( System.getProperty("com.sun.codemodel.internal.FileSystemCaseSensitive")!=null )
 113                 return true;
 114         } catch( Exception e ) {}
 115 
 116         // on Unix, it's case sensitive.
 117         return (File.separatorChar == '/');
 118     }
 119 
 120 
 121     public JCodeModel() {}
 122 
 123     /**
 124      * Add a package to the list of packages to be generated
 125      *
 126      * @param name
 127      *        Name of the package. Use "" to indicate the root package.
 128      *
 129      * @return Newly generated package
 130      */
 131     public JPackage _package(String name) {
 132         JPackage p = packages.get(name);
 133         if (p == null) {
 134             p = new JPackage(name, this);
 135             packages.put(name, p);
 136         }
 137         return p;
 138     }
 139 
 140     public final JPackage rootPackage() {
 141         return _package("");
 142     }
 143 
 144     /**
 145      * Returns an iterator that walks the packages defined using this code
 146      * writer.
 147      */
 148     public Iterator<JPackage> packages() {
 149         return packages.values().iterator();
 150     }
 151 
 152     /**
 153      * Creates a new generated class.
 154      *
 155      * @exception JClassAlreadyExistsException
 156      *      When the specified class/interface was already created.
 157      */
 158     public JDefinedClass _class(String fullyqualifiedName) throws JClassAlreadyExistsException {
 159         return _class(fullyqualifiedName,ClassType.CLASS);
 160     }
 161 
 162     /**
 163      * Creates a dummy, unknown {@link JClass} that represents a given name.
 164      *
 165      * <p>
 166      * This method is useful when the code generation needs to include the user-specified
 167      * class that may or may not exist, and only thing known about it is a class name.
 168      */
 169     public JClass directClass(String name) {
 170         return new JDirectClass(this,name);
 171     }
 172 
 173     /**
 174      * Creates a new generated class.
 175      *
 176      * @exception JClassAlreadyExistsException
 177      *      When the specified class/interface was already created.
 178      */
 179     public JDefinedClass _class(int mods, String fullyqualifiedName,ClassType t) throws JClassAlreadyExistsException {
 180         int idx = fullyqualifiedName.lastIndexOf('.');
 181         if( idx<0 )     return rootPackage()._class(fullyqualifiedName);
 182         else
 183             return _package(fullyqualifiedName.substring(0,idx))
 184                 ._class(mods, fullyqualifiedName.substring(idx+1), t );
 185     }
 186 
 187     /**
 188      * Creates a new generated class.
 189      *
 190      * @exception JClassAlreadyExistsException
 191      *      When the specified class/interface was already created.
 192      */
 193     public JDefinedClass _class(String fullyqualifiedName,ClassType t) throws JClassAlreadyExistsException {
 194         return _class( JMod.PUBLIC, fullyqualifiedName, t );
 195     }
 196 
 197     /**
 198      * Gets a reference to the already created generated class.
 199      *
 200      * @return null
 201      *      If the class is not yet created.
 202      * @see JPackage#_getClass(String)
 203      */
 204     public JDefinedClass _getClass(String fullyQualifiedName) {
 205         int idx = fullyQualifiedName.lastIndexOf('.');
 206         if( idx<0 )     return rootPackage()._getClass(fullyQualifiedName);
 207         else
 208             return _package(fullyQualifiedName.substring(0,idx))
 209                 ._getClass( fullyQualifiedName.substring(idx+1) );
 210     }
 211 
 212     /**
 213      * Creates a new anonymous class.
 214      *
 215      * @deprecated
 216      *      The naming convention doesn't match the rest of the CodeModel.
 217      *      Use {@link #anonymousClass(JClass)} instead.
 218      */
 219     public JDefinedClass newAnonymousClass(JClass baseType) {
 220         return new JAnonymousClass(baseType);
 221     }
 222 
 223     /**
 224      * Creates a new anonymous class.
 225      */
 226     public JDefinedClass anonymousClass(JClass baseType) {
 227         return new JAnonymousClass(baseType);
 228     }
 229 
 230     public JDefinedClass anonymousClass(Class<?> baseType) {
 231         return anonymousClass(ref(baseType));
 232     }
 233 
 234     /**
 235      * Generates Java source code.
 236      * A convenience method for <code>build(destDir,destDir,System.out)</code>.
 237      *
 238      * @param   destDir
 239      *          source files are generated into this directory.
 240      * @param   status
 241      *      if non-null, progress indication will be sent to this stream.
 242      */
 243     public void build( File destDir, PrintStream status ) throws IOException {
 244         build(destDir,destDir,status);
 245     }
 246 
 247     /**
 248      * Generates Java source code.
 249      * A convenience method that calls {@link #build(CodeWriter,CodeWriter)}.
 250      *
 251      * @param   srcDir
 252      *          Java source files are generated into this directory.
 253      * @param   resourceDir
 254      *          Other resource files are generated into this directory.
 255      * @param   status
 256      *      if non-null, progress indication will be sent to this stream.
 257      */
 258     public void build( File srcDir, File resourceDir, PrintStream status ) throws IOException {
 259         CodeWriter src = new FileCodeWriter(srcDir);
 260         CodeWriter res = new FileCodeWriter(resourceDir);
 261         if(status!=null) {
 262             src = new ProgressCodeWriter(src, status );
 263             res = new ProgressCodeWriter(res, status );
 264         }
 265         build(src,res);
 266     }
 267 
 268     /**
 269      * A convenience method for <code>build(destDir,System.out)</code>.
 270      */
 271     public void build( File destDir ) throws IOException {
 272         build(destDir,System.out);
 273     }
 274 
 275     /**
 276      * A convenience method for <code>build(srcDir,resourceDir,System.out)</code>.
 277      */
 278     public void build( File srcDir, File resourceDir ) throws IOException {
 279         build(srcDir,resourceDir,System.out);
 280     }
 281 
 282     /**
 283      * A convenience method for <code>build(out,out)</code>.
 284      */
 285     public void build( CodeWriter out ) throws IOException {
 286         build(out,out);
 287     }
 288 
 289     /**
 290      * Generates Java source code.
 291      */
 292     public void build( CodeWriter source, CodeWriter resource ) throws IOException {
 293         JPackage[] pkgs = packages.values().toArray(new JPackage[packages.size()]);
 294         // avoid concurrent modification exception
 295         for( JPackage pkg : pkgs )
 296             pkg.build(source,resource);
 297         source.close();
 298         resource.close();
 299     }
 300 
 301     /**
 302      * Returns the number of files to be generated if
 303      * {@link #build} is invoked now.
 304      */
 305     public int countArtifacts() {
 306         int r = 0;
 307         JPackage[] pkgs = packages.values().toArray(new JPackage[packages.size()]);
 308         // avoid concurrent modification exception
 309         for( JPackage pkg : pkgs )
 310             r += pkg.countArtifacts();
 311         return r;
 312     }
 313 
 314 
 315     /**
 316      * Obtains a reference to an existing class from its Class object.
 317      *
 318      * <p>
 319      * The parameter may not be primitive.
 320      *
 321      * @see #_ref(Class) for the version that handles more cases.
 322      */
 323     public JClass ref(Class<?> clazz) {
 324         JReferencedClass jrc = (JReferencedClass)refClasses.get(clazz);
 325         if (jrc == null) {
 326             if (clazz.isPrimitive())
 327                 throw new IllegalArgumentException(clazz+" is a primitive");
 328             if (clazz.isArray()) {
 329                 return new JArrayClass(this, _ref(clazz.getComponentType()));
 330             } else {
 331                 jrc = new JReferencedClass(clazz);
 332                 refClasses.put(clazz, jrc);
 333             }
 334         }
 335         return jrc;
 336     }
 337 
 338     public JType _ref(Class<?> c) {
 339         if(c.isPrimitive())
 340             return JType.parse(this,c.getName());
 341         else
 342             return ref(c);
 343     }
 344 
 345     /**
 346      * Obtains a reference to an existing class from its fully-qualified
 347      * class name.
 348      *
 349      * <p>
 350      * First, this method attempts to load the class of the given name.
 351      * If that fails, we assume that the class is derived straight from
 352      * {@link Object}, and return a {@link JClass}.
 353      */
 354     public JClass ref(String fullyQualifiedClassName) {
 355         try {
 356             // try the context class loader first
 357             return ref(SecureLoader.getContextClassLoader().loadClass(fullyQualifiedClassName));
 358         } catch (ClassNotFoundException e) {
 359             // fall through
 360         }
 361         // then the default mechanism.
 362         try {
 363             return ref(Class.forName(fullyQualifiedClassName));
 364         } catch (ClassNotFoundException e1) {
 365             // fall through
 366         }
 367 
 368         // assume it's not visible to us.
 369         return new JDirectClass(this,fullyQualifiedClassName);
 370     }
 371 
 372     /**
 373      * Cached for {@link #wildcard()}.
 374      */
 375     private JClass wildcard;
 376 
 377     /**
 378      * Gets a {@link JClass} representation for "?",
 379      * which is equivalent to "? extends Object".
 380      */
 381     public JClass wildcard() {
 382         if(wildcard==null)
 383             wildcard = ref(Object.class).wildcard();
 384         return wildcard;
 385     }
 386 
 387     /**
 388      * Obtains a type object from a type name.
 389      *
 390      * <p>
 391      * This method handles primitive types, arrays, and existing {@link Class}es.
 392      *
 393      * @exception ClassNotFoundException
 394      *      If the specified type is not found.
 395      */
 396     public JType parseType(String name) throws ClassNotFoundException {
 397         // array
 398         if(name.endsWith("[]"))
 399             return parseType(name.substring(0,name.length()-2)).array();
 400 
 401         // try primitive type
 402         try {
 403             return JType.parse(this,name);
 404         } catch (IllegalArgumentException e) {
 405             ;
 406         }
 407 
 408         // existing class
 409         return new TypeNameParser(name).parseTypeName();
 410     }
 411 
 412     private final class TypeNameParser {
 413         private final String s;
 414         private int idx;
 415 
 416         public TypeNameParser(String s) {
 417             this.s = s;
 418         }
 419 
 420         /**
 421          * Parses a type name token T (which can be potentially of the form Tr&ly;T1,T2,...>,
 422          * or "? extends/super T".)
 423          *
 424          * @return the index of the character next to T.
 425          */
 426         JClass parseTypeName() throws ClassNotFoundException {
 427             int start = idx;
 428 
 429             if(s.charAt(idx)=='?') {
 430                 // wildcard
 431                 idx++;
 432                 ws();
 433                 String head = s.substring(idx);
 434                 if(head.startsWith("extends")) {
 435                     idx+=7;
 436                     ws();
 437                     return parseTypeName().wildcard();
 438                 } else
 439                 if(head.startsWith("super")) {
 440                     throw new UnsupportedOperationException("? super T not implemented");
 441                 } else {
 442                     // not supported
 443                     throw new IllegalArgumentException("only extends/super can follow ?, but found "+s.substring(idx));
 444                 }
 445             }
 446 
 447             while(idx<s.length()) {
 448                 char ch = s.charAt(idx);
 449                 if(Character.isJavaIdentifierStart(ch)
 450                 || Character.isJavaIdentifierPart(ch)
 451                 || ch=='.')
 452                     idx++;
 453                 else
 454                     break;
 455             }
 456 
 457             JClass clazz = ref(s.substring(start,idx));
 458 
 459             return parseSuffix(clazz);
 460         }
 461 
 462         /**
 463          * Parses additional left-associative suffixes, like type arguments
 464          * and array specifiers.
 465          */
 466         private JClass parseSuffix(JClass clazz) throws ClassNotFoundException {
 467             if(idx==s.length())
 468                 return clazz; // hit EOL
 469 
 470             char ch = s.charAt(idx);
 471 
 472             if(ch=='<')
 473                 return parseSuffix(parseArguments(clazz));
 474 
 475             if(ch=='[') {
 476                 if(s.charAt(idx+1)==']') {
 477                     idx+=2;
 478                     return parseSuffix(clazz.array());
 479                 }
 480                 throw new IllegalArgumentException("Expected ']' but found "+s.substring(idx+1));
 481             }
 482 
 483             return clazz;
 484         }
 485 
 486         /**
 487          * Skips whitespaces
 488          */
 489         private void ws() {
 490             while(Character.isWhitespace(s.charAt(idx)) && idx<s.length())
 491                 idx++;
 492         }
 493 
 494         /**
 495          * Parses '&lt;T1,T2,...,Tn>'
 496          *
 497          * @return the index of the character next to '>'
 498          */
 499         private JClass parseArguments(JClass rawType) throws ClassNotFoundException {
 500             if(s.charAt(idx)!='<')
 501                 throw new IllegalArgumentException();
 502             idx++;
 503 
 504             List<JClass> args = new ArrayList<JClass>();
 505 
 506             while(true) {
 507                 args.add(parseTypeName());
 508                 if(idx==s.length())
 509                     throw new IllegalArgumentException("Missing '>' in "+s);
 510                 char ch = s.charAt(idx);
 511                 if(ch=='>')
 512                     return rawType.narrow(args.toArray(new JClass[args.size()]));
 513 
 514                 if(ch!=',')
 515                     throw new IllegalArgumentException(s);
 516                 idx++;
 517             }
 518 
 519         }
 520     }
 521 
 522     /**
 523      * References to existing classes.
 524      *
 525      * <p>
 526      * JReferencedClass is kept in a pool so that they are shared.
 527      * There is one pool for each JCodeModel object.
 528      *
 529      * <p>
 530      * It is impossible to cache JReferencedClass globally only because
 531      * there is the _package() method, which obtains the owner JPackage
 532      * object, which is scoped to JCodeModel.
 533      */
 534     private class JReferencedClass extends JClass implements JDeclaration {
 535         private final Class<?> _class;
 536 
 537         JReferencedClass(Class<?> _clazz) {
 538             super(JCodeModel.this);
 539             this._class = _clazz;
 540             assert !_class.isArray();
 541         }
 542 
 543         public String name() {
 544             return _class.getSimpleName().replace('$','.');
 545         }
 546 
 547         public String fullName() {
 548             return _class.getName().replace('$','.');
 549         }
 550 
 551         public String binaryName() {
 552             return _class.getName();
 553         }
 554 
 555         public JClass outer() {
 556             Class<?> p = _class.getDeclaringClass();
 557             if(p==null)     return null;
 558             return ref(p);
 559         }
 560 
 561         public JPackage _package() {
 562             String name = fullName();
 563 
 564             // this type is array
 565             if (name.indexOf('[') != -1)
 566                 return JCodeModel.this._package("");
 567 
 568             // other normal case
 569             int idx = name.lastIndexOf('.');
 570             if (idx < 0)
 571                 return JCodeModel.this._package("");
 572             else
 573                 return JCodeModel.this._package(name.substring(0, idx));
 574         }
 575 
 576         public JClass _extends() {
 577             Class<?> sp = _class.getSuperclass();
 578             if (sp == null) {
 579                 if(isInterface())
 580                     return owner().ref(Object.class);
 581                 return null;
 582             } else
 583                 return ref(sp);
 584         }
 585 
 586         public Iterator<JClass> _implements() {
 587             final Class<?>[] interfaces = _class.getInterfaces();
 588             return new Iterator<JClass>() {
 589                 private int idx = 0;
 590                 public boolean hasNext() {
 591                     return idx < interfaces.length;
 592                 }
 593                 public JClass next() {
 594                     return JCodeModel.this.ref(interfaces[idx++]);
 595                 }
 596                 public void remove() {
 597                     throw new UnsupportedOperationException();
 598                 }
 599             };
 600         }
 601 
 602         public boolean isInterface() {
 603             return _class.isInterface();
 604         }
 605 
 606         public boolean isAbstract() {
 607             return Modifier.isAbstract(_class.getModifiers());
 608         }
 609 
 610         public JPrimitiveType getPrimitiveType() {
 611             Class<?> v = boxToPrimitive.get(_class);
 612             if(v!=null)
 613                 return JType.parse(JCodeModel.this,v.getName());
 614             else
 615                 return null;
 616         }
 617 
 618         public boolean isArray() {
 619             return false;
 620         }
 621 
 622         public void declare(JFormatter f) {
 623         }
 624 
 625         public JTypeVar[] typeParams() {
 626             // TODO: does JDK 1.5 reflection provides these information?
 627             return super.typeParams();
 628         }
 629 
 630         protected JClass substituteParams(JTypeVar[] variables, List<JClass> bindings) {
 631             // TODO: does JDK 1.5 reflection provides these information?
 632             return this;
 633         }
 634     }
 635 
 636     /**
 637      * Conversion from primitive type {@link Class} (such as {@link Integer#TYPE}
 638      * to its boxed type (such as <tt>Integer.class</tt>)
 639      */
 640     public static final Map<Class<?>,Class<?>> primitiveToBox;
 641     /**
 642      * The reverse look up for {@link #primitiveToBox}
 643      */
 644     public static final Map<Class<?>,Class<?>> boxToPrimitive;
 645 
 646     static {
 647         Map<Class<?>,Class<?>> m1 = new HashMap<Class<?>,Class<?>>();
 648         Map<Class<?>,Class<?>> m2 = new HashMap<Class<?>,Class<?>>();
 649 
 650         m1.put(Boolean.class,Boolean.TYPE);
 651         m1.put(Byte.class,Byte.TYPE);
 652         m1.put(Character.class,Character.TYPE);
 653         m1.put(Double.class,Double.TYPE);
 654         m1.put(Float.class,Float.TYPE);
 655         m1.put(Integer.class,Integer.TYPE);
 656         m1.put(Long.class,Long.TYPE);
 657         m1.put(Short.class,Short.TYPE);
 658         m1.put(Void.class,Void.TYPE);
 659 
 660         for (Map.Entry<Class<?>, Class<?>> e : m1.entrySet())
 661             m2.put(e.getValue(),e.getKey());
 662 
 663         boxToPrimitive = Collections.unmodifiableMap(m1);
 664         primitiveToBox = Collections.unmodifiableMap(m2);
 665 
 666     }
 667 }