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 '<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 }