11/*
2- * Copyright 2016-2021 DiffPlug
2+ * Copyright 2016-2022 DiffPlug
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1717
1818import java .io .Serializable ;
1919import java .util .*;
20+ import java .util .stream .Collectors ;
21+ import java .util .stream .Stream ;
2022
2123import javax .annotation .Nullable ;
2224
2527// which itself is licensed under the Apache 2.0 license.
2628final class ImportSorterImpl {
2729
28- private final List <String > template = new ArrayList <>();
30+ private static final String CATCH_ALL_SUBGROUP = "" ;
31+ private static final String STATIC_KEYWORD = "static " ;
32+ private static final String STATIC_SYMBOL = "\\ #" ;
33+ private static final String SUBGROUP_SEPARATOR = "|" ;
34+
35+ private final List <ImportsGroup > importsGroups ;
2936 private final Map <String , List <String >> matchingImports = new HashMap <>();
3037 private final List <String > notMatching = new ArrayList <>();
3138 private final Set <String > allImportOrderItems = new HashSet <>();
3239 private final Comparator <String > ordering ;
3340
41+ // An ImportsGroup is a group of imports ; each group is separated by blank lines.
42+ // A group is composed of subgroups : imports are sorted by subgroup.
43+ private static class ImportsGroup {
44+
45+ private final List <String > subGroups ;
46+
47+ public ImportsGroup (String importOrder ) {
48+ this .subGroups = Stream .of (importOrder .split ("\\ " + SUBGROUP_SEPARATOR ))
49+ .map (this ::normalizeStatic )
50+ .collect (Collectors .toList ());
51+ }
52+
53+ private String normalizeStatic (String subgroup ) {
54+ if (subgroup .startsWith (STATIC_SYMBOL )) {
55+ return subgroup .replace (STATIC_SYMBOL , STATIC_KEYWORD );
56+ }
57+ return subgroup ;
58+ }
59+
60+ public List <String > getSubGroups () {
61+ return subGroups ;
62+ }
63+ }
64+
3465 static List <String > sort (List <String > imports , List <String > importsOrder , boolean wildcardsLast , String lineFormat ) {
3566 ImportSorterImpl importsSorter = new ImportSorterImpl (importsOrder , wildcardsLast );
3667 return importsSorter .sort (imports , lineFormat );
@@ -40,43 +71,42 @@ private List<String> sort(List<String> imports, String lineFormat) {
4071 filterMatchingImports (imports );
4172 mergeNotMatchingItems (false );
4273 mergeNotMatchingItems (true );
43- mergeMatchingItems ();
74+ List < String > sortedImported = mergeMatchingItems ();
4475
45- return getResult (lineFormat );
76+ return getResult (sortedImported , lineFormat );
4677 }
4778
4879 private ImportSorterImpl (List <String > importOrder , boolean wildcardsLast ) {
49- List < String > importOrderCopy = new ArrayList <>( importOrder );
50- normalizeStaticOrderItems ( importOrderCopy );
51- putStaticItemIfNotExists ( importOrderCopy );
52- template . addAll ( importOrderCopy );
80+ importsGroups = importOrder . stream (). filter ( Objects :: nonNull ). map ( ImportsGroup :: new ). collect ( Collectors . toList () );
81+ putStaticItemIfNotExists ( importsGroups );
82+ putCatchAllGroupIfNotExists ( importsGroups );
83+
5384 ordering = new OrderingComparator (wildcardsLast );
54- this .allImportOrderItems .addAll (importOrderCopy );
85+
86+ List <String > subgroups = importsGroups .stream ().map (ImportsGroup ::getSubGroups ).flatMap (Collection ::stream ).collect (Collectors .toList ());
87+ this .allImportOrderItems .addAll (subgroups );
5588 }
5689
57- private static void putStaticItemIfNotExists (List <String > allImportOrderItems ) {
58- boolean contains = false ;
90+ private void putStaticItemIfNotExists (List <ImportsGroup > importsGroups ) {
91+ boolean catchAllSubGroupExist = importsGroups .stream ().anyMatch (group -> group .getSubGroups ().contains (STATIC_KEYWORD ));
92+ if (catchAllSubGroupExist ) {
93+ return ;
94+ }
95+
5996 int indexOfFirstStatic = 0 ;
60- for (int i = 0 ; i < allImportOrderItems .size (); i ++) {
61- String allImportOrderItem = allImportOrderItems .get (i );
62- if (allImportOrderItem .equals ("static " )) {
63- contains = true ;
64- }
65- if (allImportOrderItem .startsWith ("static " )) {
97+ for (int i = 0 ; i < importsGroups .size (); i ++) {
98+ boolean subgroupMatch = importsGroups .get (i ).getSubGroups ().stream ().anyMatch (subgroup -> subgroup .startsWith (STATIC_KEYWORD ));
99+ if (subgroupMatch ) {
66100 indexOfFirstStatic = i ;
67101 }
68102 }
69- if (!contains ) {
70- allImportOrderItems .add (indexOfFirstStatic , "static " );
71- }
103+ importsGroups .add (indexOfFirstStatic , new ImportsGroup (STATIC_KEYWORD ));
72104 }
73105
74- private static void normalizeStaticOrderItems (List <String > allImportOrderItems ) {
75- for (int i = 0 ; i < allImportOrderItems .size (); i ++) {
76- String s = allImportOrderItems .get (i );
77- if (s .startsWith ("\\ #" )) {
78- allImportOrderItems .set (i , s .replace ("\\ #" , "static " ));
79- }
106+ private void putCatchAllGroupIfNotExists (List <ImportsGroup > importsGroups ) {
107+ boolean catchAllSubGroupExist = importsGroups .stream ().anyMatch (group -> group .getSubGroups ().contains (CATCH_ALL_SUBGROUP ));
108+ if (!catchAllSubGroupExist ) {
109+ importsGroups .add (new ImportsGroup (CATCH_ALL_SUBGROUP ));
80110 }
81111 }
82112
@@ -87,9 +117,7 @@ private void filterMatchingImports(List<String> imports) {
87117 for (String anImport : imports ) {
88118 String orderItem = getBestMatchingImportOrderItem (anImport );
89119 if (orderItem != null ) {
90- if (!matchingImports .containsKey (orderItem )) {
91- matchingImports .put (orderItem , new ArrayList <>());
92- }
120+ matchingImports .computeIfAbsent (orderItem , key -> new ArrayList <>());
93121 matchingImports .get (orderItem ).add (anImport );
94122 } else {
95123 notMatching .add (anImport );
@@ -116,34 +144,14 @@ private void filterMatchingImports(List<String> imports) {
116144 * not matching means it does not match any order item, so it will be appended before or after order items
117145 */
118146 private void mergeNotMatchingItems (boolean staticItems ) {
119- sort (notMatching );
120-
121- int firstIndexOfOrderItem = getFirstIndexOfOrderItem (notMatching , staticItems );
122- int indexOfOrderItem = 0 ;
123147 for (String notMatchingItem : notMatching ) {
124148 if (!matchesStatic (staticItems , notMatchingItem )) {
125149 continue ;
126150 }
127151 boolean isOrderItem = isOrderItem (notMatchingItem , staticItems );
128- if (isOrderItem ) {
129- indexOfOrderItem = template .indexOf (notMatchingItem );
130- } else {
131- if (indexOfOrderItem == 0 && firstIndexOfOrderItem != 0 ) {
132- // insert before alphabetically first order item
133- template .add (firstIndexOfOrderItem , notMatchingItem );
134- firstIndexOfOrderItem ++;
135- } else if (firstIndexOfOrderItem == 0 ) {
136- // no order is specified
137- if (template .size () > 0 && (template .get (template .size () - 1 ).startsWith ("static" ))) {
138- // insert N after last static import
139- template .add (ImportSorter .N );
140- }
141- template .add (notMatchingItem );
142- } else {
143- // insert after the previous order item
144- template .add (indexOfOrderItem + 1 , notMatchingItem );
145- indexOfOrderItem ++;
146- }
152+ if (!isOrderItem ) {
153+ matchingImports .computeIfAbsent (CATCH_ALL_SUBGROUP , key -> new ArrayList <>());
154+ matchingImports .get (CATCH_ALL_SUBGROUP ).add (notMatchingItem );
147155 }
148156 }
149157 }
@@ -153,76 +161,44 @@ private boolean isOrderItem(String notMatchingItem, boolean staticItems) {
153161 return contains && matchesStatic (staticItems , notMatchingItem );
154162 }
155163
156- /**
157- * gets first order item from sorted input list, and finds out it's index in template.
158- */
159- private int getFirstIndexOfOrderItem (List <String > notMatching , boolean staticItems ) {
160- int firstIndexOfOrderItem = 0 ;
161- for (String notMatchingItem : notMatching ) {
162- if (!matchesStatic (staticItems , notMatchingItem )) {
163- continue ;
164- }
165- boolean isOrderItem = isOrderItem (notMatchingItem , staticItems );
166- if (isOrderItem ) {
167- firstIndexOfOrderItem = template .indexOf (notMatchingItem );
168- break ;
169- }
170- }
171- return firstIndexOfOrderItem ;
172- }
173-
174164 private static boolean matchesStatic (boolean staticItems , String notMatchingItem ) {
175- boolean isStatic = notMatchingItem .startsWith ("static " );
165+ boolean isStatic = notMatchingItem .startsWith (STATIC_KEYWORD );
176166 return (isStatic && staticItems ) || (!isStatic && !staticItems );
177167 }
178168
179- private void mergeMatchingItems () {
180- for ( int i = 0 ; i < template . size (); i ++) {
181- String item = template . get ( i );
182- if ( allImportOrderItems . contains ( item )) {
183- // find matching items for order item
184- List <String > strings = matchingImports .get (item );
169+ private List < String > mergeMatchingItems () {
170+ List < String > template = new ArrayList <> ();
171+ for ( ImportsGroup group : importsGroups ) {
172+ boolean groupIsNotEmpty = false ;
173+ for ( String subgroup : group . getSubGroups ()) {
174+ List <String > strings = matchingImports .get (subgroup );
185175 if (strings == null || strings .isEmpty ()) {
186- // if there is none, just remove order item
187- template .remove (i );
188- i --;
189176 continue ;
190177 }
178+ groupIsNotEmpty = true ;
191179 List <String > matchingItems = new ArrayList <>(strings );
192180 sort (matchingItems );
193-
194- // replace order item by matching import statements
195- // this is a mess and it is only a luck that it works :-]
196- template .remove (i );
197- if (i != 0 && !template .get (i - 1 ).equals (ImportSorter .N )) {
198- template .add (i , ImportSorter .N );
199- i ++;
200- }
201- if (i + 1 < template .size () && !template .get (i + 1 ).equals (ImportSorter .N )
202- && !template .get (i ).equals (ImportSorter .N )) {
203- template .add (i , ImportSorter .N );
204- }
205- template .addAll (i , matchingItems );
206- if (i != 0 && !template .get (i - 1 ).equals (ImportSorter .N )) {
207- template .add (i , ImportSorter .N );
208- }
209-
181+ template .addAll (matchingItems );
182+ }
183+ if (groupIsNotEmpty ) {
184+ template .add (ImportSorter .N );
210185 }
211186 }
212187 // if there is \n on the end, remove it
213- if (template .size () > 0 && template .get (template .size () - 1 ).equals (ImportSorter .N )) {
188+ if (! template .isEmpty () && template .get (template .size () - 1 ).equals (ImportSorter .N )) {
214189 template .remove (template .size () - 1 );
215190 }
191+ return template ;
216192 }
217193
218194 private void sort (List <String > items ) {
219195 items .sort (ordering );
220196 }
221197
222- private List <String > getResult (String lineFormat ) {
198+ private List <String > getResult (List < String > sortedImported , String lineFormat ) {
223199 List <String > strings = new ArrayList <>();
224200
225- for (String s : template ) {
201+ for (String s : sortedImported ) {
226202 if (s .equals (ImportSorter .N )) {
227203 strings .add (s );
228204 } else {
0 commit comments