View Javadoc
1   package org.codehaus.mojo.clirr;
2   
3   /*
4    * Copyright 2012 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
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   * A specification of a ignored difference found by Clirr.
34   *
35   * @author Lukas Krejci
36   * @since 2.5
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: // start document
110                             state = 1;
111                             break;
112                         case 1: // expect next difference
113                             if ( "difference".equals( parser.getName() ) )
114                             {
115                                 current = new Difference();
116                                 state = 2;
117                             }
118                             break;
119                         case 2: // reading difference
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      * See http://clirr.sourceforge.net/clirr-core/exegesis.html for the different codes of differences.
173      * <p>
174      * Different types of differences require different parameters set (className is always required):
175      * <ul>
176      * <li><b>1000 (Increased visibility of a class)</b>: no other params but className
177      * <li><b>1001 (Decreased visibility of a class)</b>: no other params but className
178      * <li><b>2000 (Changed from class to interface)</b>: no other params but className
179      * <li><b>2001 (Changed from interface to class)</b>: no other params but className
180      * <li><b>3001 (Removed final modifier from class)</b>: no other params but className
181      * <li><b>3002 (Added final modifier to effectively final class)</b>: no other params but className
182      * <li><b>3003 (Added final modifier to class)</b>: no other params but className
183      * <li><b>3004 (Removed abstract modifier from class)</b>: no other params but className
184      * <li><b>3005 (Added abstract modifier to class)</b>: no other params but className
185      * <li><b>4000 (Added interface to the set of implemented interfaces)</b>: className, to (as a path expression)
186      * <li><b>4001 (Removed interface from the set of implemented interfaces)</b>: className, to (as a path expression)
187      * <li><b>5000 (Added class to the set of superclasses)</b>: className, to (as a path expression)
188      * <li><b>5001 (Removed class from the set of superclasses)</b>: className, to (as a path expression)
189      * <li><b>6000 (added field)</b>: className, field
190      * <li><b>6001 (removed field)</b>: className, field
191      * <li><b>6002 (field value no longer a compile-time constant)</b>: className, field
192      * <li><b>6003 (value of the compile-time constant changed on a field)</b>: className, field
193      * <li><b>6004 (field type changed)</b>: className, field, from, to
194      * <li><b>6005 (field now non-final)</b>: className, field
195      * <li><b>6006 (field now final)</b>: className, field
196      * <li><b>6007 (field now non-static)</b>: className, field
197      * <li><b>6008 (field now static)</b>: className, field
198      * <li><b>6009 (field more accessible)</b>: className, field
199      * <li><b>6010 (field less accessible)</b>: className, field
200      * <li><b>6011 (removed a constant field)</b>: className, field
201      * <li><b>7000 (method now in superclass)</b>: className, method
202      * <li><b>7001 (method now in interface)</b>: className, method
203      * <li><b>7002 (method removed)</b>: className, method
204      * <li><b>7003 (Method Overide Removed)</b>: className, method
205      * <li><b>7004 (Method Argument Count Changed)</b>: className, method
206      * <li><b>7005 (Method Argument Type changed)</b>: className, method, to (to is a full new signature)
207      * <li><b>7006 (Method Return Type changed)</b>: className, method, to (to is just the return type)
208      * <li><b>7007 (Method has been Deprecated)</b>: className, method
209      * <li><b>7008 (Method has been Undeprecated)</b>: className, method
210      * <li><b>7009 (Method is now Less Accessible)</b>: className, method
211      * <li><b>7010 (Method is now More Accessible)</b>: className, method
212      * <li><b>7011 (Method Added)</b>: className, method
213      * <li><b>7012 (Method Added to Interface)</b>: className, method
214      * <li><b>7013 (Abstract Method Added to Class)</b>: className, method
215      * <li><b>7014 (Method now final)</b>: className, method
216      * <li><b>7015 (Method now non-final)</b>: className, method
217      * <li><b>8000 (Class added)</b>: className
218      * <li><b>8001 (Class removed)</b>: className
219      * <li><b>10000 (Class format version increased)</b>: className, from, to (class format version numbers)
220      * <li><b>10001 (Class format version decreased)</b>: className, from, to (class format version numbers)
221      * </ul>
222      *
223      * @parameter
224      * @required
225      */
226     private int differenceType;
227 
228     /**
229      * The name of the class that contains the ignored difference. This can be a path expression.
230      *
231      * @parameter
232      * @required
233      */
234     private String className;
235 
236     /**
237      * The name of the field that should be ignored according to the difference type and optionally 'from' and 'to'
238      * conditions. This is parameter is a regular expression. Note that this must not contain any type information and
239      * only match the field's name. This parameter is an expression (technically a maven path expression).
240      *
241      * @parameter
242      */
243     private String field;
244 
245     /**
246      * The signature of the method that should be ignored according to the {@link #differenceType difference type}. This
247      * parameter is an expression (technically a maven path expression).
248      *
249      * @parameter
250      */
251     private String method;
252 
253     /**
254      * The original type of the field (if it is important for the difference type, otherwise can be left out). Note that
255      * this parameter is ignored when dealing with methods. This parameter is an expression (technically a maven path
256      * expression).
257      *
258      * @parameter
259      */
260     private String from;
261 
262     /**
263      * The "new" type of the field (or method return type) or "new" method signature (if it is important for the
264      * difference type, otherwise can be left out). This parameter is an expression (technically a maven path
265      * expression).
266      *
267      * @parameter
268      */
269     private String to;
270 
271     /**
272      * The reason why ignoring this difference is deemed OK.
273      *
274      * @parameter
275      * @required
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: // added field
393                 return matches6000( apiDiff ) ? Result.matched() : Result.notMatched();
394             case 6001: // removed field
395                 return matches6001( apiDiff ) ? Result.matched() : Result.notMatched();
396             case 6002: // field value no longer a compile-time constant
397                 return matches6002( apiDiff ) ? Result.matched() : Result.notMatched();
398             case 6003: // value of the compile-time constant changed on a field
399                 return matches6003( apiDiff ) ? Result.matched() : Result.notMatched();
400             case 6004: // field type changed
401                 return matches6004( apiDiff ) ? Result.matched() : Result.notMatched();
402             case 6005: // field now non-final
403                 return matches6005( apiDiff ) ? Result.matched() : Result.notMatched();
404             case 6006: // field now final
405                 return matches6006( apiDiff ) ? Result.matched() : Result.notMatched();
406             case 6007: // field now non-static
407                 return matches6007( apiDiff ) ? Result.matched() : Result.notMatched();
408             case 6008: // field now static
409                 return matches6008( apiDiff ) ? Result.matched() : Result.notMatched();
410             case 6009: // field more accessible
411                 return matches6009( apiDiff ) ? Result.matched() : Result.notMatched();
412             case 6010: // field less accessible
413                 return matches6010( apiDiff ) ? Result.matched() : Result.notMatched();
414             case 6011: // removed a constant field
415                 return matches6011( apiDiff ) ? Result.matched() : Result.notMatched();
416             case 7000: // method now in superclass
417                 return matches7000( apiDiff ) ? Result.matched() : Result.notMatched();
418             case 7001: // method now in interface
419                 return matches7001( apiDiff ) ? Result.matched() : Result.notMatched();
420             case 7002: // method removed
421                 return matches7002( apiDiff ) ? Result.matched() : Result.notMatched();
422             case 7003: // Method Overide Removed
423                 return matches7003( apiDiff ) ? Result.matched() : Result.notMatched();
424             case 7004: // Method Argument Count Changed
425                 return matches7004( apiDiff ) ? Result.matched() : Result.notMatched();
426             case 7005: // Method Argument Type changed
427                 return Result.deferred( getDifferentiatorFor7005( apiDiff ) );
428             case 7006: // Method Return Type changed
429                 return matches7006( apiDiff ) ? Result.matched() : Result.notMatched();
430             case 7007: // Method has been Deprecated
431                 return matches7007( apiDiff ) ? Result.matched() : Result.notMatched();
432             case 7008: // Method has been Undeprecated
433                 return matches7008( apiDiff ) ? Result.matched() : Result.notMatched();
434             case 7009: // Method is now Less Accessible
435                 return matches7009( apiDiff ) ? Result.matched() : Result.notMatched();
436             case 7010: // Method is now More Accessible
437                 return matches7010( apiDiff ) ? Result.matched() : Result.notMatched();
438             case 7011: // Method Added
439                 return matches7011( apiDiff ) ? Result.matched() : Result.notMatched();
440             case 7012: // Method Added to Interface
441                 return matches7012( apiDiff ) ? Result.matched() : Result.notMatched();
442             case 7013: // Abstract Method Added to Class
443                 return matches7013( apiDiff ) ? Result.matched() : Result.notMatched();
444             case 7014: // Method now final
445                 return matches7014( apiDiff ) ? Result.matched() : Result.notMatched();
446             case 7015: // Method now non-final
447                 return matches7015( apiDiff ) ? Result.matched() : Result.notMatched();
448             case 8000: // Class added
449                 return Result.matched();
450             case 8001: // Class removed
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      * Added interface to the set of implemented interfaces
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      * Removed interface from the set of implemented interfaces
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      * Added class to the set of superclasses
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      * Removed class from the set of superclasses
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      * added field
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      * removed field
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      * field value no longer a compile-time constant
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      * value of the compile-time constant changed on a field
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      * field type changed
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      * field now non-final
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      * field now final
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      * field now non-static
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      * field now static
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      * field more accessible
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      * field less accessible
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      * removed a constant field
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      * method now in superclass
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      * method now in interface
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      * method removed
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      * Method Overide Removed
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      * Method Argument Count Changed
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      * Method Argument Type changed
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           // 1-based
725           int idx = Integer.parseInt( args[0] ) - 1;
726           String diffNewType = args[1];
727 
728           // construct the new full method signature
729           newMethodSig = replaceNthArgumentType( newMethodSig, idx, diffNewType );
730       }
731       return newMethodSig;
732     }
733 
734     /**
735      * Method Return Type changed
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      * Method has been Deprecated
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      * Method has been Undeprecated
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      * Method is now Less Accessible
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      * Method is now More Accessible
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      * Method Added
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      * Method Added to Interface
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      * Abstract Method Added to Class
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      * Method now final
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      * Method now non-final
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      * Class format version increased
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      * Class format version decreased
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 }