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.PrintWriter; 29 import java.io.Writer; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.Collection; 33 import java.util.HashMap; 34 import java.util.HashSet; 35 import java.util.Iterator; 36 import java.util.List; 37 38 39 /** 40 * This is a utility class for managing indentation and other basic 41 * formatting for PrintWriter. 42 */ 43 public final class JFormatter { 44 /** all classes and ids encountered during the collection mode **/ 45 /** map from short type name to ReferenceList (list of JClass and ids sharing that name) **/ 46 private HashMap<String,ReferenceList> collectedReferences; 47 48 /** set of imported types (including package java types, eventhough we won't generate imports for them) */ 49 private HashSet<JClass> importedClasses; 50 51 private static enum Mode { 52 /** 53 * Collect all the type names and identifiers. 54 * In this mode we don't actually generate anything. 55 */ 56 COLLECTING, 57 /** 58 * Print the actual source code. 59 */ 60 PRINTING 61 } 62 63 /** 64 * The current running mode. 65 * Set to PRINTING so that a casual client can use a formatter just like before. 66 */ 67 private Mode mode = Mode.PRINTING; 68 69 /** 70 * Current number of indentation strings to print 71 */ 72 private int indentLevel; 73 74 /** 75 * String to be used for each indentation. 76 * Defaults to four spaces. 77 */ 78 private final String indentSpace; 79 80 /** 81 * Stream associated with this JFormatter 82 */ 83 private final PrintWriter pw; 84 85 /** 86 * Creates a JFormatter. 87 * 88 * @param s 89 * PrintWriter to JFormatter to use. 90 * 91 * @param space 92 * Incremental indentation string, similar to tab value. 93 */ 94 public JFormatter(PrintWriter s, String space) { 95 pw = s; 96 indentSpace = space; 97 collectedReferences = new HashMap<String,ReferenceList>(); 98 //ids = new HashSet<String>(); 99 importedClasses = new HashSet<JClass>(); 100 } 101 102 /** 103 * Creates a formatter with default incremental indentations of 104 * four spaces. 105 */ 106 public JFormatter(PrintWriter s) { 107 this(s, " "); 108 } 109 110 /** 111 * Creates a formatter with default incremental indentations of 112 * four spaces. 113 */ 114 public JFormatter(Writer w) { 115 this(new PrintWriter(w)); 116 } 117 118 /** 119 * Closes this formatter. 120 */ 121 public void close() { 122 pw.close(); 123 } 124 125 /** 126 * Returns true if we are in the printing mode, 127 * where we actually produce text. 128 * 129 * The other mode is the "collecting mode' 130 */ 131 public boolean isPrinting() { 132 return mode == Mode.PRINTING; 133 } 134 135 /** 136 * Decrement the indentation level. 137 */ 138 public JFormatter o() { 139 indentLevel--; 140 return this; 141 } 142 143 /** 144 * Increment the indentation level. 145 */ 146 public JFormatter i() { 147 indentLevel++; 148 return this; 149 } 150 151 private boolean needSpace(char c1, char c2) { 152 if ((c1 == ']') && (c2 == '{')) return true; 153 if (c1 == ';') return true; 154 if (c1 == CLOSE_TYPE_ARGS) { 155 // e.g., "public Foo<Bar> test;" 156 if(c2=='(') // but not "new Foo<Bar>()" 157 return false; 158 return true; 159 } 160 if ((c1 == ')') && (c2 == '{')) return true; 161 if ((c1 == ',') || (c1 == '=')) return true; 162 if (c2 == '=') return true; 163 if (Character.isDigit(c1)) { 164 if ((c2 == '(') || (c2 == ')') || (c2 == ';') || (c2 == ',')) 165 return false; 166 return true; 167 } 168 if (Character.isJavaIdentifierPart(c1)) { 169 switch (c2) { 170 case '{': 171 case '}': 172 case '+': 173 case '>': 174 case '@': 175 return true; 176 default: 177 return Character.isJavaIdentifierStart(c2); 178 } 179 } 180 if (Character.isJavaIdentifierStart(c2)) { 181 switch (c1) { 182 case ']': 183 case ')': 184 case '}': 185 case '+': 186 return true; 187 default: 188 return false; 189 } 190 } 191 if (Character.isDigit(c2)) { 192 if (c1 == '(') return false; 193 return true; 194 } 195 return false; 196 } 197 198 private char lastChar = 0; 199 private boolean atBeginningOfLine = true; 200 201 private void spaceIfNeeded(char c) { 202 if (atBeginningOfLine) { 203 for (int i = 0; i < indentLevel; i++) 204 pw.print(indentSpace); 205 atBeginningOfLine = false; 206 } else if ((lastChar != 0) && needSpace(lastChar, c)) 207 pw.print(' '); 208 } 209 210 /** 211 * Print a char into the stream 212 * 213 * @param c the char 214 */ 215 public JFormatter p(char c) { 216 if(mode==Mode.PRINTING) { 217 if(c==CLOSE_TYPE_ARGS) { 218 pw.print('>'); 219 } else { 220 spaceIfNeeded(c); 221 pw.print(c); 222 } 223 lastChar = c; 224 } 225 return this; 226 } 227 228 /** 229 * Print a String into the stream 230 * 231 * @param s the String 232 */ 233 public JFormatter p(String s) { 234 if(mode==Mode.PRINTING) { 235 spaceIfNeeded(s.charAt(0)); 236 pw.print(s); 237 lastChar = s.charAt(s.length() - 1); 238 } 239 return this; 240 } 241 242 public JFormatter t(JType type) { 243 if(type.isReference()) { 244 return t((JClass)type); 245 } else { 246 return g(type); 247 } 248 } 249 250 /** 251 * Print a type name. 252 * 253 * <p> 254 * In the collecting mode we use this information to 255 * decide what types to import and what not to. 256 */ 257 public JFormatter t(JClass type) { 258 switch(mode) { 259 case PRINTING: 260 // many of the JTypes in this list are either primitive or belong to package java 261 // so we don't need a FQCN 262 if(importedClasses.contains(type)) { 263 p(type.name()); // FQCN imported or not necessary, so generate short name 264 } else { 265 if(type.outer()!=null) 266 t(type.outer()).p('.').p(type.name()); 267 else 268 p(type.fullName()); // collision was detected, so generate FQCN 269 } 270 break; 271 case COLLECTING: 272 final String shortName = type.name(); 273 if(collectedReferences.containsKey(shortName)) { 274 collectedReferences.get(shortName).add(type); 275 } else { 276 ReferenceList tl = new ReferenceList(); 277 tl.add(type); 278 collectedReferences.put(shortName, tl); 279 } 280 break; 281 } 282 return this; 283 } 284 285 /** 286 * Print an identifier 287 */ 288 public JFormatter id(String id) { 289 switch(mode) { 290 case PRINTING: 291 p(id); 292 break; 293 case COLLECTING: 294 // see if there is a type name that collides with this id 295 if(collectedReferences.containsKey(id)) { 296 if( !collectedReferences.get(id).getClasses().isEmpty() ) { 297 for( JClass type : collectedReferences.get(id).getClasses() ) { 298 if (type.outer()!=null) { 299 collectedReferences.get(id).setId(false); 300 return this; 301 } 302 } 303 } 304 collectedReferences.get(id).setId(true); 305 } else { 306 // not a type, but we need to create a place holder to 307 // see if there might be a collision with a type 308 ReferenceList tl = new ReferenceList(); 309 tl.setId(true); 310 collectedReferences.put(id, tl); 311 } 312 break; 313 } 314 return this; 315 } 316 317 /** 318 * Print a new line into the stream 319 */ 320 public JFormatter nl() { 321 if(mode==Mode.PRINTING) { 322 pw.println(); 323 lastChar = 0; 324 atBeginningOfLine = true; 325 } 326 return this; 327 } 328 329 /** 330 * Cause the JGenerable object to generate source for iteself 331 * 332 * @param g the JGenerable object 333 */ 334 public JFormatter g(JGenerable g) { 335 g.generate(this); 336 return this; 337 } 338 339 /** 340 * Produces {@link JGenerable}s separated by ',' 341 */ 342 public JFormatter g(Collection<? extends JGenerable> list) { 343 boolean first = true; 344 if(!list.isEmpty()) { 345 for (JGenerable item : list) { 346 if (!first) 347 p(','); 348 g(item); 349 first = false; 350 } 351 } 352 return this; 353 } 354 355 /** 356 * Cause the JDeclaration to generate source for itself 357 * 358 * @param d the JDeclaration object 359 */ 360 public JFormatter d(JDeclaration d) { 361 d.declare(this); 362 return this; 363 } 364 365 /** 366 * Cause the JStatement to generate source for itself 367 * 368 * @param s the JStatement object 369 */ 370 public JFormatter s(JStatement s) { 371 s.state(this); 372 return this; 373 } 374 375 /** 376 * Cause the JVar to generate source for itself 377 * 378 * @param v the JVar object 379 */ 380 public JFormatter b(JVar v) { 381 v.bind(this); 382 return this; 383 } 384 385 /** 386 * Generates the whole source code out of the specified class. 387 */ 388 void write(JDefinedClass c) { 389 // first collect all the types and identifiers 390 mode = Mode.COLLECTING; 391 d(c); 392 393 javaLang = c.owner()._package("java.lang"); 394 395 // collate type names and identifiers to determine which types can be imported 396 for( ReferenceList tl : collectedReferences.values() ) { 397 if(!tl.collisions(c) && !tl.isId()) { 398 assert tl.getClasses().size() == 1; 399 400 // add to list of collected types 401 importedClasses.add(tl.getClasses().get(0)); 402 } 403 } 404 405 // the class itself that we will be generating is always accessible 406 importedClasses.add(c); 407 408 // then print the declaration 409 mode = Mode.PRINTING; 410 411 assert c.parentContainer().isPackage() : "this method is only for a pacakge-level class"; 412 JPackage pkg = (JPackage) c.parentContainer(); 413 if (!pkg.isUnnamed()) { 414 nl().d(pkg); 415 nl(); 416 } 417 418 // generate import statements 419 JClass[] imports = importedClasses.toArray(new JClass[importedClasses.size()]); 420 Arrays.sort(imports); 421 for (JClass clazz : imports) { 422 // suppress import statements for primitive types, built-in types, 423 // types in the root package, and types in 424 // the same package as the current type 425 if(!supressImport(clazz, c)) { 426 if (clazz instanceof JNarrowedClass) { 427 clazz = clazz.erasure(); 428 } 429 430 p("import").p(clazz.fullName()).p(';').nl(); 431 } 432 } 433 nl(); 434 435 d(c); 436 } 437 438 /** 439 * determine if an import statement should be supressed 440 * 441 * @param clazz JType that may or may not have an import 442 * @param c JType that is the current class being processed 443 * @return true if an import statement should be suppressed, false otherwise 444 */ 445 private boolean supressImport(JClass clazz, JClass c) { 446 if (clazz instanceof JNarrowedClass) { 447 clazz = clazz.erasure(); 448 } 449 if (clazz instanceof JAnonymousClass) { 450 clazz = clazz._extends(); 451 } 452 453 if(clazz._package().isUnnamed()) 454 return true; 455 456 final String packageName = clazz._package().name(); 457 if(packageName.equals("java.lang")) 458 return true; // no need to explicitly import java.lang classes 459 460 if (clazz._package() == c._package()){ 461 // inner classes require an import stmt. 462 // All other pkg local classes do not need an 463 // import stmt for ref. 464 if(clazz.outer()==null) { 465 return true; // no need to explicitly import a class into itself 466 } 467 } 468 return false; 469 } 470 471 private JPackage javaLang; 472 473 474 475 /** 476 * Special character token we use to differenciate '>' as an operator and 477 * '>' as the end of the type arguments. The former uses '>' and it requires 478 * a preceding whitespace. The latter uses this, and it does not have a preceding 479 * whitespace. 480 */ 481 /*package*/ static final char CLOSE_TYPE_ARGS = '\uFFFF'; 482 483 /** 484 * Used during the optimization of class imports. 485 * 486 * List of {@link JClass}es whose short name is the same. 487 * 488 * @author Ryan.Shoemaker@Sun.COM 489 */ 490 final class ReferenceList { 491 private final ArrayList<JClass> classes = new ArrayList<JClass>(); 492 493 /** true if this name is used as an identifier (like a variable name.) **/ 494 private boolean id; 495 496 /** 497 * Returns true if the symbol represented by the short name 498 * is "importable". 499 */ 500 public boolean collisions(JDefinedClass enclosingClass) { 501 // special case where a generated type collides with a type in package java 502 503 // more than one type with the same name 504 if(classes.size() > 1) 505 return true; 506 507 // an id and (at least one) type with the same name 508 if(id && classes.size() != 0) 509 return true; 510 511 for(JClass c : classes) { 512 if (c instanceof JAnonymousClass) { 513 c = c._extends(); 514 } 515 if(c._package()==javaLang) { 516 // make sure that there's no other class with this name within the same package 517 Iterator<JDefinedClass> itr = enclosingClass._package().classes(); 518 while(itr.hasNext()) { 519 // even if this is the only "String" class we use, 520 // if the class called "String" is in the same package, 521 // we still need to import it. 522 JDefinedClass n = itr.next(); 523 if(n.name().equals(c.name())) 524 return true; //collision 525 } 526 } 527 if(c.outer()!=null) 528 return true; // avoid importing inner class to work around 6431987. Also see jaxb issue 166 529 } 530 531 return false; 532 } 533 534 public void add(JClass clazz) { 535 if(!classes.contains(clazz)) 536 classes.add(clazz); 537 } 538 539 public List<JClass> getClasses() { 540 return classes; 541 } 542 543 public void setId(boolean value) { 544 id = value; 545 } 546 547 /** 548 * Return true iff this is strictly an id, meaning that there 549 * are no collisions with type names. 550 */ 551 public boolean isId() { 552 return id && classes.size() == 0; 553 } 554 } 555 }