1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
65
66
67 @NotThreadSafe
68 public class JFormatter implements Closeable
69 {
70
71
72
73
74
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
83 private boolean m_bIsVariableName;
84
85 public NameUsage (@Nonnull final String sName)
86 {
87 m_sName = sName;
88 }
89
90
91
92
93
94 public boolean isAmbiguousIn (@Nonnull final JDefinedClass aEnclosingClass)
95 {
96
97 if (m_aReferencedClasses.size () > 1)
98 return true;
99
100
101 if (m_bIsVariableName && !m_aReferencedClasses.isEmpty ())
102 return true;
103
104
105 if (m_aReferencedClasses.isEmpty ())
106 return false;
107
108
109 AbstractJClass aSingleRef = m_aReferencedClasses.get (0);
110 if (aSingleRef instanceof JAnonymousClass)
111 {
112 aSingleRef = ((JAnonymousClass) aSingleRef).base ();
113 }
114
115
116
117 if (aSingleRef._package () == JFormatter.this.m_aPckJavaLang)
118 {
119
120
121 for (final JDefinedClass aClass : aEnclosingClass._package ().classes ())
122 {
123
124
125
126 if (aClass.name ().equals (aSingleRef.name ()))
127 {
128
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
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
180
181
182 public boolean isVariableName ()
183 {
184 return m_bIsVariableName;
185 }
186
187
188
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
209
210
211 COLLECTING,
212
213
214
215 PRINTING,
216
217
218
219
220
221
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
242 return _getClassForImport (((JAnonymousClass) aRealClass).base ());
243 }
244 if (aRealClass instanceof JNarrowedClass)
245 {
246
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
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
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
316
317
318
319
320 static final char CLOSE_TYPE_ARGS = '\uFFFF';
321
322
323
324
325
326
327 private final Map <String, NameUsage> m_aCollectedReferences = new HashMap <> ();
328
329
330
331
332
333 private final ImportedClasses m_aImportedClasses = new ImportedClasses ();
334
335
336
337
338
339 private EMode m_eMode = EMode.PRINTING;
340
341
342
343
344 private int m_nIndentLevel;
345
346
347
348
349 private final String m_sIndentSpace;
350
351
352
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
362
363
364 private boolean m_bContainsErrorTypes;
365
366 private boolean m_bDebugImport = false;
367
368
369
370
371
372
373
374 public JFormatter (@Nonnull final SourcePrintWriter aPW)
375 {
376 this (aPW, DEFAULT_INDENT_SPACE);
377 }
378
379
380
381
382
383
384
385
386
387
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
400
401
402
403
404 public JFormatter (@Nonnull final Writer aWriter)
405 {
406 this (aWriter, DEFAULT_INDENT_SPACE);
407 }
408
409
410
411
412
413
414
415
416
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
425
426
427
428
429
430
431
432
433
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
454
455 public void close ()
456 {
457 m_aPW.close ();
458 }
459
460
461
462
463
464 public boolean isPrinting ()
465 {
466 return m_eMode == EMode.PRINTING;
467 }
468
469
470
471
472
473
474 @Nonnull
475 public JFormatter outdent ()
476 {
477 m_nIndentLevel--;
478 return this;
479 }
480
481
482
483
484
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
502 if (c2 == '(')
503 {
504
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
573
574
575
576
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
599
600
601
602
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
626
627
628
629
630
631
632
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
659
660 if (m_aImportedClasses.contains (aType) || aType._package () == m_aPckJavaLang)
661 {
662
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
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
689
690
691
692
693
694 @Nonnull
695 public JFormatter id (@Nonnull final String sID)
696 {
697 switch (m_eMode)
698 {
699 case COLLECTING:
700
701 NameUsage aUsages = m_aCollectedReferences.get (sID);
702 if (aUsages == null)
703 {
704
705
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
720
721
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
737
738
739
740
741
742 @Nonnull
743 public JFormatter generable (@Nonnull final IJGenerable g)
744 {
745 g.generate (this);
746 return this;
747 }
748
749
750
751
752
753
754
755
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
776
777
778
779
780
781 @Nonnull
782 public JFormatter declaration (@Nonnull final IJDeclaration d)
783 {
784 d.declare (this);
785 return this;
786 }
787
788
789
790
791
792
793
794
795 @Nonnull
796 public JFormatter statement (@Nonnull final IJStatement aStmt)
797 {
798 aStmt.state (this);
799 return this;
800 }
801
802
803
804
805
806
807
808
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
835
836
837
838
839
840
841
842
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
858 aRealReference = ((JAnonymousClass) aRealReference).base ();
859 }
860 if (aRealReference instanceof JNarrowedClass)
861 {
862
863 aRealReference = aRealReference.erasure ();
864 }
865
866
867 final AbstractJClass aOuter = aRealReference.outer ();
868 if (aOuter != null)
869 {
870
871
872
873
874 if (aRealReference.name ().contains (aOuter.name ()))
875 {
876
877 if (_collectShouldBeImported (aOuter, aClassToBeWritten))
878 return true;
879 }
880
881
882
883 return false;
884 }
885 return true;
886 }
887
888
889
890
891
892
893
894
895
896
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
919 _collectImportOuterClassIfCausesNoAmbiguities (aOuter, aClassToBeWritten);
920 }
921 }
922 }
923
924
925
926
927
928
929
930
931
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
947 aRealReference = ((JAnonymousClass) aRealReference).base ();
948 }
949 if (aRealReference instanceof JNarrowedClass)
950 {
951
952 aRealReference = aRealReference.erasure ();
953 }
954
955 final JPackage aPackage = aRealReference._package ();
956 if (aPackage == null)
957 {
958
959 return true;
960 }
961
962 if (aPackage.isUnnamed ())
963 {
964
965 return true;
966 }
967
968 if (aPackage == m_aPckJavaLang)
969 {
970
971 return true;
972 }
973
974
975
976 if (aPackage == aClassToBeWrittem._package ())
977 {
978 AbstractJClass aOuter = aRealReference.outer ();
979 if (aOuter == null)
980 {
981
982 return true;
983 }
984
985
986 AbstractJClass aTopLevelClass = aOuter;
987 aOuter = aTopLevelClass.outer ();
988 while (aOuter != null)
989 {
990 aTopLevelClass = aOuter;
991 aOuter = aTopLevelClass.outer ();
992 }
993
994
995
996
997
998 return aTopLevelClass == aClassToBeWrittem;
999 }
1000 return false;
1001 }
1002
1003
1004
1005
1006
1007
1008
1009 void write (@Nonnull final JDefinedClass aClassToBeWritten)
1010 {
1011 m_aPckJavaLang = aClassToBeWritten.owner ()._package ("java.lang");
1012
1013
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
1023
1024 m_aImportedClasses.add (aClassToBeWritten);
1025
1026
1027
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
1057 m_eMode = EMode.PRINTING;
1058
1059 assert aClassToBeWritten.parentContainer ().isPackage () : "this method is only for a pacakge-level class";
1060
1061
1062 if (aClassToBeWritten.hasHeaderComment ())
1063 generable (aClassToBeWritten.headerComment ());
1064
1065
1066 final JPackage aPackage = (JPackage) aClassToBeWritten.parentContainer ();
1067 if (!aPackage.isUnnamed ())
1068 {
1069 declaration (aPackage).newline ();
1070 }
1071
1072
1073 boolean bAnyImport = false;
1074 for (final AbstractJClass aImportClass : m_aImportedClasses.getAllSorted ())
1075 {
1076
1077
1078
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
1097
1098
1099
1100
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 }