1 package org.codehaus.mojo.clirr;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import java.io.IOException;
20 import java.io.Reader;
21 import java.util.ArrayList;
22 import java.util.List;
23
24 import net.sf.clirr.core.ApiDifference;
25 import net.sf.clirr.core.MessageTranslator;
26
27 import org.codehaus.plexus.util.SelectorUtils;
28 import org.codehaus.plexus.util.xml.pull.MXParser;
29 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
30 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
31
32
33
34
35
36
37
38 public class Difference
39 {
40
41 public static class Result
42 {
43 public static final int MATCHED = 0;
44
45 public static final int NOT_MATCHED = 1;
46
47 public static final int DEFERRED_MATCH = 2;
48
49 public Result( int code, Object differentiator )
50 {
51 this.code = code;
52 this.differentiator = differentiator;
53 }
54
55 public static Result notMatched()
56 {
57 return new Result( NOT_MATCHED, null );
58 }
59
60 public static Result matched()
61 {
62 return new Result( MATCHED, null );
63 }
64
65 public static Result deferred( Object differentiator )
66 {
67 return new Result( DEFERRED_MATCH, differentiator );
68 }
69
70 private int code;
71
72 private Object differentiator;
73
74 public int getCode()
75 {
76 return code;
77 }
78
79 public Object getDifferentiator()
80 {
81 return differentiator;
82 }
83 }
84
85 private static final MessageTranslator ARGS_EXTRACTOR = new MessageTranslator();
86 static
87 {
88 ARGS_EXTRACTOR.setResourceName( Difference.class.getName() );
89 }
90
91 public static Difference[] parseXml( Reader xml )
92 throws XmlPullParserException, IOException
93 {
94 XmlPullParser parser = new MXParser();
95 parser.setInput( xml );
96
97 List<Difference> diffs = new ArrayList<Difference>();
98
99 int state = 0;
100 int event;
101 Difference current = null;
102 while ( ( event = parser.next() ) != XmlPullParser.END_DOCUMENT )
103 {
104 switch ( event )
105 {
106 case XmlPullParser.START_TAG:
107 switch ( state )
108 {
109 case 0:
110 state = 1;
111 break;
112 case 1:
113 if ( "difference".equals( parser.getName() ) )
114 {
115 current = new Difference();
116 state = 2;
117 }
118 break;
119 case 2:
120 String name = parser.getName();
121 String value = parser.nextText().trim();
122 if ( "className".equals( name ) )
123 {
124 current.className = value;
125 }
126 else if ( "differenceType".equals( name ) )
127 {
128 current.differenceType = Integer.parseInt( value );
129 }
130 else if ( "field".equals( name ) )
131 {
132 current.field = value;
133 }
134 else if ( "method".equals( name ) )
135 {
136 current.method = value;
137 }
138 else if ( "from".equals( name ) )
139 {
140 current.from = value;
141 }
142 else if ( "to".equals( name ) )
143 {
144 current.to = value;
145 }
146 else if ( "justification".equals( name ) )
147 {
148 current.justification = value;
149 }
150 break;
151 }
152 break;
153 case XmlPullParser.END_TAG:
154 switch ( state )
155 {
156 case 1:
157 case 2:
158 if ( "difference".equals( parser.getName() ) )
159 {
160 diffs.add( current );
161 state = 1;
162 }
163 break;
164 }
165 }
166 }
167
168 return diffs.toArray( new Difference[diffs.size()] );
169 }
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 private int differenceType;
227
228
229
230
231
232
233
234 private String className;
235
236
237
238
239
240
241
242
243 private String field;
244
245
246
247
248
249
250
251 private String method;
252
253
254
255
256
257
258
259
260 private String from;
261
262
263
264
265
266
267
268
269 private String to;
270
271
272
273
274
275
276
277 private String justification;
278
279 public int getDifferenceType()
280 {
281 return differenceType;
282 }
283
284 public void setDifferenceType( int differenceType )
285 {
286 this.differenceType = differenceType;
287 }
288
289 public String getClassName()
290 {
291 return className;
292 }
293
294 public void setClassName( String className )
295 {
296 this.className = className;
297 }
298
299 public String getField()
300 {
301 return field;
302 }
303
304 public void setField( String field )
305 {
306 this.field = field;
307 }
308
309 public String getMethod()
310 {
311 return method;
312 }
313
314 public void setMethod( String method )
315 {
316 this.method = method;
317 }
318
319 public String getFrom()
320 {
321 return from;
322 }
323
324 public void setFrom( String from )
325 {
326 this.from = from;
327 }
328
329 public String getTo()
330 {
331 return to;
332 }
333
334 public void setTo( String to )
335 {
336 this.to = to;
337 }
338
339 public String getJustification()
340 {
341 return justification;
342 }
343
344 public void setJustification( String justification )
345 {
346 this.justification = justification;
347 }
348
349 public Result matches( ApiDifference apiDiff )
350 {
351 if ( apiDiff.getMessage().getId() != differenceType )
352 {
353 return Result.notMatched();
354 }
355
356 String affectedClassPath = apiDiff.getAffectedClass().replace( '.', '/' );
357 if ( !SelectorUtils.matchPath( className, affectedClassPath, "/", true ) )
358 {
359 return Result.notMatched();
360 }
361
362 switch ( differenceType )
363 {
364 case 1000:
365 return Result.matched();
366 case 1001:
367 return Result.matched();
368 case 2000:
369 return Result.matched();
370 case 2001:
371 return Result.matched();
372 case 3000:
373 return Result.matched();
374 case 3001:
375 return Result.matched();
376 case 3002:
377 return Result.matched();
378 case 3003:
379 return Result.matched();
380 case 3004:
381 return Result.matched();
382 case 3005:
383 return Result.matched();
384 case 4000:
385 return matches4000( apiDiff ) ? Result.matched() : Result.notMatched();
386 case 4001:
387 return matches4001( apiDiff ) ? Result.matched() : Result.notMatched();
388 case 5000:
389 return matches5000( apiDiff ) ? Result.matched() : Result.notMatched();
390 case 5001:
391 return matches5001( apiDiff ) ? Result.matched() : Result.notMatched();
392 case 6000:
393 return matches6000( apiDiff ) ? Result.matched() : Result.notMatched();
394 case 6001:
395 return matches6001( apiDiff ) ? Result.matched() : Result.notMatched();
396 case 6002:
397 return matches6002( apiDiff ) ? Result.matched() : Result.notMatched();
398 case 6003:
399 return matches6003( apiDiff ) ? Result.matched() : Result.notMatched();
400 case 6004:
401 return matches6004( apiDiff ) ? Result.matched() : Result.notMatched();
402 case 6005:
403 return matches6005( apiDiff ) ? Result.matched() : Result.notMatched();
404 case 6006:
405 return matches6006( apiDiff ) ? Result.matched() : Result.notMatched();
406 case 6007:
407 return matches6007( apiDiff ) ? Result.matched() : Result.notMatched();
408 case 6008:
409 return matches6008( apiDiff ) ? Result.matched() : Result.notMatched();
410 case 6009:
411 return matches6009( apiDiff ) ? Result.matched() : Result.notMatched();
412 case 6010:
413 return matches6010( apiDiff ) ? Result.matched() : Result.notMatched();
414 case 6011:
415 return matches6011( apiDiff ) ? Result.matched() : Result.notMatched();
416 case 7000:
417 return matches7000( apiDiff ) ? Result.matched() : Result.notMatched();
418 case 7001:
419 return matches7001( apiDiff ) ? Result.matched() : Result.notMatched();
420 case 7002:
421 return matches7002( apiDiff ) ? Result.matched() : Result.notMatched();
422 case 7003:
423 return matches7003( apiDiff ) ? Result.matched() : Result.notMatched();
424 case 7004:
425 return matches7004( apiDiff ) ? Result.matched() : Result.notMatched();
426 case 7005:
427 return Result.deferred( getDifferentiatorFor7005( apiDiff ) );
428 case 7006:
429 return matches7006( apiDiff ) ? Result.matched() : Result.notMatched();
430 case 7007:
431 return matches7007( apiDiff ) ? Result.matched() : Result.notMatched();
432 case 7008:
433 return matches7008( apiDiff ) ? Result.matched() : Result.notMatched();
434 case 7009:
435 return matches7009( apiDiff ) ? Result.matched() : Result.notMatched();
436 case 7010:
437 return matches7010( apiDiff ) ? Result.matched() : Result.notMatched();
438 case 7011:
439 return matches7011( apiDiff ) ? Result.matched() : Result.notMatched();
440 case 7012:
441 return matches7012( apiDiff ) ? Result.matched() : Result.notMatched();
442 case 7013:
443 return matches7013( apiDiff ) ? Result.matched() : Result.notMatched();
444 case 7014:
445 return matches7014( apiDiff ) ? Result.matched() : Result.notMatched();
446 case 7015:
447 return matches7015( apiDiff ) ? Result.matched() : Result.notMatched();
448 case 8000:
449 return Result.matched();
450 case 8001:
451 return Result.matched();
452 case 10000:
453 return matches10000( apiDiff ) ? Result.matched() : Result.notMatched();
454 case 10001:
455 return matches10001( apiDiff ) ? Result.matched() : Result.notMatched();
456 default:
457 return Result.notMatched();
458 }
459 }
460
461 public boolean resolveDefferedMatches( List<ApiDifference> defferedApiDifferences )
462 {
463 if ( differenceType == 7005 )
464 {
465 return matches7005( defferedApiDifferences );
466 }
467 else
468 {
469 return false;
470 }
471 }
472
473 @Override
474 public String toString()
475 {
476 return "Difference[differenceType=" + differenceType + ", className=" + className + ", field=" + field + ", method=" + method + ", from=" + from + ", to=" + to + "]";
477 }
478
479
480
481
482 private boolean matches4000( ApiDifference apiDiff )
483 {
484 throwIfMissing( false, false, false, true );
485
486 String newIface = getArgs( apiDiff )[0];
487 newIface = newIface.replace( '.', '/' );
488
489 return SelectorUtils.matchPath( to, newIface, "/", true );
490 }
491
492
493
494
495 private boolean matches4001( ApiDifference apiDiff )
496 {
497 throwIfMissing( false, false, false, true );
498
499 String removedIface = getArgs( apiDiff )[0];
500 removedIface = removedIface.replace( '.', '/' );
501
502 return SelectorUtils.matchPath( to, removedIface, "/", true );
503 }
504
505
506
507
508 private boolean matches5000( ApiDifference apiDiff )
509 {
510 throwIfMissing( false, false, false, true );
511
512 String newSuperclass = getArgs( apiDiff )[0];
513 newSuperclass = newSuperclass.replace( '.', '/' );
514
515 return SelectorUtils.matchPath( to, newSuperclass, "/", true );
516 }
517
518
519
520
521 private boolean matches5001( ApiDifference apiDiff )
522 {
523 throwIfMissing( false, false, false, true );
524
525 String removedSuperclass = getArgs( apiDiff )[0];
526 removedSuperclass = removedSuperclass.replace( '.', '/' );
527
528 return SelectorUtils.matchPath( to, removedSuperclass, "/", true );
529 }
530
531
532
533
534 private boolean matches6000( ApiDifference apiDiff )
535 {
536 throwIfMissing( true, false, false, false );
537 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
538 }
539
540
541
542
543 private boolean matches6001( ApiDifference apiDiff )
544 {
545 throwIfMissing( true, false, false, false );
546 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
547 }
548
549
550
551
552 private boolean matches6002( ApiDifference apiDiff )
553 {
554 throwIfMissing( true, false, false, false );
555 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
556 }
557
558
559
560
561 private boolean matches6003( ApiDifference apiDiff )
562 {
563 throwIfMissing( true, false, false, false );
564 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
565 }
566
567
568
569
570 private boolean matches6004( ApiDifference apiDiff )
571 {
572 throwIfMissing( true, false, true, true );
573
574 if ( !SelectorUtils.matchPath( field, apiDiff.getAffectedField() ) )
575 {
576 return false;
577 }
578
579 String[] args = getArgs( apiDiff );
580 String diffFrom = args[0];
581 String diffTo = args[1];
582
583 return SelectorUtils.matchPath( from, diffFrom ) && SelectorUtils.matchPath( to, diffTo );
584 }
585
586
587
588
589 private boolean matches6005( ApiDifference apiDiff )
590 {
591 throwIfMissing( true, false, false, false );
592 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
593 }
594
595
596
597
598 private boolean matches6006( ApiDifference apiDiff )
599 {
600 throwIfMissing( true, false, false, false );
601 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
602 }
603
604
605
606
607 private boolean matches6007( ApiDifference apiDiff )
608 {
609 throwIfMissing( true, false, false, false );
610 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
611 }
612
613
614
615
616 private boolean matches6008( ApiDifference apiDiff )
617 {
618 throwIfMissing( true, false, false, false );
619 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
620 }
621
622
623
624
625 private boolean matches6009( ApiDifference apiDiff )
626 {
627 throwIfMissing( true, false, false, false );
628 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
629 }
630
631
632
633
634 private boolean matches6010( ApiDifference apiDiff )
635 {
636 throwIfMissing( true, false, false, false );
637 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
638 }
639
640
641
642
643 private boolean matches6011( ApiDifference apiDiff )
644 {
645 throwIfMissing( true, false, false, false );
646 return SelectorUtils.matchPath( field, apiDiff.getAffectedField() );
647 }
648
649
650
651
652 private boolean matches7000( ApiDifference apiDiff )
653 {
654 throwIfMissing( false, true, false, false );
655 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
656 }
657
658
659
660
661 private boolean matches7001( ApiDifference apiDiff )
662 {
663 throwIfMissing( false, true, false, false );
664 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
665 }
666
667
668
669
670 private boolean matches7002( ApiDifference apiDiff )
671 {
672 throwIfMissing( false, true, false, false );
673 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
674 }
675
676
677
678
679 private boolean matches7003( ApiDifference apiDiff )
680 {
681 throwIfMissing( false, true, false, false );
682 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
683 }
684
685
686
687
688 private boolean matches7004( ApiDifference apiDiff )
689 {
690 throwIfMissing( false, true, false, false );
691 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
692 }
693
694 private Object getDifferentiatorFor7005( ApiDifference apiDiff )
695 {
696 return apiDiff.getAffectedClass() + apiDiff.getAffectedMethod();
697 }
698
699
700
701
702 private boolean matches7005( List<ApiDifference> apiDiffs )
703 {
704 throwIfMissing( false, true, false, true );
705
706 ApiDifference firstDiff = apiDiffs.get( 0 );
707 String methodSig = removeVisibilityFromMethodSignature( firstDiff );
708 if ( !SelectorUtils.matchPath( method, methodSig ) )
709 {
710 return false;
711 }
712
713 String newMethodSig = getNewMethodSignature( methodSig, apiDiffs );
714 return SelectorUtils.matchPath( to, newMethodSig );
715 }
716
717 public static String getNewMethodSignature( String methodSig, List<ApiDifference> apiDiffs )
718 {
719 String newMethodSig = methodSig;
720 for ( ApiDifference apiDiff : apiDiffs )
721 {
722 String[] args = getArgs( apiDiff );
723
724
725 int idx = Integer.parseInt( args[0] ) - 1;
726 String diffNewType = args[1];
727
728
729 newMethodSig = replaceNthArgumentType( newMethodSig, idx, diffNewType );
730 }
731 return newMethodSig;
732 }
733
734
735
736
737 private boolean matches7006( ApiDifference apiDiff )
738 {
739 throwIfMissing( false, true, false, true );
740
741 String methodSig = removeVisibilityFromMethodSignature( apiDiff );
742 if ( !SelectorUtils.matchPath( method, methodSig ) )
743 {
744 return false;
745 }
746
747 String newRetType = getArgs( apiDiff )[0];
748
749 return SelectorUtils.matchPath( to, newRetType );
750 }
751
752
753
754
755 private boolean matches7007( ApiDifference apiDiff )
756 {
757 throwIfMissing( false, true, false, false );
758 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
759 }
760
761
762
763
764 private boolean matches7008( ApiDifference apiDiff )
765 {
766 throwIfMissing( false, true, false, false );
767 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
768 }
769
770
771
772
773 private boolean matches7009( ApiDifference apiDiff )
774 {
775 throwIfMissing( false, true, false, false );
776 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
777 }
778
779
780
781
782 private boolean matches7010( ApiDifference apiDiff )
783 {
784 throwIfMissing( false, true, false, false );
785 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
786 }
787
788
789
790
791 private boolean matches7011( ApiDifference apiDiff )
792 {
793 throwIfMissing( false, true, false, false );
794 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
795 }
796
797
798
799
800 private boolean matches7012( ApiDifference apiDiff )
801 {
802 throwIfMissing( false, true, false, false );
803 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
804 }
805
806
807
808
809 private boolean matches7013( ApiDifference apiDiff )
810 {
811 throwIfMissing( false, true, false, false );
812 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
813 }
814
815
816
817
818 private boolean matches7014( ApiDifference apiDiff )
819 {
820 throwIfMissing( false, true, false, false );
821 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
822 }
823
824
825
826
827 private boolean matches7015( ApiDifference apiDiff )
828 {
829 throwIfMissing( false, true, false, false );
830 return SelectorUtils.matchPath( method, removeVisibilityFromMethodSignature( apiDiff ) );
831 }
832
833
834
835
836 private boolean matches10000( ApiDifference apiDiff )
837 {
838 throwIfMissing( false, false, true, true );
839
840 int fromVersion = 0;
841 int toVersion = 0;
842 try
843 {
844 fromVersion = Integer.parseInt( from );
845 }
846 catch ( NumberFormatException e )
847 {
848 throw new IllegalArgumentException( "Failed to parse the \"from\" parameter as a number for " + this );
849 }
850
851 try
852 {
853 toVersion = Integer.parseInt( to );
854 }
855 catch ( NumberFormatException e )
856 {
857 throw new IllegalArgumentException( "Failed to parse the \"to\" parameter as a number for " + this );
858 }
859
860 String[] args = getArgs( apiDiff );
861
862 int reportedOld = Integer.parseInt( args[0] );
863 int reportedNew = Integer.parseInt( args[1] );
864
865 return fromVersion == reportedOld && toVersion == reportedNew;
866 }
867
868
869
870
871 private boolean matches10001( ApiDifference apiDiff )
872 {
873 throwIfMissing( false, false, true, true );
874
875 int fromVersion = 0;
876 int toVersion = 0;
877 try
878 {
879 fromVersion = Integer.parseInt( from );
880 }
881 catch ( NumberFormatException e )
882 {
883 throw new IllegalArgumentException( "Failed to parse the \"from\" parameter as a number for " + this );
884 }
885
886 try
887 {
888 toVersion = Integer.parseInt( to );
889 }
890 catch ( NumberFormatException e )
891 {
892 throw new IllegalArgumentException( "Failed to parse the \"to\" parameter as a number for " + this );
893 }
894
895 String[] args = getArgs( apiDiff );
896
897 int reportedOld = Integer.parseInt( args[0] );
898 int reportedNew = Integer.parseInt( args[1] );
899
900 return fromVersion == reportedOld && toVersion == reportedNew;
901 }
902
903 private static String[] getArgs( ApiDifference apiDiff )
904 {
905 String args = apiDiff.getReport( ARGS_EXTRACTOR );
906 return args.split( "&" );
907 }
908
909 private void throwIfMissing( boolean field, boolean method, boolean from, boolean to )
910 {
911 boolean doThrow =
912 ( field && this.field == null ) || ( method && this.method == null ) || ( from && this.from == null )
913 || ( to && this.to == null );
914
915 if ( doThrow )
916 {
917 StringBuilder message = new StringBuilder( "The following parameters are missing: " );
918 if ( field && this.field == null )
919 {
920 message.append( "field, " );
921 }
922
923 if ( method && this.method == null )
924 {
925 message.append( "method, " );
926 }
927
928 if ( from && this.from == null )
929 {
930 message.append( "from, " );
931 }
932
933 if ( to && this.to == null )
934 {
935 message.append( "to, " );
936 }
937
938 message.replace( message.length() - 2, message.length(), "" );
939
940 message.append( " on " ).append( this );
941
942 throw new IllegalArgumentException( message.toString() );
943 }
944 }
945
946 private static String replaceNthArgumentType( String signature, int idx, String newType )
947 {
948 int openParIdx = signature.indexOf( '(' );
949 int closeParIdx = signature.indexOf( ')' );
950
951 if ( openParIdx < 0 || closeParIdx < 0 )
952 {
953 throw new IllegalArgumentException( "Invalid method signature found in the API difference report: "
954 + signature );
955 }
956
957 StringBuilder bld = new StringBuilder();
958 bld.append( signature, 0, openParIdx ).append( '(' );
959
960 int commaIdx = openParIdx + 1;
961 int paramIdx = 0;
962 while ( true )
963 {
964 int nextCommaIdx = signature.indexOf( ',', commaIdx );
965
966 if ( nextCommaIdx < 0 )
967 {
968 break;
969 }
970
971 String type = paramIdx == idx ? newType : signature.substring( commaIdx, nextCommaIdx );
972
973 bld.append( type.trim() );
974 bld.append( ", " );
975
976 commaIdx = nextCommaIdx + 1;
977 paramIdx++;
978
979 }
980
981 if ( paramIdx == idx )
982 {
983 bld.append( newType );
984 }
985 else
986 {
987 bld.append( signature, commaIdx + 1, closeParIdx );
988 }
989
990 bld.append( ")" );
991
992 return bld.toString();
993 }
994
995 private String removeVisibilityFromMethodSignature( ApiDifference apiDiff )
996 {
997 String methodSig = apiDiff.getAffectedMethod();
998 if ( methodSig == null )
999 {
1000 return null;
1001 }
1002
1003 int spaceIdx = methodSig.indexOf( ' ' );
1004 if ( spaceIdx < 0 )
1005 {
1006 return methodSig;
1007 }
1008 else
1009 {
1010 return methodSig.substring( spaceIdx + 1 );
1011 }
1012 }
1013 }