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