001: /*
002: * The contents of this file are subject to the terms
003: * of the Common Development and Distribution License
004: * (the "License"). You may not use this file except
005: * in compliance with the License.
006: *
007: * You can obtain a copy of the license at
008: * https://jwsdp.dev.java.net/CDDLv1.0.html
009: * See the License for the specific language governing
010: * permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL
013: * HEADER in each file and include the License file at
014: * https://jwsdp.dev.java.net/CDDLv1.0.html If applicable,
015: * add the following below this CDDL HEADER, with the
016: * fields enclosed by brackets "[]" replaced with your
017: * own identifying information: Portions Copyright [yyyy]
018: * [name of copyright owner]
019: */
020:
021: package com.sun.codemodel;
022:
023: import java.io.PrintWriter;
024: import java.io.Writer;
025: import java.util.ArrayList;
026: import java.util.Arrays;
027: import java.util.Collection;
028: import java.util.HashMap;
029: import java.util.HashSet;
030: import java.util.Iterator;
031: import java.util.List;
032:
033: /**
034: * This is a utility class for managing indentation and other basic
035: * formatting for PrintWriter.
036: */
037: public final class JFormatter {
038: /** all classes and ids encountered during the collection mode **/
039: /** map from short type name to ReferenceList (list of JClass and ids sharing that name) **/
040: private HashMap<String, ReferenceList> collectedReferences;
041:
042: /** set of imported types (including package java types, eventhough we won't generate imports for them) */
043: private HashSet<JClass> importedClasses;
044:
045: private static enum Mode {
046: /**
047: * Collect all the type names and identifiers.
048: * In this mode we don't actually generate anything.
049: */
050: COLLECTING,
051: /**
052: * Print the actual source code.
053: */
054: PRINTING
055: }
056:
057: /**
058: * The current running mode.
059: * Set to PRINTING so that a casual client can use a formatter just like before.
060: */
061: private Mode mode = Mode.PRINTING;
062:
063: /**
064: * Current number of indentation strings to print
065: */
066: private int indentLevel;
067:
068: /**
069: * String to be used for each indentation.
070: * Defaults to four spaces.
071: */
072: private final String indentSpace;
073:
074: /**
075: * Stream associated with this JFormatter
076: */
077: private final PrintWriter pw;
078:
079: /**
080: * Creates a JFormatter.
081: *
082: * @param s
083: * PrintWriter to JFormatter to use.
084: *
085: * @param space
086: * Incremental indentation string, similar to tab value.
087: */
088: public JFormatter(PrintWriter s, String space) {
089: pw = s;
090: indentSpace = space;
091: collectedReferences = new HashMap<String, ReferenceList>();
092: //ids = new HashSet<String>();
093: importedClasses = new HashSet<JClass>();
094: }
095:
096: /**
097: * Creates a formatter with default incremental indentations of
098: * four spaces.
099: */
100: public JFormatter(PrintWriter s) {
101: this (s, " ");
102: }
103:
104: /**
105: * Creates a formatter with default incremental indentations of
106: * four spaces.
107: */
108: public JFormatter(Writer w) {
109: this (new PrintWriter(w));
110: }
111:
112: /**
113: * Closes this formatter.
114: */
115: public void close() {
116: pw.close();
117: }
118:
119: /**
120: * Returns true if we are in the printing mode,
121: * where we actually produce text.
122: *
123: * The other mode is the "collecting mode'
124: */
125: public boolean isPrinting() {
126: return mode == Mode.PRINTING;
127: }
128:
129: /**
130: * Decrement the indentation level.
131: */
132: public JFormatter o() {
133: indentLevel--;
134: return this ;
135: }
136:
137: /**
138: * Increment the indentation level.
139: */
140: public JFormatter i() {
141: indentLevel++;
142: return this ;
143: }
144:
145: private boolean needSpace(char c1, char c2) {
146: if ((c1 == ']') && (c2 == '{'))
147: return true;
148: if (c1 == ';')
149: return true;
150: if (c1 == CLOSE_TYPE_ARGS) {
151: // e.g., "public Foo<Bar> test;"
152: if (c2 == '(') // but not "new Foo<Bar>()"
153: return false;
154: return true;
155: }
156: if ((c1 == ')') && (c2 == '{'))
157: return true;
158: if ((c1 == ',') || (c1 == '='))
159: return true;
160: if (c2 == '=')
161: return true;
162: if (Character.isDigit(c1)) {
163: if ((c2 == '(') || (c2 == ')') || (c2 == ';')
164: || (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 == '(')
193: return false;
194: return true;
195: }
196: return false;
197: }
198:
199: private char lastChar = 0;
200: private boolean atBeginningOfLine = true;
201:
202: private void spaceIfNeeded(char c) {
203: if (atBeginningOfLine) {
204: for (int i = 0; i < indentLevel; i++)
205: pw.print(indentSpace);
206: atBeginningOfLine = false;
207: } else if ((lastChar != 0) && needSpace(lastChar, c))
208: pw.print(' ');
209: }
210:
211: /**
212: * Print a char into the stream
213: *
214: * @param c the char
215: */
216: public JFormatter p(char c) {
217: if (mode == Mode.PRINTING) {
218: if (c == CLOSE_TYPE_ARGS) {
219: pw.print('>');
220: } else {
221: spaceIfNeeded(c);
222: pw.print(c);
223: }
224: lastChar = c;
225: }
226: return this ;
227: }
228:
229: /**
230: * Print a String into the stream
231: *
232: * @param s the String
233: */
234: public JFormatter p(String s) {
235: if (mode == Mode.PRINTING) {
236: spaceIfNeeded(s.charAt(0));
237: pw.print(s);
238: lastChar = s.charAt(s.length() - 1);
239: }
240: return this ;
241: }
242:
243: public JFormatter t(JType type) {
244: if (type.isReference()) {
245: return t((JClass) type);
246: } else {
247: return g(type);
248: }
249: }
250:
251: /**
252: * Print a type name.
253: *
254: * <p>
255: * In the collecting mode we use this information to
256: * decide what types to import and what not to.
257: */
258: public JFormatter t(JClass type) {
259: switch (mode) {
260: case PRINTING:
261: // many of the JTypes in this list are either primitive or belong to package java
262: // so we don't need a FQCN
263: if (importedClasses.contains(type)) {
264: p(type.name()); // FQCN imported or not necessary, so generate short name
265: } else {
266: if (type.outer() != null)
267: t(type.outer()).p('.').p(type.name());
268: else
269: p(type.fullName()); // collision was detected, so generate FQCN
270: }
271: break;
272: case COLLECTING:
273: final String shortName = type.name();
274: if (collectedReferences.containsKey(shortName)) {
275: collectedReferences.get(shortName).add(type);
276: } else {
277: ReferenceList tl = new ReferenceList();
278: tl.add(type);
279: collectedReferences.put(shortName, tl);
280: }
281: break;
282: }
283: return this ;
284: }
285:
286: /**
287: * Print an identifier
288: */
289: public JFormatter id(String id) {
290: switch (mode) {
291: case PRINTING:
292: p(id);
293: break;
294: case COLLECTING:
295: // see if there is a type name that collides with this id
296: if (collectedReferences.containsKey(id)) {
297: if (!collectedReferences.get(id).getClasses().isEmpty()) {
298: for (JClass type : collectedReferences.get(id)
299: .getClasses()) {
300: if (type.outer() != null) {
301: collectedReferences.get(id).setId(false);
302: return this ;
303: }
304: }
305: }
306: collectedReferences.get(id).setId(true);
307: } else {
308: // not a type, but we need to create a place holder to
309: // see if there might be a collision with a type
310: ReferenceList tl = new ReferenceList();
311: tl.setId(true);
312: collectedReferences.put(id, tl);
313: }
314: break;
315: }
316: return this ;
317: }
318:
319: /**
320: * Print a new line into the stream
321: */
322: public JFormatter nl() {
323: if (mode == Mode.PRINTING) {
324: pw.println();
325: lastChar = 0;
326: atBeginningOfLine = true;
327: }
328: return this ;
329: }
330:
331: /**
332: * Cause the JGenerable object to generate source for iteself
333: *
334: * @param g the JGenerable object
335: */
336: public JFormatter g(JGenerable g) {
337: g.generate(this );
338: return this ;
339: }
340:
341: /**
342: * Produces {@link JGenerable}s separated by ','
343: */
344: public JFormatter g(Collection<? extends JGenerable> list) {
345: boolean first = true;
346: if (!list.isEmpty()) {
347: for (JGenerable item : list) {
348: if (!first)
349: p(',');
350: g(item);
351: first = false;
352: }
353: }
354: return this ;
355: }
356:
357: /**
358: * Cause the JDeclaration to generate source for itself
359: *
360: * @param d the JDeclaration object
361: */
362: public JFormatter d(JDeclaration d) {
363: d.declare(this );
364: return this ;
365: }
366:
367: /**
368: * Cause the JStatement to generate source for itself
369: *
370: * @param s the JStatement object
371: */
372: public JFormatter s(JStatement s) {
373: s.state(this );
374: return this ;
375: }
376:
377: /**
378: * Cause the JVar to generate source for itself
379: *
380: * @param v the JVar object
381: */
382: public JFormatter b(JVar v) {
383: v.bind(this );
384: return this ;
385: }
386:
387: /**
388: * Generates the whole source code out of the specified class.
389: */
390: void write(JDefinedClass c) {
391: // first collect all the types and identifiers
392: mode = Mode.COLLECTING;
393: d(c);
394:
395: javaLang = c.owner()._package("java.lang");
396:
397: // collate type names and identifiers to determine which types can be imported
398: for (ReferenceList tl : collectedReferences.values()) {
399: if (!tl.collisions(c) && !tl.isId()) {
400: assert tl.getClasses().size() == 1;
401:
402: // add to list of collected types
403: importedClasses.add(tl.getClasses().get(0));
404: }
405: }
406:
407: // the class itself that we will be generating is always accessible
408: importedClasses.add(c);
409:
410: // then print the declaration
411: mode = Mode.PRINTING;
412:
413: assert c.parentContainer().isPackage() : "this method is only for a pacakge-level class";
414: JPackage pkg = (JPackage) c.parentContainer();
415: if (!pkg.isUnnamed()) {
416: nl().d(pkg);
417: nl();
418: }
419:
420: // generate import statements
421: JClass[] imports = importedClasses
422: .toArray(new JClass[importedClasses.size()]);
423: Arrays.sort(imports);
424: for (JClass clazz : imports) {
425: // suppress import statements for primitive types, built-in types,
426: // types in the root package, and types in
427: // the same package as the current type
428: if (!supressImport(clazz, c)) {
429: p("import").p(clazz.fullName()).p(';').nl();
430: }
431: }
432: nl();
433:
434: d(c);
435: }
436:
437: /**
438: * determine if an import statement should be supressed
439: *
440: * @param clazz JType that may or may not have an import
441: * @param c JType that is the current class being processed
442: * @return true if an import statement should be suppressed, false otherwise
443: */
444: private boolean supressImport(JClass clazz, JClass c) {
445: if (clazz._package().isUnnamed())
446: return true;
447:
448: final String packageName = clazz._package().name();
449: if (packageName.equals("java.lang"))
450: return true; // no need to explicitly import java.lang classes
451:
452: if (clazz._package() == c._package()) {
453: // inner classes require an import stmt.
454: // All other pkg local classes do not need an
455: // import stmt for ref.
456: if (clazz.outer() == null) {
457: return true; // no need to explicitly import a class into itself
458: }
459: }
460: return false;
461: }
462:
463: private JPackage javaLang;
464:
465: /**
466: * Special character token we use to differenciate '>' as an operator and
467: * '>' as the end of the type arguments. The former uses '>' and it requires
468: * a preceding whitespace. The latter uses this, and it does not have a preceding
469: * whitespace.
470: */
471: /*package*/static final char CLOSE_TYPE_ARGS = '\uFFFF';
472:
473: /**
474: * Used during the optimization of class imports.
475: *
476: * List of {@link JClass}es whose short name is the same.
477: *
478: * @author Ryan.Shoemaker@Sun.COM
479: */
480: final class ReferenceList {
481: private final ArrayList<JClass> classes = new ArrayList<JClass>();
482:
483: /** true if this name is used as an identifier (like a variable name.) **/
484: private boolean id;
485:
486: /**
487: * Returns true if the symbol represented by the short name
488: * is "importable".
489: */
490: public boolean collisions(JDefinedClass enclosingClass) {
491: // special case where a generated type collides with a type in package java
492:
493: // more than one type with the same name
494: if (classes.size() > 1)
495: return true;
496:
497: // an id and (at least one) type with the same name
498: if (id && classes.size() != 0)
499: return true;
500:
501: for (JClass c : classes) {
502: if (c._package() == javaLang) {
503: // make sure that there's no other class with this name within the same package
504: Iterator itr = enclosingClass._package().classes();
505: while (itr.hasNext()) {
506: // even if this is the only "String" class we use,
507: // if the class called "String" is in the same package,
508: // we still need to import it.
509: JDefinedClass n = (JDefinedClass) itr.next();
510: if (n.name().equals(c.name()))
511: return true; //collision
512: }
513: }
514: if (c.outer() != null)
515: return true; // avoid importing inner class to work around 6431987. Also see jaxb issue 166
516: }
517:
518: return false;
519: }
520:
521: public void add(JClass clazz) {
522: if (!classes.contains(clazz))
523: classes.add(clazz);
524: }
525:
526: public List<JClass> getClasses() {
527: return classes;
528: }
529:
530: public void setId(boolean value) {
531: id = value;
532: }
533:
534: /**
535: * Return true iff this is strictly an id, meaning that there
536: * are no collisions with type names.
537: */
538: public boolean isId() {
539: return id && classes.size() == 0;
540: }
541: }
542: }
|