@@ -62,8 +62,10 @@ public List<String> getSubGroups() {
62
62
}
63
63
}
64
64
65
- static List <String > sort (List <String > imports , List <String > importsOrder , boolean wildcardsLast , String lineFormat ) {
66
- ImportSorterImpl importsSorter = new ImportSorterImpl (importsOrder , wildcardsLast );
65
+ static List <String > sort (List <String > imports , List <String > importsOrder , boolean wildcardsLast ,
66
+ boolean semanticSort , Set <String > treatAsPackage , Set <String > treatAsClass , String lineFormat ) {
67
+ ImportSorterImpl importsSorter = new ImportSorterImpl (importsOrder , wildcardsLast , semanticSort , treatAsPackage ,
68
+ treatAsClass );
67
69
return importsSorter .sort (imports , lineFormat );
68
70
}
69
71
@@ -76,12 +78,17 @@ private List<String> sort(List<String> imports, String lineFormat) {
76
78
return getResult (sortedImported , lineFormat );
77
79
}
78
80
79
- private ImportSorterImpl (List <String > importOrder , boolean wildcardsLast ) {
81
+ private ImportSorterImpl (List <String > importOrder , boolean wildcardsLast , boolean semanticSort ,
82
+ Set <String > treatAsPackage , Set <String > treatAsClass ) {
80
83
importsGroups = importOrder .stream ().filter (Objects ::nonNull ).map (ImportsGroup ::new ).collect (Collectors .toList ());
81
84
putStaticItemIfNotExists (importsGroups );
82
85
putCatchAllGroupIfNotExists (importsGroups );
83
86
84
- ordering = new OrderingComparator (wildcardsLast );
87
+ if (semanticSort ) {
88
+ ordering = new SemanticOrderingComparator (wildcardsLast , treatAsPackage , treatAsClass );
89
+ } else {
90
+ ordering = new LexicographicalOrderingComparator (wildcardsLast );
91
+ }
85
92
86
93
List <String > subgroups = importsGroups .stream ().map (ImportsGroup ::getSubGroups ).flatMap (Collection ::stream ).collect (Collectors .toList ());
87
94
this .allImportOrderItems .addAll (subgroups );
@@ -233,30 +240,192 @@ private List<String> getResult(List<String> sortedImported, String lineFormat) {
233
240
return null ;
234
241
}
235
242
236
- private static class OrderingComparator implements Comparator <String >, Serializable {
243
+ private static int compareWithWildcare (String string1 , String string2 , boolean wildcardsLast ) {
244
+ int string1WildcardIndex = string1 .indexOf ('*' );
245
+ int string2WildcardIndex = string2 .indexOf ('*' );
246
+ boolean string1IsWildcard = string1WildcardIndex >= 0 ;
247
+ boolean string2IsWildcard = string2WildcardIndex >= 0 ;
248
+ if (string1IsWildcard == string2IsWildcard ) {
249
+ return string1 .compareTo (string2 );
250
+ }
251
+ int prefixLength = string1IsWildcard ? string1WildcardIndex : string2WildcardIndex ;
252
+ boolean samePrefix = string1 .regionMatches (0 , string2 , 0 , prefixLength );
253
+ if (!samePrefix ) {
254
+ return string1 .compareTo (string2 );
255
+ }
256
+ return (string1IsWildcard == wildcardsLast ) ? 1 : -1 ;
257
+ }
258
+
259
+ private static class LexicographicalOrderingComparator implements Comparator <String >, Serializable {
237
260
private static final long serialVersionUID = 1 ;
238
261
239
262
private final boolean wildcardsLast ;
240
263
241
- private OrderingComparator (boolean wildcardsLast ) {
264
+ private LexicographicalOrderingComparator (boolean wildcardsLast ) {
242
265
this .wildcardsLast = wildcardsLast ;
243
266
}
244
267
245
268
@ Override
246
269
public int compare (String string1 , String string2 ) {
247
- int string1WildcardIndex = string1 .indexOf ('*' );
248
- int string2WildcardIndex = string2 .indexOf ('*' );
249
- boolean string1IsWildcard = string1WildcardIndex >= 0 ;
250
- boolean string2IsWildcard = string2WildcardIndex >= 0 ;
251
- if (string1IsWildcard == string2IsWildcard ) {
252
- return string1 .compareTo (string2 );
270
+ return compareWithWildcare (string1 , string2 , wildcardsLast );
271
+ }
272
+ }
273
+
274
+ private static class SemanticOrderingComparator implements Comparator <String >, Serializable {
275
+ private static final long serialVersionUID = 1 ;
276
+
277
+ private final boolean wildcardsLast ;
278
+ private final Set <String > treatAsPackage ;
279
+ private final Set <String > treatAsClass ;
280
+
281
+ private SemanticOrderingComparator (boolean wildcardsLast , Set <String > treatAsPackage ,
282
+ Set <String > treatAsClass ) {
283
+ this .wildcardsLast = wildcardsLast ;
284
+ this .treatAsPackage = treatAsPackage ;
285
+ this .treatAsClass = treatAsClass ;
286
+ }
287
+
288
+ @ Override
289
+ public int compare (String string1 , String string2 ) {
290
+ /*
291
+ * Ordering uses semantics of the import string by splitting it into package,
292
+ * class name(s) and static member (for static imports) and then comparing by
293
+ * each of those three substrings in sequence.
294
+ *
295
+ * When comparing static imports, the last segment in the dot-separated string
296
+ * is considered to be the member (field, method, type) name.
297
+ *
298
+ * The first segment starting with an upper case letter is considered to be the
299
+ * (first) class name. Since this comparator has no actual type information,
300
+ * this auto-detection will fail for upper case package names and lower case
301
+ * class names. treatAsPackage and treatAsClass can be used respectively to
302
+ * provide hints to the auto-detection.
303
+ */
304
+ if (string1 .startsWith (STATIC_KEYWORD )) {
305
+ String [] split = splitFqcnAndMember (string1 );
306
+ String fqcn1 = split [0 ];
307
+ String member1 = split [1 ];
308
+
309
+ split = splitFqcnAndMember (string2 );
310
+ String fqcn2 = split [0 ];
311
+ String member2 = split [1 ];
312
+
313
+ int result = compareFullyQualifiedClassName (fqcn1 , fqcn2 );
314
+ if (result != 0 )
315
+ return result ;
316
+
317
+ return compareWithWildcare (member1 , member2 , wildcardsLast );
318
+ } else {
319
+ return compareFullyQualifiedClassName (string1 , string2 );
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Compares two fully qualified class names by splitting them into package and
325
+ * (nested) class names.
326
+ */
327
+ private int compareFullyQualifiedClassName (String fqcn1 , String fqcn2 ) {
328
+ String [] split = splitPackageAndClasses (fqcn1 );
329
+ String p1 = split [0 ];
330
+ String c1 = split [1 ];
331
+
332
+ split = splitPackageAndClasses (fqcn2 );
333
+ String p2 = split [0 ];
334
+ String c2 = split [1 ];
335
+
336
+ int result = p1 .compareTo (p2 );
337
+ if (result != 0 )
338
+ return result ;
339
+
340
+ return compareWithWildcare (c1 , c2 , wildcardsLast );
341
+ }
342
+
343
+ /**
344
+ * Splits the provided static import string into fully qualified class name and
345
+ * the imported static member (field, method or type).
346
+ */
347
+ private String [] splitFqcnAndMember (String importString ) {
348
+ String s = importString .substring (STATIC_KEYWORD .length ()).trim ();
349
+
350
+ String fqcn ;
351
+ String member ;
352
+
353
+ int dot = s .lastIndexOf ("." );
354
+ if (!Character .isUpperCase (s .charAt (dot + 1 ))) {
355
+ fqcn = s .substring (0 , dot );
356
+ member = s .substring (dot + 1 );
357
+ } else {
358
+ fqcn = s ;
359
+ member = null ;
253
360
}
254
- int prefixLength = string1IsWildcard ? string1WildcardIndex : string2WildcardIndex ;
255
- boolean samePrefix = string1 .regionMatches (0 , string2 , 0 , prefixLength );
256
- if (!samePrefix ) {
257
- return string1 .compareTo (string2 );
361
+
362
+ return new String [] { fqcn , member };
363
+ }
364
+
365
+ /**
366
+ * Splits the fully qualified class name into package and class name(s).
367
+ */
368
+ private String [] splitPackageAndClasses (String fqcn ) {
369
+ String packageNames = null ;
370
+ String classNames = null ;
371
+
372
+ /*
373
+ * The first segment that starts with an upper case letter starts the class
374
+ * name(s), unless it matches treatAsPackage (then it's explicitly declared as
375
+ * package via configuration). If no segment starts with an upper case letter
376
+ * then the last segment must be a class name (unless the method input is
377
+ * garbage).
378
+ */
379
+ int dot = fqcn .indexOf ('.' );
380
+ while (dot > -1 ) {
381
+ int nextDot = fqcn .indexOf ('.' , dot + 1 );
382
+ if (nextDot > -1 ) {
383
+ if (Character .isUpperCase (fqcn .charAt (dot + 1 ))) {
384
+ // if upper case, check if should be treated as package nonetheless
385
+ if (!treatAsPackage (fqcn .substring (0 , nextDot ))) {
386
+ packageNames = fqcn .substring (0 , dot );
387
+ classNames = fqcn .substring (dot + 1 );
388
+ break ;
389
+ }
390
+ } else {
391
+ // if lower case, check if should be treated as class nonetheless
392
+ if (treatAsClass (fqcn .substring (0 , nextDot ))) {
393
+ packageNames = fqcn .substring (0 , dot );
394
+ classNames = fqcn .substring (dot + 1 );
395
+ break ;
396
+ }
397
+ }
398
+ }
399
+
400
+ dot = nextDot ;
258
401
}
259
- return (string1IsWildcard == wildcardsLast ) ? 1 : -1 ;
402
+
403
+ if (packageNames == null ) {
404
+ int i = fqcn .lastIndexOf ("." );
405
+ packageNames = fqcn .substring (0 , i );
406
+ classNames = fqcn .substring (i + 1 );
407
+ }
408
+
409
+ return new String [] { packageNames , classNames };
260
410
}
411
+
412
+ /**
413
+ * Returns whether the provided prefix matches any entry of
414
+ * {@code treatAsPackage}.
415
+ */
416
+ private boolean treatAsPackage (String prefix ) {
417
+ // This would be the place to introduce wild cards or even regex matching.
418
+ return treatAsPackage .contains (prefix );
419
+ }
420
+
421
+ /**
422
+ * Returns whether the provided prefix name matches any entry of
423
+ * {@code treatAsClass}.
424
+ */
425
+ private boolean treatAsClass (String prefix ) {
426
+ // This would be the place to introduce wild cards or even regex matching.
427
+ return treatAsClass .contains (prefix );
428
+ }
429
+
261
430
}
262
431
}
0 commit comments