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