View Javadoc
1   /**
2    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3    *
4    * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
5    * Portions Copyright 2013-2017 Philip Helger + contributors
6    *
7    * The contents of this file are subject to the terms of either the GNU
8    * General Public License Version 2 only ("GPL") or the Common Development
9    * and Distribution License("CDDL") (collectively, the "License").  You
10   * may not use this file except in compliance with the License.  You can
11   * obtain a copy of the License at
12   * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
13   * or packager/legal/LICENSE.txt.  See the License for the specific
14   * language governing permissions and limitations under the License.
15   *
16   * When distributing the software, include this License Header Notice in each
17   * file and include the License file at packager/legal/LICENSE.txt.
18   *
19   * GPL Classpath Exception:
20   * Oracle designates this particular file as subject to the "Classpath"
21   * exception as provided by Oracle in the GPL Version 2 section of the License
22   * file that accompanied this code.
23   *
24   * Modifications:
25   * If applicable, add the following below the License Header, with the fields
26   * enclosed by brackets [] replaced by your own identifying information:
27   * "Portions Copyright [year] [name of copyright owner]"
28   *
29   * Contributor(s):
30   * If you wish your version of this file to be governed by only the CDDL or
31   * only the GPL Version 2, indicate your decision by adding "[Contributor]
32   * elects to include this software in this distribution under the [CDDL or GPL
33   * Version 2] license."  If you don't indicate a single choice of license, a
34   * recipient has the option to distribute your version of this file under
35   * either the CDDL, the GPL Version 2 or to extend the choice of license to
36   * its licensees as provided above.  However, if you add GPL Version 2 code
37   * and therefore, elected the GPL Version 2 license, then the option applies
38   * only if the new code is made subject to such option by the copyright
39   * holder.
40   */
41  package com.helger.jcodemodel;
42  
43  import java.io.Closeable;
44  import java.io.PrintWriter;
45  import java.io.Writer;
46  import java.util.ArrayList;
47  import java.util.Collection;
48  import java.util.Collections;
49  import java.util.HashMap;
50  import java.util.HashSet;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.Set;
54  
55  import javax.annotation.Nonnull;
56  import javax.annotation.Nullable;
57  import javax.annotation.concurrent.NotThreadSafe;
58  
59  import com.helger.jcodemodel.util.ClassNameComparator;
60  import com.helger.jcodemodel.util.JCValueEnforcer;
61  import com.helger.jcodemodel.util.NullWriter;
62  
63  /**
64   * This is a utility class for managing indentation and other basic formatting
65   * for PrintWriter.
66   */
67  @NotThreadSafe
68  public class JFormatter implements Closeable
69  {
70    /**
71     * Used during the optimization of class imports. List of
72     * {@link AbstractJClass}es whose short name is the same.
73     *
74     * @author Ryan.Shoemaker@Sun.COM
75     */
76    private final class NameUsage
77    {
78      private final String m_sName;
79  
80      private final List <AbstractJClass> m_aReferencedClasses = new ArrayList <> ();
81  
82      /** true if this name is used as an identifier (like a variable name.) **/
83      private boolean m_bIsVariableName;
84  
85      public NameUsage (@Nonnull final String sName)
86      {
87        m_sName = sName;
88      }
89  
90      /**
91       * @return <code>true</code> if the short name is ambiguous in context of
92       *         enclosingClass and classes with this name can't be imported.
93       */
94      public boolean isAmbiguousIn (@Nonnull final JDefinedClass aEnclosingClass)
95      {
96        // more than one type with the same name
97        if (m_aReferencedClasses.size () > 1)
98          return true;
99  
100       // an id and (at least one) type with the same name
101       if (m_bIsVariableName && !m_aReferencedClasses.isEmpty ())
102         return true;
103 
104       // no references is always unambiguous
105       if (m_aReferencedClasses.isEmpty ())
106         return false;
107 
108       // we have exactly one reference
109       AbstractJClass aSingleRef = m_aReferencedClasses.get (0);
110       if (aSingleRef instanceof JAnonymousClass)
111       {
112         aSingleRef = ((JAnonymousClass) aSingleRef).base ();
113       }
114 
115       // special case where a generated type collides with a type in package
116       // java.lang
117       if (aSingleRef._package () == JFormatter.this.m_aPckJavaLang)
118       {
119         // make sure that there's no other class with this name within the
120         // same package
121         for (final JDefinedClass aClass : aEnclosingClass._package ().classes ())
122         {
123           // even if this is the only "String" class we use,
124           // if the class called "String" is in the same package,
125           // we still need to import it.
126           if (aClass.name ().equals (aSingleRef.name ()))
127           {
128             // collision -> ambiguous
129             return true;
130           }
131         }
132       }
133 
134       return false;
135     }
136 
137     public boolean addReferencedType (@Nonnull final AbstractJClass aClazz)
138     {
139       if (false)
140         System.out.println ("Adding referenced type[" + m_sName + "]: " + aClazz.fullName ());
141       if (m_aReferencedClasses.contains (aClazz))
142         return false;
143       return m_aReferencedClasses.add (aClazz);
144     }
145 
146     public boolean containsReferencedType (@Nullable final AbstractJClass aClazz)
147     {
148       return m_aReferencedClasses.contains (aClazz);
149     }
150 
151     @Nonnull
152     public AbstractJClass getSingleReferencedType ()
153     {
154       assert m_aReferencedClasses.size () == 1;
155       return m_aReferencedClasses.get (0);
156     }
157 
158     @Nonnull
159     public List <AbstractJClass> getReferencedTypes ()
160     {
161       return m_aReferencedClasses;
162     }
163 
164     public void setVariableName ()
165     {
166       // Check if something can be a variable or a type
167       for (final AbstractJClass aRefedType : m_aReferencedClasses)
168       {
169         if (aRefedType.outer () != null)
170         {
171           m_bIsVariableName = false;
172           return;
173         }
174       }
175       m_bIsVariableName = true;
176     }
177 
178     /**
179      * @return true if this name is used as an identifier (like a variable
180      *         name.).
181      */
182     public boolean isVariableName ()
183     {
184       return m_bIsVariableName;
185     }
186 
187     /**
188      * @return true if this name is used as an type name (like class name.)
189      */
190     public boolean isTypeName ()
191     {
192       return !m_aReferencedClasses.isEmpty ();
193     }
194 
195     @Override
196     public String toString ()
197     {
198       final StringBuilder aSB = new StringBuilder ("Usages[").append (m_sName).append ("]");
199       aSB.append ("; isVarName=").append (m_bIsVariableName);
200       aSB.append ("; refedClasses=").append (m_aReferencedClasses);
201       return aSB.toString ();
202     }
203   }
204 
205   private static enum EMode
206   {
207     /**
208      * Collect all the type names and identifiers. In this mode we don't
209      * actually generate anything.
210      */
211     COLLECTING,
212     /**
213      * Print the actual source code.
214      */
215     PRINTING,
216 
217     /**
218      * Find any error types in output code. In this mode we don't actually
219      * generate anything. <br/>
220      * Only used by {@link JFormatter#containsErrorTypes(JDefinedClass)
221      * containsErrorTypes} method
222      */
223     FIND_ERROR_TYPES
224   }
225 
226   private final class ImportedClasses
227   {
228     private final Set <AbstractJClass> m_aDontImportClasses = new HashSet <> ();
229     private final Set <AbstractJClass> m_aClasses = new HashSet <> ();
230     private final Set <String> m_aNames = new HashSet <> ();
231 
232     public ImportedClasses ()
233     {}
234 
235     @Nullable
236     private AbstractJClass _getClassForImport (@Nullable final AbstractJClass aClass)
237     {
238       AbstractJClass aRealClass = aClass;
239       if (aRealClass instanceof JAnonymousClass)
240       {
241         // get the super class of the anonymous class
242         return _getClassForImport (((JAnonymousClass) aRealClass).base ());
243       }
244       if (aRealClass instanceof JNarrowedClass)
245       {
246         // Never imported narrowed class but the erasure only
247         aRealClass = aRealClass.erasure ();
248       }
249       return aRealClass;
250     }
251 
252     public void addDontImportClass (@Nonnull final AbstractJClass aClass)
253     {
254       final AbstractJClass aRealClass = _getClassForImport (aClass);
255       m_aDontImportClasses.add (aRealClass);
256     }
257 
258     public boolean add (@Nonnull final AbstractJClass aClass)
259     {
260       final AbstractJClass aRealClass = _getClassForImport (aClass);
261 
262       if (m_aDontImportClasses.contains (aRealClass))
263       {
264         if (m_bDebugImport)
265           System.out.println ("The class '" + aRealClass.fullName () + "' should not be imported!");
266         return false;
267       }
268 
269       // Avoid importing 2 classes with the same class name
270       if (!m_aNames.add (aRealClass.name ()))
271       {
272         if (m_bDebugImport)
273           System.out.println ("A class with local name '" + aRealClass.name () + "' is already in the import list.");
274         return false;
275       }
276 
277       if (!m_aClasses.add (aRealClass))
278       {
279         if (m_bDebugImport)
280           System.out.println ("The class '" + aRealClass.fullName () + "' is already in the import list.");
281         return false;
282       }
283 
284       if (m_bDebugImport)
285         System.out.println ("Added import class '" + aClass.fullName () + "'");
286       return true;
287     }
288 
289     public boolean contains (@Nullable final AbstractJClass aClass)
290     {
291       final AbstractJClass aRealClass = _getClassForImport (aClass);
292 
293       return m_aClasses.contains (aRealClass);
294     }
295 
296     public void clear ()
297     {
298       m_aClasses.clear ();
299       m_aNames.clear ();
300     }
301 
302     @Nonnull
303     public List <AbstractJClass> getAllSorted ()
304     {
305       // Copy and sort
306       final List <AbstractJClass> aImports = new ArrayList <> (m_aClasses);
307       Collections.sort (aImports, ClassNameComparator.getInstance ());
308       return aImports;
309     }
310   }
311 
312   public static final String DEFAULT_INDENT_SPACE = "    ";
313 
314   /**
315    * Special character token we use to differentiate '&gt;' as an operator and
316    * '&gt;' as the end of the type arguments. The former uses '&gt;' and it
317    * requires a preceding whitespace. The latter uses this, and it does not have
318    * a preceding whitespace.
319    */
320   /* package */static final char CLOSE_TYPE_ARGS = '\uFFFF';
321 
322   /**
323    * all classes and ids encountered during the collection mode.<br>
324    * map from short type name to {@link NameUsage} (list of
325    * {@link AbstractJClass} and ids sharing that name)
326    **/
327   private final Map <String, NameUsage> m_aCollectedReferences = new HashMap <> ();
328 
329   /**
330    * set of imported types (including package java types, even though we won't
331    * generate imports for them)
332    */
333   private final ImportedClasses m_aImportedClasses = new ImportedClasses ();
334 
335   /**
336    * The current running mode. Set to PRINTING so that a casual client can use a
337    * formatter just like before.
338    */
339   private EMode m_eMode = EMode.PRINTING;
340 
341   /**
342    * Current number of indentation strings to print
343    */
344   private int m_nIndentLevel;
345 
346   /**
347    * String to be used for each indentation. Defaults to four spaces.
348    */
349   private final String m_sIndentSpace;
350 
351   /**
352    * Writer associated with this {@link JFormatter}
353    */
354   private final SourcePrintWriter m_aPW;
355 
356   private char m_cLastChar = 0;
357   private boolean m_bAtBeginningOfLine = true;
358   private JPackage m_aPckJavaLang;
359 
360   /**
361    * Only used by {@link JFormatter#containsErrorTypes(JDefinedClass)
362    * containsErrorTypes} method
363    */
364   private boolean m_bContainsErrorTypes;
365 
366   private boolean m_bDebugImport = false;
367 
368   /**
369    * Creates a formatter with default incremental indentations of four spaces.
370    *
371    * @param aPW
372    *        The {@link PrintWriter} to use
373    */
374   public JFormatter (@Nonnull final SourcePrintWriter aPW)
375   {
376     this (aPW, DEFAULT_INDENT_SPACE);
377   }
378 
379   /**
380    * Creates a JFormatter.
381    *
382    * @param aPW
383    *        {@link PrintWriter} to {@link JFormatter} to use. May not be
384    *        <code>null</code>.
385    * @param sIndentSpace
386    *        Incremental indentation string, similar to tab value. May not be
387    *        <code>null</code>.
388    */
389   public JFormatter (@Nonnull final SourcePrintWriter aPW, @Nonnull final String sIndentSpace)
390   {
391     JCValueEnforcer.notNull (aPW, "PrintWriter");
392     JCValueEnforcer.notNull (sIndentSpace, "IndentSpace");
393 
394     m_aPW = aPW;
395     m_sIndentSpace = sIndentSpace;
396   }
397 
398   /**
399    * Creates a formatter with default incremental indentations of four spaces.
400    *
401    * @param aWriter
402    *        The {@link Writer} to be wrapped in a {@link PrintWriter}
403    */
404   public JFormatter (@Nonnull final Writer aWriter)
405   {
406     this (aWriter, DEFAULT_INDENT_SPACE);
407   }
408 
409   /**
410    * Creates a formatter with default incremental indentations of four spaces.
411    *
412    * @param aWriter
413    *        The {@link Writer} to be wrapped in a {@link PrintWriter}
414    * @param sIndentSpace
415    *        Incremental indentation string, similar to tab value. May not be
416    *        <code>null</code>.
417    */
418   public JFormatter (@Nonnull final Writer aWriter, @Nonnull final String sIndentSpace)
419   {
420     this (aWriter, sIndentSpace, System.getProperty ("line.separator"));
421   }
422 
423   /**
424    * Creates a formatter with default incremental indentations of four spaces.
425    *
426    * @param aWriter
427    *        The {@link Writer} to be wrapped in a {@link PrintWriter}
428    * @param sIndentSpace
429    *        Incremental indentation string, similar to tab value. May not be
430    *        <code>null</code>.
431    * @param sNewLine
432    *        The new line string to be used. May neither be <code>null</code> nor
433    *        empty.
434    */
435   public JFormatter (@Nonnull final Writer aWriter, @Nonnull final String sIndentSpace, @Nonnull final String sNewLine)
436   {
437     this (aWriter instanceof SourcePrintWriter ? (SourcePrintWriter) aWriter
438                                                : new SourcePrintWriter (aWriter, sNewLine),
439           sIndentSpace);
440   }
441 
442   public void setDebugImports (final boolean bDebug)
443   {
444     m_bDebugImport = bDebug;
445   }
446 
447   public boolean isDebugImports ()
448   {
449     return m_bDebugImport;
450   }
451 
452   /**
453    * Closes this formatter.
454    */
455   public void close ()
456   {
457     m_aPW.close ();
458   }
459 
460   /**
461    * @return <code>true</code> if we are in the printing mode, where we actually
462    *         produce text. The other mode is the "collecting mode'
463    */
464   public boolean isPrinting ()
465   {
466     return m_eMode == EMode.PRINTING;
467   }
468 
469   /**
470    * Decrement the indentation level.
471    *
472    * @return this for chaining
473    */
474   @Nonnull
475   public JFormatter outdent ()
476   {
477     m_nIndentLevel--;
478     return this;
479   }
480 
481   /**
482    * Increment the indentation level.
483    *
484    * @return this for chaining
485    */
486   @Nonnull
487   public JFormatter indent ()
488   {
489     m_nIndentLevel++;
490     return this;
491   }
492 
493   private static boolean _needSpace (final char c1, final char c2)
494   {
495     if ((c1 == ']') && (c2 == '{'))
496       return true;
497     if (c1 == ';')
498       return true;
499     if (c1 == CLOSE_TYPE_ARGS)
500     {
501       // e.g., "public Foo<Bar> test;"
502       if (c2 == '(')
503       {
504         // but not "new Foo<Bar>()"
505         return false;
506       }
507       return true;
508     }
509     if ((c1 == ')') && (c2 == '{'))
510       return true;
511     if ((c1 == ',') || (c1 == '='))
512       return true;
513     if (c2 == '=')
514       return true;
515     if (Character.isDigit (c1))
516     {
517       if ((c2 == '(') || (c2 == ')') || (c2 == ';') || (c2 == ','))
518         return false;
519       return true;
520     }
521     if (Character.isJavaIdentifierPart (c1))
522     {
523       switch (c2)
524       {
525         case '{':
526         case '}':
527         case '+':
528         case '-':
529         case '>':
530         case '@':
531           return true;
532         default:
533           return Character.isJavaIdentifierStart (c2);
534       }
535     }
536     if (Character.isJavaIdentifierStart (c2))
537     {
538       switch (c1)
539       {
540         case ']':
541         case ')':
542         case '}':
543         case '+':
544           return true;
545         default:
546           return false;
547       }
548     }
549     if (Character.isDigit (c2))
550     {
551       if (c1 == '(')
552         return false;
553       return true;
554     }
555     return false;
556   }
557 
558   private void _spaceIfNeeded (final char c)
559   {
560     if (m_bAtBeginningOfLine)
561     {
562       for (int i = 0; i < m_nIndentLevel; i++)
563         m_aPW.print (m_sIndentSpace);
564       m_bAtBeginningOfLine = false;
565     }
566     else
567       if (m_cLastChar != 0 && _needSpace (m_cLastChar, c))
568         m_aPW.print (' ');
569   }
570 
571   /**
572    * Print a char into the stream
573    *
574    * @param c
575    *        the char
576    * @return this for chaining
577    */
578   @Nonnull
579   public JFormatter print (final char c)
580   {
581     if (m_eMode == EMode.PRINTING)
582     {
583       if (c == CLOSE_TYPE_ARGS)
584       {
585         m_aPW.print ('>');
586       }
587       else
588       {
589         _spaceIfNeeded (c);
590         m_aPW.print (c);
591       }
592       m_cLastChar = c;
593     }
594     return this;
595   }
596 
597   /**
598    * Print a String into the stream. Indentation happens automatically.
599    *
600    * @param sStr
601    *        the String
602    * @return this
603    */
604   @Nonnull
605   public JFormatter print (@Nonnull final String sStr)
606   {
607     if (m_eMode == EMode.PRINTING && sStr.length () > 0)
608     {
609       _spaceIfNeeded (sStr.charAt (0));
610       m_aPW.print (sStr);
611       m_cLastChar = sStr.charAt (sStr.length () - 1);
612     }
613     return this;
614   }
615 
616   @Nonnull
617   public JFormatter type (@Nonnull final AbstractJType aType)
618   {
619     if (aType.isReference ())
620       return type ((AbstractJClass) aType);
621     return generable (aType);
622   }
623 
624   /**
625    * Print a type name.
626    * <p>
627    * In the collecting mode we use this information to decide what types to
628    * import and what not to.
629    *
630    * @param aType
631    *        Type to be emitted
632    * @return this for chaining
633    */
634   @Nonnull
635   public JFormatter type (@Nonnull final AbstractJClass aType)
636   {
637     switch (m_eMode)
638     {
639       case COLLECTING:
640         if (!aType.isError ())
641         {
642           final String sShortName = aType.name ();
643           NameUsage aUsages = m_aCollectedReferences.get (sShortName);
644           if (aUsages == null)
645           {
646             aUsages = new NameUsage (sShortName);
647             m_aCollectedReferences.put (sShortName, aUsages);
648           }
649           aUsages.addReferencedType (aType);
650         }
651         break;
652       case PRINTING:
653         if (aType.isError ())
654         {
655           print ("Object");
656         }
657         else
658           // many of the JTypes in this list are either primitive or belong to
659           // package java so we don't need a FQCN
660           if (m_aImportedClasses.contains (aType) || aType._package () == m_aPckJavaLang)
661           {
662             // FQCN imported or not necessary, so generate short name
663             print (aType.name ());
664           }
665           else
666           {
667             final AbstractJClass aOuter = aType.outer ();
668             if (aOuter != null)
669             {
670               type (aOuter).print ('.').print (aType.name ());
671             }
672             else
673             {
674               // collision was detected, so generate FQCN
675               print (aType.fullName ());
676             }
677           }
678         break;
679       case FIND_ERROR_TYPES:
680         if (aType.isError ())
681           m_bContainsErrorTypes = true;
682         break;
683     }
684     return this;
685   }
686 
687   /**
688    * Print an identifier
689    *
690    * @param sID
691    *        identifier
692    * @return this for chaining
693    */
694   @Nonnull
695   public JFormatter id (@Nonnull final String sID)
696   {
697     switch (m_eMode)
698     {
699       case COLLECTING:
700         // see if there is a type name that collides with this id
701         NameUsage aUsages = m_aCollectedReferences.get (sID);
702         if (aUsages == null)
703         {
704           // not a type, but we need to create a place holder to
705           // see if there might be a collision with a type
706           aUsages = new NameUsage (sID);
707           m_aCollectedReferences.put (sID, aUsages);
708         }
709         aUsages.setVariableName ();
710         break;
711       case PRINTING:
712         print (sID);
713         break;
714     }
715     return this;
716   }
717 
718   /**
719    * Print a new line into the stream
720    *
721    * @return this for chaining
722    */
723   @Nonnull
724   public JFormatter newline ()
725   {
726     if (m_eMode == EMode.PRINTING)
727     {
728       m_aPW.println ();
729       m_cLastChar = 0;
730       m_bAtBeginningOfLine = true;
731     }
732     return this;
733   }
734 
735   /**
736    * Cause the JGenerable object to generate source for iteself
737    *
738    * @param g
739    *        the JGenerable object
740    * @return this for chaining
741    */
742   @Nonnull
743   public JFormatter generable (@Nonnull final IJGenerable g)
744   {
745     g.generate (this);
746     return this;
747   }
748 
749   /**
750    * Produces {@link IJGenerable}s separated by ','
751    *
752    * @param list
753    *        List of {@link IJGenerable} objects that will be separated by a
754    *        comma
755    * @return this for chaining
756    */
757   @Nonnull
758   public JFormatter generable (@Nonnull final Collection <? extends IJGenerable> list)
759   {
760     if (!list.isEmpty ())
761     {
762       boolean bFirst = true;
763       for (final IJGenerable item : list)
764       {
765         if (!bFirst)
766           print (',');
767         generable (item);
768         bFirst = false;
769       }
770     }
771     return this;
772   }
773 
774   /**
775    * Cause the JDeclaration to generate source for itself
776    *
777    * @param d
778    *        the JDeclaration object
779    * @return this for chaining
780    */
781   @Nonnull
782   public JFormatter declaration (@Nonnull final IJDeclaration d)
783   {
784     d.declare (this);
785     return this;
786   }
787 
788   /**
789    * Cause the JStatement to generate source for itself
790    *
791    * @param aStmt
792    *        the JStatement object
793    * @return this for chaining
794    */
795   @Nonnull
796   public JFormatter statement (@Nonnull final IJStatement aStmt)
797   {
798     aStmt.state (this);
799     return this;
800   }
801 
802   /**
803    * Cause the {@link JVar} to generate source for itself. With annotations,
804    * type, name and init expression.
805    *
806    * @param aVar
807    *        the {@link JVar} object
808    * @return this for chaining
809    */
810   @Nonnull
811   public JFormatter var (@Nonnull final JVar aVar)
812   {
813     aVar.bind (this);
814     return this;
815   }
816 
817   private boolean _collectCausesNoAmbiguities (@Nonnull final AbstractJClass aReference,
818                                                @Nonnull final JDefinedClass aClassToBeWritten)
819   {
820     if (m_bDebugImport)
821       System.out.println ("_collectCausesNoAmbiguities(" +
822                           aReference.fullName () +
823                           ", " +
824                           aClassToBeWritten.fullName () +
825                           ")");
826 
827     final NameUsage aUsages = m_aCollectedReferences.get (aReference.name ());
828     if (aUsages == null)
829       return true;
830     return !aUsages.isAmbiguousIn (aClassToBeWritten) && aUsages.containsReferencedType (aReference);
831   }
832 
833   /**
834    * determine if an import statement should be used for given class. This is a
835    * matter of style and convention
836    *
837    * @param aReference
838    *        {@link AbstractJClass} referenced class
839    * @param aClassToBeWritten
840    *        {@link AbstractJClass} currently generated class
841    * @return <code>true</code> if an import statement can be used to shorten
842    *         references to referenced class
843    */
844   private boolean _collectShouldBeImported (@Nonnull final AbstractJClass aReference,
845                                             @Nonnull final JDefinedClass aClassToBeWritten)
846   {
847     if (m_bDebugImport)
848       System.out.println ("_collectShouldBeImported(" +
849                           aReference.fullName () +
850                           ", " +
851                           aClassToBeWritten.fullName () +
852                           ")");
853 
854     AbstractJClass aRealReference = aReference;
855     if (aRealReference instanceof JAnonymousClass)
856     {
857       // get the super class of the anonymous class
858       aRealReference = ((JAnonymousClass) aRealReference).base ();
859     }
860     if (aRealReference instanceof JNarrowedClass)
861     {
862       // Remove the generic arguments
863       aRealReference = aRealReference.erasure ();
864     }
865 
866     // Is it an inner class?
867     final AbstractJClass aOuter = aRealReference.outer ();
868     if (aOuter != null)
869     {
870       // Import inner class only when it's name contain a name of enclosing
871       // class.
872       // In such case no information is lost when we refer to inner class
873       // without mentioning it's enclosing class
874       if (aRealReference.name ().contains (aOuter.name ()))
875       {
876         // Recurse
877         if (_collectShouldBeImported (aOuter, aClassToBeWritten))
878           return true;
879       }
880 
881       // Do not import inner classes in all other cases to aid
882       // understandability/readability.
883       return false;
884     }
885     return true;
886   }
887 
888   /**
889    * If reference is inner-class adds some outer class to the list of imported
890    * classes if it
891    *
892    * @param aClassToBeWritten
893    *        {@link AbstractJClass} that may or may not have an import
894    * @param aGeneratingClass
895    *        {@link AbstractJClass} that is the current class being processed
896    * @return true if an import statement should be suppressed, false otherwise
897    */
898   private void _collectImportOuterClassIfCausesNoAmbiguities (@Nonnull final AbstractJClass aReference,
899                                                               @Nonnull final JDefinedClass aClassToBeWritten)
900   {
901     if (m_bDebugImport)
902       System.out.println ("_collectImportOuterClassIfCausesNoAmbiguities(" +
903                           aReference.fullName () +
904                           ", " +
905                           aClassToBeWritten.fullName () +
906                           ")");
907 
908     final AbstractJClass aOuter = aReference.outer ();
909     if (aOuter != null)
910     {
911       if (_collectCausesNoAmbiguities (aOuter, aClassToBeWritten) &&
912           _collectShouldBeImported (aOuter, aClassToBeWritten))
913       {
914         m_aImportedClasses.add (aOuter);
915       }
916       else
917       {
918         // Recursive call
919         _collectImportOuterClassIfCausesNoAmbiguities (aOuter, aClassToBeWritten);
920       }
921     }
922   }
923 
924   /**
925    * determine if an import statement should be suppressed
926    *
927    * @param aReference
928    *        {@link AbstractJClass} that may or may not have an import
929    * @param aGeneratingClass
930    *        {@link AbstractJClass} that is the current class being processed
931    * @return true if an import statement should be suppressed, false otherwise
932    */
933   private boolean _printIsImplicitlyImported (@Nonnull final AbstractJClass aReference,
934                                               @Nonnull final AbstractJClass aClassToBeWrittem)
935   {
936     if (m_bDebugImport)
937       System.out.println ("_printIsImplicitlyImported(" +
938                           aReference.fullName () +
939                           ", " +
940                           aClassToBeWrittem.fullName () +
941                           ")");
942 
943     AbstractJClass aRealReference = aReference;
944     if (aRealReference instanceof JAnonymousClass)
945     {
946       // Get the super class of the anonymous class
947       aRealReference = ((JAnonymousClass) aRealReference).base ();
948     }
949     if (aRealReference instanceof JNarrowedClass)
950     {
951       // Remove generic type arguments
952       aRealReference = aRealReference.erasure ();
953     }
954 
955     final JPackage aPackage = aRealReference._package ();
956     if (aPackage == null)
957     {
958       // May be null for JTypeVar and JTypeWildcard
959       return true;
960     }
961 
962     if (aPackage.isUnnamed ())
963     {
964       // Root package - no need to import something
965       return true;
966     }
967 
968     if (aPackage == m_aPckJavaLang)
969     {
970       // no need to explicitly import java.lang classes
971       return true;
972     }
973 
974     // All pkg local classes do not need an
975     // import stmt for ref, except for inner classes
976     if (aPackage == aClassToBeWrittem._package ())
977     {
978       AbstractJClass aOuter = aRealReference.outer ();
979       if (aOuter == null) // top-level class
980       {
981         // top-level package-local class needs no explicit import
982         return true;
983       }
984 
985       // inner-class
986       AbstractJClass aTopLevelClass = aOuter;
987       aOuter = aTopLevelClass.outer ();
988       while (aOuter != null)
989       {
990         aTopLevelClass = aOuter;
991         aOuter = aTopLevelClass.outer ();
992       }
993 
994       // if reference is inner-class and
995       // reference's top-level class is generated clazz,
996       // i. e. reference is enclosed in generated clazz,
997       // then it needs no explicit import statement.
998       return aTopLevelClass == aClassToBeWrittem;
999     }
1000     return false;
1001   }
1002 
1003   /**
1004    * Generates the whole source code out of the specified class.
1005    *
1006    * @param aClassToBeWritten
1007    *        Class to be written
1008    */
1009   void write (@Nonnull final JDefinedClass aClassToBeWritten)
1010   {
1011     m_aPckJavaLang = aClassToBeWritten.owner ()._package ("java.lang");
1012 
1013     // first collect all the types and identifiers
1014     m_eMode = EMode.COLLECTING;
1015     m_aCollectedReferences.clear ();
1016     m_aImportedClasses.clear ();
1017     declaration (aClassToBeWritten);
1018 
1019     if (m_bDebugImport)
1020       System.out.println ("***Start collecting***");
1021 
1022     // the class itself that we will be generating is always accessible and must
1023     // be the first import
1024     m_aImportedClasses.add (aClassToBeWritten);
1025 
1026     // collate type names and identifiers to determine which types can be
1027     // imported
1028     for (final NameUsage aUsage : m_aCollectedReferences.values ())
1029     {
1030       if (!aUsage.isAmbiguousIn (aClassToBeWritten) && !aUsage.isVariableName ())
1031       {
1032         final AbstractJClass aReferencedClass = aUsage.getSingleReferencedType ();
1033 
1034         if (_collectShouldBeImported (aReferencedClass, aClassToBeWritten))
1035         {
1036           m_aImportedClasses.add (aReferencedClass);
1037         }
1038         else
1039         {
1040           _collectImportOuterClassIfCausesNoAmbiguities (aReferencedClass, aClassToBeWritten);
1041         }
1042       }
1043       else
1044       {
1045         if (aUsage.isTypeName ())
1046           for (final AbstractJClass reference : aUsage.getReferencedTypes ())
1047           {
1048             _collectImportOuterClassIfCausesNoAmbiguities (reference, aClassToBeWritten);
1049           }
1050       }
1051     }
1052 
1053     if (m_bDebugImport)
1054       System.out.println ("***Finished collecting***");
1055 
1056     // then print the declaration
1057     m_eMode = EMode.PRINTING;
1058 
1059     assert aClassToBeWritten.parentContainer ().isPackage () : "this method is only for a pacakge-level class";
1060 
1061     // Header before package
1062     if (aClassToBeWritten.hasHeaderComment ())
1063       generable (aClassToBeWritten.headerComment ());
1064 
1065     // Emit the package name (if not empty)
1066     final JPackage aPackage = (JPackage) aClassToBeWritten.parentContainer ();
1067     if (!aPackage.isUnnamed ())
1068     {
1069       declaration (aPackage).newline ();
1070     }
1071 
1072     // generate import statements
1073     boolean bAnyImport = false;
1074     for (final AbstractJClass aImportClass : m_aImportedClasses.getAllSorted ())
1075     {
1076       // suppress import statements for primitive types, built-in types,
1077       // types in the root package, and types in
1078       // the same package as the current type
1079       if (!_printIsImplicitlyImported (aImportClass, aClassToBeWritten))
1080       {
1081         print ("import").print (aImportClass.fullName ()).print (';').newline ();
1082         bAnyImport = true;
1083 
1084         if (m_bDebugImport)
1085           System.out.println ("  import " + aImportClass.fullName ());
1086       }
1087     }
1088 
1089     if (bAnyImport)
1090       newline ();
1091 
1092     declaration (aClassToBeWritten);
1093   }
1094 
1095   /**
1096    * Add classes that should not be imported.
1097    *
1098    * @param aClasses
1099    *        The classes to not be used in "import" statements. May be
1100    *        <code>null</code>.
1101    */
1102   public void addDontImportClasses (@Nullable final Iterable <? extends AbstractJClass> aClasses)
1103   {
1104     if (aClasses != null)
1105       for (final AbstractJClass aClass : aClasses)
1106         m_aImportedClasses.addDontImportClass (aClass);
1107   }
1108 
1109   public static boolean containsErrorTypes (@Nonnull final JDefinedClass aClass)
1110   {
1111     final JFormatter aFormatter = new JFormatter (NullWriter.getInstance ());
1112     aFormatter.m_eMode = EMode.FIND_ERROR_TYPES;
1113     aFormatter.m_bContainsErrorTypes = false;
1114     aFormatter.declaration (aClass);
1115     return aFormatter.m_bContainsErrorTypes;
1116   }
1117 }