source: josm/trunk/tools/japicc/japi-compliance-checker.pl@ 13391

Last change on this file since 13391 was 12872, checked in by Don-vip, 7 years ago

update to JAPICC 2.3

File size: 163.1 KB
Line 
1#!/usr/bin/perl
2###########################################################################
3# Java API Compliance Checker (JAPICC) 2.3
4# A tool for checking backward compatibility of a Java library API
5#
6# Written by Andrey Ponomarenko
7#
8# Copyright (C) 2011-2017 Andrey Ponomarenko's ABI Laboratory
9#
10# PLATFORMS
11# =========
12# Linux, FreeBSD, Mac OS X, MS Windows
13#
14# REQUIREMENTS
15# ============
16# Linux, FreeBSD, Mac OS X
17# - JDK or OpenJDK - development files (javap, javac)
18# - Perl 5 (5.8 or newer)
19#
20# MS Windows
21# - JDK or OpenJDK (javap, javac)
22# - Active Perl 5 (5.8 or newer)
23#
24# This program is free software: you can redistribute it and/or modify
25# it under the terms of the GNU General Public License or the GNU Lesser
26# General Public License as published by the Free Software Foundation.
27#
28# This program is distributed in the hope that it will be useful,
29# but WITHOUT ANY WARRANTY; without even the implied warranty of
30# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31# GNU General Public License for more details.
32#
33# You should have received a copy of the GNU General Public License
34# and the GNU Lesser General Public License along with this program.
35# If not, see <http://www.gnu.org/licenses/>.
36###########################################################################
37use Getopt::Long;
38Getopt::Long::Configure ("posix_default", "no_ignore_case", "permute");
39use File::Path qw(mkpath rmtree);
40use File::Temp qw(tempdir);
41use File::Basename qw(dirname);
42use Cwd qw(abs_path cwd);
43use Data::Dumper;
44
45my $TOOL_VERSION = "2.3";
46my $API_DUMP_VERSION = "2.2";
47my $API_DUMP_VERSION_MIN = "2.2";
48
49# Internal modules
50my $MODULES_DIR = getModules();
51push(@INC, dirname($MODULES_DIR));
52
53# Basic modules
54my %LoadedModules = ();
55loadModule("Basic");
56loadModule("Input");
57loadModule("Path");
58loadModule("Logging");
59loadModule("Utils");
60loadModule("TypeAttr");
61loadModule("Filter");
62loadModule("SysFiles");
63loadModule("Descriptor");
64loadModule("Mangling");
65
66# Rules DB
67my %RULES_PATH = (
68"Binary" => $MODULES_DIR."/RulesBin.xml",
69"Source" => $MODULES_DIR."/RulesSrc.xml");
70
71my $CmdName = getFilename($0);
72
73my %HomePage = (
74 "Dev"=>"https://github.com/lvc/japi-compliance-checker",
75 "Doc"=>"https://lvc.github.io/japi-compliance-checker/"
76);
77
78my $ShortUsage = "Java API Compliance Checker (JAPICC) $TOOL_VERSION
79A tool for checking backward compatibility of a Java library API
80Copyright (C) 2017 Andrey Ponomarenko's ABI Laboratory
81License: GNU LGPL or GNU GPL
82
83Usage: $CmdName [options]
84Example: $CmdName OLD.jar NEW.jar
85
86More info: $CmdName --help";
87
88if($#ARGV==-1)
89{
90 printMsg("INFO", $ShortUsage);
91 exit(0);
92}
93
94GetOptions("h|help!" => \$In::Opt{"Help"},
95 "v|version!" => \$In::Opt{"ShowVersion"},
96 "dumpversion!" => \$In::Opt{"DumpVersion"},
97# general options
98 "l|lib|library=s" => \$In::Opt{"TargetLib"},
99 "d1|old|o=s" => \$In::Desc{1}{"Path"},
100 "d2|new|n=s" => \$In::Desc{2}{"Path"},
101# extra options
102 "client|app=s" => \$In::Opt{"ClientPath"},
103 "binary|bin!" => \$In::Opt{"BinaryOnly"},
104 "source|src!" => \$In::Opt{"SourceOnly"},
105 "v1|version1|vnum=s" => \$In::Desc{1}{"TargetVersion"},
106 "v2|version2=s" => \$In::Desc{2}{"TargetVersion"},
107 "s|strict!" => \$In::Opt{"StrictCompat"},
108 "keep-internal!" => \$In::Opt{"KeepInternal"},
109 "skip-internal-packages|skip-internal=s" => \$In::Opt{"SkipInternalPackages"},
110 "skip-internal-types=s" => \$In::Opt{"SkipInternalTypes"},
111 "dump|dump-api=s" => \$In::Opt{"DumpAPI"},
112 "check-packages=s" => \$In::Opt{"CheckPackages"},
113 "classes-list=s" => \$In::Opt{"ClassListPath"},
114 "annotations-list=s" => \$In::Opt{"AnnotationsListPath"},
115 "skip-annotations-list=s" => \$In::Opt{"SkipAnnotationsListPath"},
116 "skip-deprecated!" => \$In::Opt{"SkipDeprecated"},
117 "skip-classes=s" => \$In::Opt{"SkipClassesList"},
118 "skip-packages=s" => \$In::Opt{"SkipPackagesList"},
119 "short" => \$In::Opt{"ShortMode"},
120 "dump-path=s" => \$In::Opt{"OutputDumpPath"},
121 "report-path=s" => \$In::Opt{"OutputReportPath"},
122 "bin-report-path=s" => \$In::Opt{"BinaryReportPath"},
123 "src-report-path=s" => \$In::Opt{"SourceReportPath"},
124 "quick!" => \$In::Opt{"Quick"},
125 "sort!" => \$In::Opt{"SortDump"},
126 "show-access!" => \$In::Opt{"ShowAccess"},
127 "limit-affected=s" => \$In::Opt{"AffectLimit"},
128 "hide-templates!" => \$In::Opt{"HideTemplates"},
129 "show-packages!" => \$In::Opt{"ShowPackages"},
130 "compact!" => \$In::Opt{"Compact"},
131 "added-annotations!" => \$In::Opt{"AddedAnnotations"},
132 "removed-annotations!" => \$In::Opt{"RemovedAnnotations"},
133 "count-methods=s" => \$In::Opt{"CountMethods"},
134 "dep1=s" => \$In::Desc{1}{"DepDump"},
135 "dep2=s" => \$In::Desc{2}{"DepDump"},
136 "old-style!" => \$In::Opt{"OldStyle"},
137# other options
138 "test!" => \$In::Opt{"TestTool"},
139 "debug!" => \$In::Opt{"Debug"},
140 "title=s" => \$In::Opt{"TargetTitle"},
141 "jdk-path=s" => \$In::Opt{"JdkPath"},
142 "external-css=s" => \$In::Opt{"ExternCss"},
143 "external-js=s" => \$In::Opt{"ExternJs"},
144# deprecated
145 "minimal!" => \$In::Opt{"Minimal"},
146 "hide-packages!" => \$In::Opt{"HidePackages"},
147# private
148 "all-affected!" => \$In::Opt{"AllAffected"}
149) or errMsg();
150
151if(@ARGV)
152{
153 if($#ARGV==1)
154 { # japi-compliance-checker OLD.jar NEW.jar
155 $In::Desc{1}{"Path"} = $ARGV[0];
156 $In::Desc{2}{"Path"} = $ARGV[1];
157 }
158 else {
159 errMsg();
160 }
161}
162
163sub errMsg()
164{
165 printMsg("INFO", "\n".$ShortUsage);
166 exit(getErrorCode("Error"));
167}
168
169my $HelpMessage = "
170NAME:
171 Java API Compliance Checker ($CmdName)
172 Check backward compatibility of a Java library API
173
174DESCRIPTION:
175 Java API Compliance Checker (JAPICC) is a tool for checking backward
176 binary/source compatibility of a Java library API. The tool checks class
177 declarations of old and new versions and analyzes changes that may break
178 compatibility: removed class members, added abstract methods, etc.
179
180 Break of the binary compatibility may result in crash or incorrect behavior
181 of existing clients built with an old library version if they run with a
182 new one. Break of the source compatibility may result in recompilation
183 errors with a new library version.
184
185 The tool is intended for developers of software libraries and maintainers
186 of operating systems who are interested in ensuring backward compatibility,
187 i.e. allow old clients to run or to be recompiled with newer library
188 versions.
189
190 This tool is free software: you can redistribute it and/or modify it
191 under the terms of the GNU LGPL or GNU GPL.
192
193USAGE:
194 $CmdName [options]
195
196EXAMPLE 1:
197 $CmdName OLD.jar NEW.jar
198
199EXAMPLE 2:
200 $CmdName -lib NAME -old OLD.xml -new NEW.xml
201 OLD.xml and NEW.xml are XML-descriptors:
202
203 <version>
204 1.0
205 </version>
206
207 <archives>
208 /path1/to/JAR(s)/
209 /path2/to/JAR(s)/
210 ...
211 </archives>
212
213INFORMATION OPTIONS:
214 -h|-help
215 Print this help.
216
217 -v|-version
218 Print version information.
219
220 -dumpversion
221 Print the tool version ($TOOL_VERSION) and don't do anything else.
222
223GENERAL OPTIONS:
224 -l|-library NAME
225 Library name (without version).
226
227 -old|-d1 PATH
228 Descriptor of the 1st (old) library version.
229 It may be one of the following:
230
231 1. Java archive (*.jar)
232 2. XML-descriptor (VERSION.xml file):
233
234 <version>
235 1.0
236 </version>
237
238 <archives>
239 /path1/to/JAR(s)/
240 /path2/to/JAR(s)/
241 ...
242 </archives>
243
244 ...
245
246 3. API dump generated by -dump option
247
248 If you are using *.jar as a descriptor then you should
249 specify version numbers with -v1 and -v2 options too.
250 If version numbers are not specified then the tool will
251 try to detect them automatically.
252
253 -new|-d2 PATH
254 Descriptor of the 2nd (new) library version.
255
256EXTRA OPTIONS:
257 -client|-app PATH
258 This option allows to specify the client Java archive that should be
259 checked for portability to the new library version.
260
261 -binary|-bin
262 Show \"Binary\" compatibility problems only.
263 Generate report to \"bin_compat_report.html\".
264
265 -source|-src
266 Show \"Source\" compatibility problems only.
267 Generate report to \"src_compat_report.html\".
268
269 -v1|-version1 NUM
270 Specify 1st API version outside the descriptor. This option is needed
271 if you have prefered an alternative descriptor type (see -d1 option).
272
273 In general case you should specify it in the XML descriptor:
274 <version>
275 VERSION
276 </version>
277
278 -v2|-version2 NUM
279 Specify 2nd library version outside the descriptor.
280
281 -vnum NUM
282 Specify the library version in the generated API dump.
283
284 -s|-strict
285 Treat all API compatibility warnings as problems.
286
287 -keep-internal
288 Do not skip checking of these packages:
289 *impl*
290 *internal*
291 *examples*
292
293 -skip-internal-packages PATTERN
294 Do not check packages matched by the regular expression.
295
296 -skip-internal-types PATTERN
297 Do not check types (classes and interfaces) matched by the regular
298 expression. It's matched against full qualified type names (e.g.
299 'org.xyz.Name<T>'). It has to match any part of type name.
300
301 -dump|-dump-api PATH
302 Dump library API to gzipped TXT format file. You can transfer it
303 anywhere and pass instead of the descriptor. Also it may be used
304 for debugging the tool. PATH is the path to the Java archive or
305 XML descriptor of the library.
306
307 -check-packages PATTERN
308 Check packages matched by the regular expression. Other packages
309 will not be checked.
310
311 -classes-list PATH
312 This option allows to specify a file with a list
313 of classes that should be checked, other classes will not be checked.
314
315 -annotations-list PATH
316 Specifies a file with a list of annotations. The tool will check only
317 classes annotated by the annotations from the list. Other classes
318 will not be checked.
319
320 -skip-annotations-list PATH
321 Skip checking of classes annotated by the annotations in the list.
322
323 -skip-deprecated
324 Skip analysis of deprecated methods and classes.
325
326 -skip-classes PATH
327 This option allows to specify a file with a list
328 of classes that should not be checked.
329
330 -skip-packages PATH
331 This option allows to specify a file with a list
332 of packages that should not be checked.
333
334 -short
335 Do not list added/removed methods.
336
337 -dump-path PATH
338 Specify a *.dump file path where to generate an API dump.
339 Default:
340 api_dumps/LIB_NAME/VERSION/API.dump
341
342 -report-path PATH
343 Path to compatibility report.
344 Default:
345 compat_reports/LIB_NAME/V1_to_V2/compat_report.html
346
347 -bin-report-path PATH
348 Path to \"Binary\" compatibility report.
349 Default:
350 compat_reports/LIB_NAME/V1_to_V2/bin_compat_report.html
351
352 -src-report-path PATH
353 Path to \"Source\" compatibility report.
354 Default:
355 compat_reports/LIB_NAME/V1_to_V2/src_compat_report.html
356
357 -quick
358 Quick analysis.
359 Disabled:
360 - analysis of method parameter names
361 - analysis of class field values
362 - analysis of usage of added abstract methods
363 - distinction of deprecated methods and classes
364
365 -sort
366 Enable sorting of data in API dumps.
367
368 -show-access
369 Show access level of non-public methods listed in the report.
370
371 -hide-templates
372 Hide template parameters in the report.
373
374 -hide-packages
375 -minimal
376 Do nothing.
377
378 -show-packages
379 Show package names in the report.
380
381 -limit-affected LIMIT
382 The maximum number of affected methods listed under the description
383 of the changed type in the report.
384
385 -compact
386 Try to simplify formatting and reduce size of the report (for a big
387 set of changes).
388
389 -added-annotations
390 Apply filters by annotations only to new version of the library.
391
392 -removed-annotations
393 Apply filters by annotations only to old version of the library.
394
395 -count-methods PATH
396 Count total public methods in the API dump.
397
398 -dep1 PATH
399 -dep2 PATH
400 Path to the API dump of the required dependency archive. It will
401 be used to resolve overwritten methods and more.
402
403 -old-style
404 Generate old-style report.
405
406OTHER OPTIONS:
407 -test
408 Run internal tests. Create two incompatible versions of a sample library
409 and run the tool to check them for compatibility. This option allows to
410 check if the tool works correctly in the current environment.
411
412 -debug
413 Debugging mode. Print debug info on the screen. Save intermediate
414 analysis stages in the debug directory:
415 debug/LIB_NAME/VER/
416
417 Also consider using -dump option for debugging the tool.
418
419 -title NAME
420 Change library name in the report title to NAME. By default
421 will be displayed a name specified by -l option.
422
423 -jdk-path PATH
424 Path to the JDK install tree (e.g. /usr/lib/jvm/java-7-openjdk-amd64).
425
426 -external-css PATH
427 Generate CSS styles file to PATH. This helps to save space when
428 generating thousands of reports.
429
430 -external-js PATH
431 Generate JS script file to PATH.
432
433REPORT:
434 Compatibility report will be generated to:
435 compat_reports/LIB_NAME/V1_to_V2/compat_report.html
436
437EXIT CODES:
438 0 - Compatible. The tool has run without any errors.
439 non-zero - Incompatible or the tool has run with errors.
440
441MORE INFO:
442 ".$HomePage{"Doc"}."
443 ".$HomePage{"Dev"};
444
445sub helpMsg() {
446 printMsg("INFO", $HelpMessage."\n");
447}
448
449#Aliases
450my (%MethodInfo, %TypeInfo, %TName_Tid) = ();
451
452my %TName_Tid_Generic = ();
453
454#Separate checked and unchecked exceptions
455my %KnownRuntimeExceptions= map {$_=>1} (
456 "java.lang.AnnotationTypeMismatchException",
457 "java.lang.ArithmeticException",
458 "java.lang.ArrayStoreException",
459 "java.lang.BufferOverflowException",
460 "java.lang.BufferUnderflowException",
461 "java.lang.CannotRedoException",
462 "java.lang.CannotUndoException",
463 "java.lang.ClassCastException",
464 "java.lang.CMMException",
465 "java.lang.ConcurrentModificationException",
466 "java.lang.DataBindingException",
467 "java.lang.DOMException",
468 "java.lang.EmptyStackException",
469 "java.lang.EnumConstantNotPresentException",
470 "java.lang.EventException",
471 "java.lang.IllegalArgumentException",
472 "java.lang.IllegalMonitorStateException",
473 "java.lang.IllegalPathStateException",
474 "java.lang.IllegalStateException",
475 "java.lang.ImagingOpException",
476 "java.lang.IncompleteAnnotationException",
477 "java.lang.IndexOutOfBoundsException",
478 "java.lang.JMRuntimeException",
479 "java.lang.LSException",
480 "java.lang.MalformedParameterizedTypeException",
481 "java.lang.MirroredTypeException",
482 "java.lang.MirroredTypesException",
483 "java.lang.MissingResourceException",
484 "java.lang.NegativeArraySizeException",
485 "java.lang.NoSuchElementException",
486 "java.lang.NoSuchMechanismException",
487 "java.lang.NullPointerException",
488 "java.lang.ProfileDataException",
489 "java.lang.ProviderException",
490 "java.lang.RasterFormatException",
491 "java.lang.RejectedExecutionException",
492 "java.lang.SecurityException",
493 "java.lang.SystemException",
494 "java.lang.TypeConstraintException",
495 "java.lang.TypeNotPresentException",
496 "java.lang.UndeclaredThrowableException",
497 "java.lang.UnknownAnnotationValueException",
498 "java.lang.UnknownElementException",
499 "java.lang.UnknownEntityException",
500 "java.lang.UnknownTypeException",
501 "java.lang.UnmodifiableSetException",
502 "java.lang.UnsupportedOperationException",
503 "java.lang.WebServiceException",
504 "java.lang.WrongMethodTypeException"
505);
506
507#java.lang.Object
508my %JavaObjectMethod = (
509
510 "java/lang/Object.clone:()Ljava/lang/Object;" => 1,
511 "java/lang/Object.equals:(Ljava/lang/Object;)Z" => 1,
512 "java/lang/Object.finalize:()V" => 1,
513 "java/lang/Object.getClass:()Ljava/lang/Class;" => 1,
514 "java/lang/Object.hashCode:()I" => 1,
515 "java/lang/Object.notify:()V" => 1,
516 "java/lang/Object.notifyAll:()V" => 1,
517 "java/lang/Object.toString:()Ljava/lang/String;" => 1,
518 "java/lang/Object.wait:()V" => 1,
519 "java/lang/Object.wait:(J)V" => 1,
520 "java/lang/Object.wait:(JI)V" => 1
521);
522
523#Global variables
524my %Cache;
525my %RESULT;
526my $TOP_REF = "<a class='top_ref' href='#Top'>to the top</a>";
527
528#Types
529my %CheckedTypes;
530
531#Classes
532my %LibArchives;
533my %Class_Methods;
534my %Class_Methods_Generic;
535my %Class_AbstractMethods;
536my %Class_Fields;
537my %ClassMethod_AddedUsed;
538my %Class_Constructed;
539
540#Methods
541my %CheckedMethods;
542my %MethodUsed;
543my %OldMethodSignature;
544
545#Merging
546my %AddedMethod_Abstract;
547my %RemovedMethod_Abstract;
548my %ChangedReturnFromVoid;
549my %CompatRules;
550my %IncompleteRules;
551my %UnknownRules;
552
553#Report
554my %TypeChanges;
555
556#Recursion locks
557my @RecurTypes;
558
559#Problem descriptions
560my %CompatProblems;
561my %TotalAffected;
562
563#Speedup
564my %TypeProblemsIndex;
565
566#Rerort
567my $ContentID = 1;
568my $ContentSpanStart = "<span class=\"section\" onclick=\"sC(this, 'CONTENT_ID')\">\n";
569my $ContentSpanStart_Affected = "<span class=\"sect_aff\" onclick=\"sC(this, 'CONTENT_ID')\">\n";
570my $ContentSpanEnd = "</span>\n";
571my $ContentDivStart = "<div id=\"CONTENT_ID\" style=\"display:none;\">\n";
572my $ContentDivEnd = "</div>\n";
573my $Content_Counter = 0;
574
575sub getModules()
576{
577 my $TOOL_DIR = dirname($0);
578 if(not $TOOL_DIR)
579 { # patch for MS Windows
580 $TOOL_DIR = ".";
581 }
582 my @SEARCH_DIRS = (
583 # tool's directory
584 abs_path($TOOL_DIR),
585 # relative path to modules
586 abs_path($TOOL_DIR)."/../share/japi-compliance-checker",
587 # install path
588 'MODULES_INSTALL_PATH'
589 );
590 foreach my $DIR (@SEARCH_DIRS)
591 {
592 if($DIR!~/\A(\/|\w+:[\/\\])/)
593 { # relative path
594 $DIR = abs_path($TOOL_DIR)."/".$DIR;
595 }
596 if(-d $DIR."/modules") {
597 return $DIR."/modules";
598 }
599 }
600
601 print STDERR "ERROR: can't find modules (Did you installed the tool by 'make install' command?)\n";
602 exit(9); # Module_Error
603}
604
605sub loadModule($)
606{
607 my $Name = $_[0];
608 if(defined $LoadedModules{$Name}) {
609 return;
610 }
611 my $Path = $MODULES_DIR."/Internals/$Name.pm";
612 if(not -f $Path)
613 {
614 print STDERR "can't access \'$Path\'\n";
615 exit(2);
616 }
617 require $Path;
618 $LoadedModules{$Name} = 1;
619}
620
621sub readModule($$)
622{
623 my ($Module, $Name) = @_;
624 my $Path = $MODULES_DIR."/Internals/$Module/".$Name;
625 if(not -f $Path) {
626 exitStatus("Module_Error", "can't access \'$Path\'");
627 }
628 return readFile($Path);
629}
630
631sub mergeClasses()
632{
633 my %ReportedRemoved = undef;
634
635 foreach my $ClassName (keys(%{$Class_Methods{1}}))
636 {
637 next if(not $ClassName);
638 my $Type1_Id = $TName_Tid{1}{$ClassName};
639 my $Type1 = getType($Type1_Id, 1);
640
641 if($Type1->{"Type"}!~/class|interface/) {
642 next;
643 }
644
645 if(defined $Type1->{"Access"}
646 and $Type1->{"Access"}=~/private/) {
647 next;
648 }
649
650 if(not classFilter($Type1, 1, 1)) {
651 next;
652 }
653
654 my $GenericName = getGeneric($ClassName);
655 my $Type2_Id = undef;
656
657 if(defined $TName_Tid{2}{$ClassName}) {
658 $Type2_Id = $TName_Tid{2}{$ClassName};
659 }
660 elsif(defined $TName_Tid_Generic{2}{$GenericName}) {
661 $Type2_Id = $TName_Tid_Generic{2}{$GenericName};
662 }
663
664 if($Type2_Id)
665 {
666 my $TName1 = $Type1->{"Name"};
667 my $TName2 = getTypeName($Type2_Id, 2);
668
669 my $Generic1 = (index($TName1, "<")!=-1);
670 my $Generic2 = (index($TName2, "<")!=-1);
671
672 if($Generic1 ne $Generic2)
673 { # removed generic parameters
674 foreach my $Method (keys(%{$Class_Methods{1}{$ClassName}}))
675 {
676 if(not methodFilter($Method, 1)) {
677 next;
678 }
679
680 $CheckedTypes{$ClassName} = 1;
681 $CheckedMethods{$Method} = 1;
682
683 if($Type1->{"Type"} eq "class")
684 {
685 if($Generic1)
686 {
687 %{$CompatProblems{$Method}{"Class_Became_Raw"}{"this"}} = (
688 "Type_Name"=>$ClassName,
689 "New_Value"=>$TName2,
690 "Target"=>$ClassName);
691 }
692 else
693 {
694 %{$CompatProblems{$Method}{"Class_Became_Generic"}{"this"}} = (
695 "Type_Name"=>$ClassName,
696 "New_Value"=>$TName2,
697 "Target"=>$ClassName);
698 }
699 }
700 else
701 {
702 if($Generic1)
703 {
704 %{$CompatProblems{$Method}{"Interface_Became_Raw"}{"this"}} = (
705 "Type_Name"=>$ClassName,
706 "New_Value"=>$TName2,
707 "Target"=>$ClassName);
708 }
709 else
710 {
711 %{$CompatProblems{$Method}{"Interface_Became_Generic"}{"this"}} = (
712 "Type_Name"=>$ClassName,
713 "New_Value"=>$TName2,
714 "Target"=>$ClassName);
715 }
716 }
717 }
718 }
719 }
720 else
721 { # classes and interfaces with public methods
722 foreach my $Method (keys(%{$Class_Methods{1}{$ClassName}}))
723 {
724 if(not methodFilter($Method, 1)) {
725 next;
726 }
727
728 $CheckedTypes{$ClassName} = 1;
729 $CheckedMethods{$Method} = 1;
730
731 if($Type1->{"Type"} eq "class")
732 {
733 %{$CompatProblems{$Method}{"Removed_Class"}{"this"}} = (
734 "Type_Name"=>$ClassName,
735 "Target"=>$ClassName);
736 }
737 else
738 {
739 %{$CompatProblems{$Method}{"Removed_Interface"}{"this"}} = (
740 "Type_Name"=>$ClassName,
741 "Target"=>$ClassName);
742 }
743
744 $ReportedRemoved{$ClassName} = 1;
745 }
746 }
747 }
748
749 foreach my $Class_Id (keys(%{$TypeInfo{1}}))
750 {
751 my $Class1 = getType($Class_Id, 1);
752
753 if($Class1->{"Type"}!~/class|interface/) {
754 next;
755 }
756
757 if(defined $Class1->{"Access"}
758 and $Class1->{"Access"}=~/private/) {
759 next;
760 }
761
762 if(not classFilter($Class1, 1, 1)) {
763 next;
764 }
765
766 my $ClassName = $Class1->{"Name"};
767 my $GenericName = getGeneric($ClassName);
768 my $Class2_Id = undef;
769
770 if(defined $TName_Tid{2}{$ClassName}) {
771 $Class2_Id = $TName_Tid{2}{$ClassName};
772 }
773 elsif(defined $TName_Tid_Generic{2}{$GenericName}) {
774 $Class2_Id = $TName_Tid_Generic{2}{$GenericName};
775 }
776
777 if($Class2_Id)
778 { # classes and interfaces with public static fields
779 if(not defined $Class_Methods{1}{$ClassName})
780 {
781 my $Class2 = getType($Class2_Id, 2);
782
783 foreach my $Field (keys(%{$Class1->{"Fields"}}))
784 {
785 my $FieldInfo = $Class1->{"Fields"}{$Field};
786
787 my $FAccess = $FieldInfo->{"Access"};
788 if($FAccess=~/private/) {
789 next;
790 }
791
792 if($FieldInfo->{"Static"})
793 {
794 $CheckedTypes{$ClassName} = 1;
795
796 if(not defined $Class2->{"Fields"}{$Field})
797 {
798 %{$CompatProblems{".client_method"}{"Removed_NonConstant_Field"}{$Field}}=(
799 "Target"=>$Field,
800 "Type_Name"=>$ClassName,
801 "Type_Type"=>$Class1->{"Type"},
802 "Field_Type"=>getTypeName($FieldInfo->{"Type"}, 1));
803 }
804 }
805 }
806 }
807 }
808 else
809 { # removed
810 if(defined $Class1->{"Annotation"})
811 {
812 %{$CompatProblems{".client_method"}{"Removed_Annotation"}{"this"}} = (
813 "Type_Name"=>$ClassName,
814 "Target"=>$ClassName);
815 }
816
817 if(not defined $Class_Methods{1}{$ClassName})
818 {
819 # classes and interfaces with public static fields
820 if(not defined $ReportedRemoved{$ClassName})
821 {
822 foreach my $Field (keys(%{$Class1->{"Fields"}}))
823 {
824 my $FieldInfo = $Class1->{"Fields"}{$Field};
825
826 my $FAccess = $FieldInfo->{"Access"};
827 if($FAccess=~/private/) {
828 next;
829 }
830
831 if($FieldInfo->{"Static"})
832 {
833 $CheckedTypes{$ClassName} = 1;
834
835 if($Class1->{"Type"} eq "class")
836 {
837 %{$CompatProblems{".client_method"}{"Removed_Class"}{"this"}} = (
838 "Type_Name"=>$ClassName,
839 "Target"=>$ClassName);
840 }
841 else
842 {
843 %{$CompatProblems{".client_method"}{"Removed_Interface"}{"this"}} = (
844 "Type_Name"=>$ClassName,
845 "Target"=>$ClassName);
846 }
847 }
848 }
849 }
850 }
851 }
852 }
853}
854
855sub findFieldPair($$)
856{
857 my ($Field_Pos, $Pair_Type) = @_;
858 foreach my $Pair_Name (sort keys(%{$Pair_Type->{"Fields"}}))
859 {
860 if(defined $Pair_Type->{"Fields"}{$Pair_Name})
861 {
862 if($Pair_Type->{"Fields"}{$Pair_Name}{"Pos"} eq $Field_Pos) {
863 return $Pair_Name;
864 }
865 }
866 }
867 return "lost";
868}
869
870my %Severity_Val=(
871 "High"=>3,
872 "Medium"=>2,
873 "Low"=>1,
874 "Safe"=>-1
875);
876
877sub isRecurType($$)
878{
879 foreach (@RecurTypes)
880 {
881 if($_->{"Tid1"} eq $_[0]
882 and $_->{"Tid2"} eq $_[1])
883 {
884 return 1;
885 }
886 }
887 return 0;
888}
889
890sub pushType($$)
891{
892 my %TypeDescriptor = (
893 "Tid1" => $_[0],
894 "Tid2" => $_[1]);
895 push(@RecurTypes, \%TypeDescriptor);
896}
897
898sub getSFormat($)
899{
900 my $Name = $_[0];
901 $Name=~s/\./\//g;
902 return $Name;
903}
904
905sub getConstantValue($$)
906{
907 my ($Value, $ValueType) = @_;
908
909 if(not defined $Value) {
910 return undef;
911 }
912
913 if($Value eq "\@EMPTY_STRING\@") {
914 return "\"\"";
915 }
916 elsif($ValueType eq "java.lang.String") {
917 return "\"".$Value."\"";
918 }
919
920 return $Value;
921}
922
923sub getInvoked($)
924{
925 my $TName = $_[0];
926
927 if(my @Invoked = sort keys(%{$ClassMethod_AddedUsed{$TName}}))
928 {
929 my $MFirst = $Invoked[0];
930 my $MSignature = unmangle($MFirst);
931 $MSignature=~s/\A.+\.(\w+\()/$1/g; # short name
932 my $InvokedBy = $ClassMethod_AddedUsed{$TName}{$MFirst};
933 return ($MSignature, $InvokedBy);
934 }
935
936 return ();
937}
938
939sub mergeTypes($$)
940{
941 my ($Type1_Id, $Type2_Id) = @_;
942 return {} if(not $Type1_Id or not $Type2_Id);
943
944 if(defined $Cache{"mergeTypes"}{$Type1_Id}{$Type2_Id})
945 { # already merged
946 return $Cache{"mergeTypes"}{$Type1_Id}{$Type2_Id};
947 }
948
949 if(isRecurType($Type1_Id, $Type2_Id))
950 { # do not follow to recursive declarations
951 return {};
952 }
953
954 my %Type1 = %{getType($Type1_Id, 1)};
955 my %Type2 = %{getType($Type2_Id, 2)};
956
957 return {} if(not $Type1{"Name"} or not $Type2{"Name"});
958 return {} if(not $Type1{"Archive"} or not $Type2{"Archive"});
959 if($Type1{"Name"} ne $Type2{"Name"})
960 {
961 if(getGeneric($Type1{"Name"}) ne getGeneric($Type2{"Name"}))
962 { # compare type declarations if became generic or raw
963 return {};
964 }
965 }
966
967 if(not classFilter(\%Type1, 1, 1)) {
968 return {};
969 }
970
971 $CheckedTypes{$Type1{"Name"}} = 1;
972
973 my %SubProblems = ();
974
975 if($Type1{"BaseType"} and $Type2{"BaseType"})
976 { # check base type (arrays)
977 return mergeTypes($Type1{"BaseType"}, $Type2{"BaseType"});
978 }
979
980 if($Type2{"Type"}!~/(class|interface)/) {
981 return {};
982 }
983
984 if($Type1{"Type"} eq "class" and not $Class_Constructed{1}{$Type1_Id})
985 { # class cannot be constructed or inherited by clients
986 return {};
987 }
988
989 if($Type1{"Type"} eq "class"
990 and $Type2{"Type"} eq "interface")
991 {
992 %{$SubProblems{"Class_Became_Interface"}{""}}=(
993 "Type_Name"=>$Type1{"Name"});
994
995 return ($Cache{"mergeTypes"}{$Type1_Id}{$Type2_Id} = \%SubProblems);
996 }
997 if($Type1{"Type"} eq "interface"
998 and $Type2{"Type"} eq "class")
999 {
1000 %{$SubProblems{"Interface_Became_Class"}{""}}=(
1001 "Type_Name"=>$Type1{"Name"});
1002
1003 return ($Cache{"mergeTypes"}{$Type1_Id}{$Type2_Id} = \%SubProblems);
1004 }
1005 if(not $Type1{"Final"}
1006 and $Type2{"Final"})
1007 {
1008 %{$SubProblems{"Class_Became_Final"}{""}}=(
1009 "Type_Name"=>$Type1{"Name"},
1010 "Target"=>$Type1{"Name"});
1011 }
1012 if(not $Type1{"Abstract"}
1013 and $Type2{"Abstract"})
1014 {
1015 %{$SubProblems{"Class_Became_Abstract"}{""}}=(
1016 "Type_Name"=>$Type1{"Name"});
1017 }
1018
1019 pushType($Type1_Id, $Type2_Id);
1020
1021 foreach my $AddedMethod (keys(%{$AddedMethod_Abstract{$Type2{"Name"}}}))
1022 {
1023 if($Type1{"Type"} eq "class")
1024 {
1025 if($Type1{"Abstract"})
1026 {
1027 if(my @InvokedBy = sort keys(%{$MethodUsed{2}{$AddedMethod}}))
1028 {
1029 %{$SubProblems{"Abstract_Class_Added_Abstract_Method_Invoked_By_Others"}{getSFormat($AddedMethod)}} = (
1030 "Type_Name"=>$Type1{"Name"},
1031 "Type_Type"=>$Type1{"Type"},
1032 "Target"=>$AddedMethod,
1033 "Invoked_By"=>$InvokedBy[0]);
1034 }
1035 else
1036 {
1037 %{$SubProblems{"Abstract_Class_Added_Abstract_Method"}{getSFormat($AddedMethod)}} = (
1038 "Type_Name"=>$Type1{"Name"},
1039 "Type_Type"=>$Type1{"Type"},
1040 "Target"=>$AddedMethod);
1041 }
1042 }
1043 else
1044 {
1045 %{$SubProblems{"NonAbstract_Class_Added_Abstract_Method"}{getSFormat($AddedMethod)}} = (
1046 "Type_Name"=>$Type1{"Name"},
1047 "Type_Type"=>$Type1{"Type"},
1048 "Target"=>$AddedMethod);
1049 }
1050 }
1051 else
1052 {
1053 if(my @InvokedBy = sort keys(%{$MethodUsed{2}{$AddedMethod}}))
1054 {
1055 %{$SubProblems{"Interface_Added_Abstract_Method_Invoked_By_Others"}{getSFormat($AddedMethod)}} = (
1056 "Type_Name"=>$Type1{"Name"},
1057 "Type_Type"=>$Type1{"Type"},
1058 "Target"=>$AddedMethod,
1059 "Invoked_By"=>$InvokedBy[0]);
1060 }
1061 else
1062 {
1063 %{$SubProblems{"Interface_Added_Abstract_Method"}{getSFormat($AddedMethod)}} = (
1064 "Type_Name"=>$Type1{"Name"},
1065 "Type_Type"=>$Type1{"Type"},
1066 "Target"=>$AddedMethod);
1067 }
1068 }
1069 }
1070 foreach my $RemovedMethod (keys(%{$RemovedMethod_Abstract{$Type1{"Name"}}}))
1071 {
1072 if($Type1{"Type"} eq "class")
1073 {
1074 %{$SubProblems{"Class_Removed_Abstract_Method"}{getSFormat($RemovedMethod)}} = (
1075 "Type_Name"=>$Type1{"Name"},
1076 "Type_Type"=>$Type1{"Type"},
1077 "Target"=>$RemovedMethod);
1078 }
1079 else
1080 {
1081 %{$SubProblems{"Interface_Removed_Abstract_Method"}{getSFormat($RemovedMethod)}} = (
1082 "Type_Name"=>$Type1{"Name"},
1083 "Type_Type"=>$Type1{"Type"},
1084 "Target"=>$RemovedMethod);
1085 }
1086 }
1087 if($Type1{"Type"} eq "class"
1088 and $Type2{"Type"} eq "class")
1089 {
1090 my $SuperClass1 = getType($Type1{"SuperClass"}, 1);
1091 my $SuperClass2 = getType($Type2{"SuperClass"}, 2);
1092
1093 my $SuperClassName1 = $SuperClass1->{"Name"};
1094 my $SuperClassName2 = $SuperClass2->{"Name"};
1095
1096 # Java 6: java.lang.Object
1097 # Java 7: none
1098 if(not $SuperClassName1) {
1099 $SuperClassName1 = "java.lang.Object";
1100 }
1101
1102 if(not $SuperClassName2) {
1103 $SuperClassName2 = "java.lang.Object";
1104 }
1105
1106 if($SuperClassName2 ne $SuperClassName1)
1107 {
1108 if($SuperClassName1 eq "java.lang.Object")
1109 {
1110 if($SuperClass2->{"Abstract"}
1111 and $Type1{"Abstract"} and $Type2{"Abstract"}
1112 and keys(%{$Class_AbstractMethods{2}{$SuperClassName2}}))
1113 {
1114 if(my ($Invoked, $InvokedBy) = getInvoked($Type1{"Name"}))
1115 {
1116 %{$SubProblems{"Abstract_Class_Added_Super_Abstract_Class_Invoked_By_Others"}{""}} = (
1117 "Type_Name"=>$Type1{"Name"},
1118 "Target"=>$SuperClassName2,
1119 "Invoked"=>$Invoked,
1120 "Invoked_By"=>$InvokedBy);
1121 }
1122 else
1123 {
1124 %{$SubProblems{"Abstract_Class_Added_Super_Abstract_Class"}{""}} = (
1125 "Type_Name"=>$Type1{"Name"},
1126 "Target"=>$SuperClassName2);
1127 }
1128 }
1129 else
1130 {
1131 %{$SubProblems{"Added_Super_Class"}{""}} = (
1132 "Type_Name"=>$Type1{"Name"},
1133 "Target"=>$SuperClassName2);
1134 }
1135 }
1136 elsif($SuperClassName2 eq "java.lang.Object")
1137 {
1138 %{$SubProblems{"Removed_Super_Class"}{""}} = (
1139 "Type_Name"=>$Type1{"Name"},
1140 "Target"=>$SuperClassName1);
1141 }
1142 else
1143 {
1144 %{$SubProblems{"Changed_Super_Class"}{""}} = (
1145 "Type_Name"=>$Type1{"Name"},
1146 "Target"=>$SuperClassName1,
1147 "Old_Value"=>$SuperClassName1,
1148 "New_Value"=>$SuperClassName2);
1149 }
1150 }
1151 }
1152 my %SuperInterfaces_Old = map {getTypeName($_, 1) => 1} keys(%{$Type1{"SuperInterface"}});
1153 my %SuperInterfaces_New = map {getTypeName($_, 2) => 1} keys(%{$Type2{"SuperInterface"}});
1154 foreach my $SuperInterface (keys(%SuperInterfaces_New))
1155 {
1156 if(not $SuperInterfaces_Old{$SuperInterface})
1157 {
1158 my $HaveMethods = keys(%{$Class_AbstractMethods{2}{$SuperInterface}});
1159 my $HaveFields = keys(%{$Class_Fields{2}{$SuperInterface}});
1160
1161 if($Type1{"Type"} eq "interface")
1162 {
1163 if($HaveMethods
1164 or $SuperInterface=~/\Ajava\./)
1165 {
1166 if($HaveMethods and checkDefaultImpl(2, $SuperInterface, $Type2{"Name"}))
1167 {
1168 %{$SubProblems{"Interface_Added_Super_Interface_With_Implemented_Methods"}{getSFormat($SuperInterface)}} = (
1169 "Type_Name"=>$Type1{"Name"},
1170 "Target"=>$SuperInterface);
1171 }
1172 else
1173 {
1174 if(my ($Invoked, $InvokedBy) = getInvoked($Type1{"Name"}))
1175 {
1176 %{$SubProblems{"Interface_Added_Super_Interface_Used_By_Others"}{getSFormat($SuperInterface)}} = (
1177 "Type_Name"=>$Type1{"Name"},
1178 "Target"=>$SuperInterface,
1179 "Invoked"=>$Invoked,
1180 "Invoked_By"=>$InvokedBy);
1181 }
1182 else
1183 {
1184 %{$SubProblems{"Interface_Added_Super_Interface"}{getSFormat($SuperInterface)}} = (
1185 "Type_Name"=>$Type1{"Name"},
1186 "Target"=>$SuperInterface);
1187 }
1188 }
1189 }
1190 elsif($HaveFields)
1191 {
1192 %{$SubProblems{"Interface_Added_Super_Constant_Interface"}{getSFormat($SuperInterface)}} = (
1193 "Type_Name"=>$Type2{"Name"},
1194 "Target"=>$SuperInterface);
1195 }
1196 else
1197 {
1198 # empty interface
1199 }
1200 }
1201 else
1202 {
1203 if($Type1{"Abstract"} and $Type2{"Abstract"})
1204 {
1205 if($HaveMethods and checkDefaultImpl(2, $SuperInterface, $Type2{"Name"}))
1206 {
1207 %{$SubProblems{"Abstract_Class_Added_Super_Interface_With_Implemented_Methods"}{getSFormat($SuperInterface)}} = (
1208 "Type_Name"=>$Type1{"Name"},
1209 "Target"=>$SuperInterface);
1210 }
1211 else
1212 {
1213 if(my ($Invoked, $InvokedBy) = getInvoked($Type1{"Name"}))
1214 {
1215 %{$SubProblems{"Abstract_Class_Added_Super_Interface_Invoked_By_Others"}{getSFormat($SuperInterface)}} = (
1216 "Type_Name"=>$Type1{"Name"},
1217 "Target"=>$SuperInterface,
1218 "Invoked"=>$Invoked,
1219 "Invoked_By"=>$InvokedBy);
1220 }
1221 else
1222 {
1223 %{$SubProblems{"Abstract_Class_Added_Super_Interface"}{getSFormat($SuperInterface)}} = (
1224 "Type_Name"=>$Type1{"Name"},
1225 "Target"=>$SuperInterface);
1226 }
1227 }
1228 }
1229 }
1230 }
1231 }
1232 foreach my $SuperInterface (keys(%SuperInterfaces_Old))
1233 {
1234 if(not $SuperInterfaces_New{$SuperInterface})
1235 {
1236 my $HaveMethods = keys(%{$Class_AbstractMethods{1}{$SuperInterface}});
1237 my $HaveFields = keys(%{$Class_Fields{1}{$SuperInterface}});
1238
1239 if($Type1{"Type"} eq "interface")
1240 {
1241 if($HaveMethods
1242 or $SuperInterface=~/\Ajava\./)
1243 {
1244 %{$SubProblems{"Interface_Removed_Super_Interface"}{getSFormat($SuperInterface)}} = (
1245 "Type_Name"=>$Type1{"Name"},
1246 "Type_Type"=>"interface",
1247 "Target"=>$SuperInterface);
1248 }
1249 elsif($HaveFields)
1250 {
1251 %{$SubProblems{"Interface_Removed_Super_Constant_Interface"}{getSFormat($SuperInterface)}} = (
1252 "Type_Name"=>$Type1{"Name"},
1253 "Target"=>$SuperInterface);
1254 }
1255 else {
1256 # empty interface
1257 }
1258 }
1259 else
1260 {
1261 %{$SubProblems{"Class_Removed_Super_Interface"}{getSFormat($SuperInterface)}} = (
1262 "Type_Name"=>$Type1{"Name"},
1263 "Type_Type"=>"class",
1264 "Target"=>$SuperInterface);
1265 }
1266 }
1267 }
1268
1269 foreach my $Field_Name (sort keys(%{$Type1{"Fields"}}))
1270 {# check older fields
1271 my $Access1 = $Type1{"Fields"}{$Field_Name}{"Access"};
1272 if($Access1=~/private/) {
1273 next;
1274 }
1275
1276 my $Field_Pos1 = $Type1{"Fields"}{$Field_Name}{"Pos"};
1277 my $FieldType1_Id = $Type1{"Fields"}{$Field_Name}{"Type"};
1278 my $FieldType1_Name = getTypeName($FieldType1_Id, 1);
1279
1280 if(not $Type2{"Fields"}{$Field_Name})
1281 {# removed fields
1282 my $StraightPair_Name = findFieldPair($Field_Pos1, \%Type2);
1283 if($StraightPair_Name ne "lost" and not $Type1{"Fields"}{$StraightPair_Name}
1284 and $FieldType1_Name eq getTypeName($Type2{"Fields"}{$StraightPair_Name}{"Type"}, 2))
1285 {
1286 if(my $Constant = getConstantValue($Type1{"Fields"}{$Field_Name}{"Value"}, $FieldType1_Name))
1287 {
1288 %{$SubProblems{"Renamed_Constant_Field"}{$Field_Name}}=(
1289 "Target"=>$Field_Name,
1290 "Type_Name"=>$Type1{"Name"},
1291 "Old_Value"=>$Field_Name,
1292 "New_Value"=>$StraightPair_Name,
1293 "Field_Type"=>$FieldType1_Name,
1294 "Field_Value"=>$Constant);
1295 }
1296 else
1297 {
1298 %{$SubProblems{"Renamed_Field"}{$Field_Name}}=(
1299 "Target"=>$Field_Name,
1300 "Type_Name"=>$Type1{"Name"},
1301 "Old_Value"=>$Field_Name,
1302 "New_Value"=>$StraightPair_Name,
1303 "Field_Type"=>$FieldType1_Name);
1304 }
1305 }
1306 else
1307 {
1308 if(my $Constant = getConstantValue($Type1{"Fields"}{$Field_Name}{"Value"}, $FieldType1_Name))
1309 { # has a compile-time constant value
1310 %{$SubProblems{"Removed_Constant_Field"}{$Field_Name}}=(
1311 "Target"=>$Field_Name,
1312 "Type_Name"=>$Type1{"Name"},
1313 "Field_Value"=>$Constant,
1314 "Field_Type"=>$FieldType1_Name,
1315 "Type_Type"=>$Type1{"Type"});
1316 }
1317 else
1318 {
1319 %{$SubProblems{"Removed_NonConstant_Field"}{$Field_Name}}=(
1320 "Target"=>$Field_Name,
1321 "Type_Name"=>$Type1{"Name"},
1322 "Type_Type"=>$Type1{"Type"},
1323 "Field_Type"=>$FieldType1_Name);
1324 }
1325 }
1326 next;
1327 }
1328
1329 my $FieldType2_Id = $Type2{"Fields"}{$Field_Name}{"Type"};
1330 my $FieldType2_Name = getTypeName($FieldType2_Id, 2);
1331
1332 if(not $Type1{"Fields"}{$Field_Name}{"Static"}
1333 and $Type2{"Fields"}{$Field_Name}{"Static"})
1334 {
1335 if(not $Type1{"Fields"}{$Field_Name}{"Value"})
1336 {
1337 %{$SubProblems{"NonConstant_Field_Became_Static"}{$Field_Name}}=(
1338 "Target"=>$Field_Name,
1339 "Field_Type"=>$FieldType1_Name,
1340 "Type_Name"=>$Type1{"Name"});
1341 }
1342 }
1343 elsif($Type1{"Fields"}{$Field_Name}{"Static"}
1344 and not $Type2{"Fields"}{$Field_Name}{"Static"})
1345 {
1346 if($Type1{"Fields"}{$Field_Name}{"Value"})
1347 {
1348 %{$SubProblems{"Constant_Field_Became_NonStatic"}{$Field_Name}}=(
1349 "Target"=>$Field_Name,
1350 "Field_Type"=>$FieldType1_Name,
1351 "Type_Name"=>$Type1{"Name"});
1352 }
1353 else
1354 {
1355 %{$SubProblems{"NonConstant_Field_Became_NonStatic"}{$Field_Name}}=(
1356 "Target"=>$Field_Name,
1357 "Field_Type"=>$FieldType1_Name,
1358 "Type_Name"=>$Type1{"Name"});
1359 }
1360 }
1361 if(not $Type1{"Fields"}{$Field_Name}{"Final"}
1362 and $Type2{"Fields"}{$Field_Name}{"Final"})
1363 {
1364 %{$SubProblems{"Field_Became_Final"}{$Field_Name}}=(
1365 "Target"=>$Field_Name,
1366 "Field_Type"=>$FieldType1_Name,
1367 "Type_Name"=>$Type1{"Name"});
1368 }
1369 elsif($Type1{"Fields"}{$Field_Name}{"Final"}
1370 and not $Type2{"Fields"}{$Field_Name}{"Final"})
1371 {
1372 %{$SubProblems{"Field_Became_NonFinal"}{$Field_Name}}=(
1373 "Target"=>$Field_Name,
1374 "Field_Type"=>$FieldType1_Name,
1375 "Type_Name"=>$Type1{"Name"});
1376 }
1377 my $Access2 = $Type2{"Fields"}{$Field_Name}{"Access"};
1378 if($Access1 eq "public" and $Access2=~/protected|private/
1379 or $Access1 eq "protected" and $Access2=~/private/)
1380 {
1381 if($Access2 eq "package-private")
1382 {
1383 %{$SubProblems{"Changed_Field_Access_To_Package_Private"}{$Field_Name}}=(
1384 "Target"=>$Field_Name,
1385 "Type_Name"=>$Type1{"Name"},
1386 "Old_Value"=>$Access1,
1387 "New_Value"=>$Access2);
1388 }
1389 else
1390 {
1391 %{$SubProblems{"Changed_Field_Access"}{$Field_Name}}=(
1392 "Target"=>$Field_Name,
1393 "Type_Name"=>$Type1{"Name"},
1394 "Old_Value"=>$Access1,
1395 "New_Value"=>$Access2);
1396 }
1397 }
1398
1399 my $Value1 = getConstantValue($Type1{"Fields"}{$Field_Name}{"Value"}, $FieldType1_Name);
1400 my $Value2 = getConstantValue($Type2{"Fields"}{$Field_Name}{"Value"}, $FieldType2_Name);
1401
1402 if($Value1 ne $Value2)
1403 {
1404 if($Value1 and $Value2)
1405 {
1406 if($Type1{"Fields"}{$Field_Name}{"Final"}
1407 and $Type2{"Fields"}{$Field_Name}{"Final"})
1408 {
1409 if($Field_Name=~/(\A|_)(VERSION|VERNUM|BUILDNUMBER|BUILD)(_|\Z)/i)
1410 {
1411 %{$SubProblems{"Changed_Final_Version_Field_Value"}{$Field_Name}}=(
1412 "Target"=>$Field_Name,
1413 "Field_Type"=>$FieldType1_Name,
1414 "Type_Name"=>$Type1{"Name"},
1415 "Old_Value"=>$Value1,
1416 "New_Value"=>$Value2);
1417 }
1418 else
1419 {
1420 %{$SubProblems{"Changed_Final_Field_Value"}{$Field_Name}}=(
1421 "Target"=>$Field_Name,
1422 "Field_Type"=>$FieldType1_Name,
1423 "Type_Name"=>$Type1{"Name"},
1424 "Old_Value"=>$Value1,
1425 "New_Value"=>$Value2);
1426 }
1427 }
1428 }
1429 }
1430
1431 my %Sub_SubChanges = detectTypeChange(\%Type1, \%Type2, $FieldType1_Id, $FieldType2_Id, "Field");
1432 foreach my $Sub_SubProblemType (keys(%Sub_SubChanges))
1433 {
1434 %{$SubProblems{$Sub_SubProblemType}{$Field_Name}}=(
1435 "Target"=>$Field_Name,
1436 "Type_Name"=>$Type1{"Name"});
1437
1438 foreach my $Attr (keys(%{$Sub_SubChanges{$Sub_SubProblemType}}))
1439 {
1440 $SubProblems{$Sub_SubProblemType}{$Field_Name}{$Attr} = $Sub_SubChanges{$Sub_SubProblemType}{$Attr};
1441 }
1442 }
1443
1444 if($FieldType1_Id and $FieldType2_Id)
1445 { # check field type change
1446 my $Sub_SubProblems = mergeTypes($FieldType1_Id, $FieldType2_Id);
1447 my %DupProblems = ();
1448
1449 foreach my $Sub_SubProblemType (sort keys(%{$Sub_SubProblems}))
1450 {
1451 foreach my $Sub_SubLocation (sort {length($a)<=>length($b)} sort keys(%{$Sub_SubProblems->{$Sub_SubProblemType}}))
1452 {
1453 if(not defined $In::Opt{"AllAffected"})
1454 {
1455 if(defined $DupProblems{$Sub_SubProblems->{$Sub_SubProblemType}{$Sub_SubLocation}}) {
1456 next;
1457 }
1458 }
1459
1460 my $NewLocation = ($Sub_SubLocation)?$Field_Name.".".$Sub_SubLocation:$Field_Name;
1461 $SubProblems{$Sub_SubProblemType}{$NewLocation} = $Sub_SubProblems->{$Sub_SubProblemType}{$Sub_SubLocation};
1462
1463 if(not defined $In::Opt{"AllAffected"})
1464 {
1465 $DupProblems{$Sub_SubProblems->{$Sub_SubProblemType}{$Sub_SubLocation}} = 1;
1466 }
1467 }
1468 }
1469 %DupProblems = ();
1470 }
1471 }
1472
1473 foreach my $Field_Name (sort keys(%{$Type2{"Fields"}}))
1474 { # check added fields
1475 if($Type2{"Fields"}{$Field_Name}{"Access"}=~/private/) {
1476 next;
1477 }
1478 my $FieldPos2 = $Type2{"Fields"}{$Field_Name}{"Pos"};
1479 my $FieldType2_Id = $Type2{"Fields"}{$Field_Name}{"Type"};
1480 my $FieldType2_Name = getTypeName($FieldType2_Id, 2);
1481
1482 if(not $Type1{"Fields"}{$Field_Name})
1483 {# added fields
1484 my $StraightPair_Name = findFieldPair($FieldPos2, \%Type1);
1485 if($StraightPair_Name ne "lost" and not $Type2{"Fields"}{$StraightPair_Name}
1486 and getTypeName($Type1{"Fields"}{$StraightPair_Name}{"Type"}, 1) eq $FieldType2_Name)
1487 {
1488 # Already reported as "Renamed_Field" or "Renamed_Constant_Field"
1489 }
1490 else
1491 {
1492 if($Type1{"Type"} eq "interface")
1493 {
1494 %{$SubProblems{"Interface_Added_Field"}{$Field_Name}}=(
1495 "Target"=>$Field_Name,
1496 "Type_Name"=>$Type1{"Name"});
1497 }
1498 else
1499 {
1500 %{$SubProblems{"Class_Added_Field"}{$Field_Name}}=(
1501 "Target"=>$Field_Name,
1502 "Type_Name"=>$Type1{"Name"});
1503 }
1504 }
1505 }
1506 }
1507
1508 pop(@RecurTypes);
1509 return ($Cache{"mergeTypes"}{$Type1_Id}{$Type2_Id} = \%SubProblems);
1510}
1511
1512sub checkDefaultImpl($$$)
1513{ # Check if all abstract methods of the super class have
1514 # default implementations in the class
1515 my ($LVer, $SuperClassName, $ClassName) = @_;
1516
1517 foreach my $Method (keys(%{$Class_AbstractMethods{$LVer}{$SuperClassName}}))
1518 {
1519 if(my $Overridden = findMethod_Class($Method, $ClassName, $LVer))
1520 {
1521 if($MethodInfo{$LVer}{$Overridden}{"Abstract"}) {
1522 return 0;
1523 }
1524 }
1525 else {
1526 return 0;
1527 }
1528 }
1529
1530 return 1;
1531}
1532
1533sub getMSuffix($)
1534{
1535 my $Method = $_[0];
1536 if($Method=~/(\(.*\))/) {
1537 return $1;
1538 }
1539 return "";
1540}
1541
1542sub getMShort($)
1543{
1544 my $Method = $_[0];
1545 if($Method=~/([^\.]+)\:\(/) {
1546 return $1;
1547 }
1548 return "";
1549}
1550
1551sub findMethod($$$$)
1552{
1553 my ($Method, $MethodVersion, $ClassName, $ClassVersion) = @_;
1554
1555 my $GenericName = getGeneric($ClassName);
1556 my $ClassId = undef;
1557
1558 if(defined $TName_Tid{$ClassVersion}{$ClassName}) {
1559 $ClassId = $TName_Tid{$ClassVersion}{$ClassName};
1560 }
1561 elsif(defined $TName_Tid_Generic{$ClassVersion}{$GenericName}) {
1562 $ClassId = $TName_Tid_Generic{$ClassVersion}{$GenericName};
1563 }
1564
1565 if($ClassId)
1566 {
1567 my @Search = ();
1568 if(getTypeType($ClassId, $ClassVersion) eq "class")
1569 {
1570 if(my $SuperClassId = $TypeInfo{$ClassVersion}{$ClassId}{"SuperClass"}) {
1571 push(@Search, $SuperClassId);
1572 }
1573 }
1574
1575 if(not defined $MethodInfo{$MethodVersion}{$Method}
1576 or $MethodInfo{$MethodVersion}{$Method}{"Abstract"})
1577 {
1578 if(my @SuperInterfaces = sort keys(%{$TypeInfo{$ClassVersion}{$ClassId}{"SuperInterface"}})) {
1579 push(@Search, @SuperInterfaces);
1580 }
1581 }
1582
1583 foreach my $SuperId (@Search)
1584 {
1585 if($SuperId eq $ClassId) {
1586 next;
1587 }
1588
1589 my $SuperName = getTypeName($SuperId, $ClassVersion);
1590
1591 if(my $MethodInClass = findMethod_Class($Method, $SuperName, $ClassVersion)) {
1592 return $MethodInClass;
1593 }
1594 elsif(my $MethodInSuperClasses = findMethod($Method, $MethodVersion, $SuperName, $ClassVersion)) {
1595 return $MethodInSuperClasses;
1596 }
1597 }
1598 }
1599
1600 my $TargetSuffix = getMSuffix($Method);
1601 my $TargetShortName = getMShort($Method);
1602
1603 # search in java.lang.Object
1604 foreach my $C (keys(%JavaObjectMethod))
1605 {
1606 if($TargetSuffix eq getMSuffix($C))
1607 {
1608 if($TargetShortName eq getMShort($C)) {
1609 return $C;
1610 }
1611 }
1612 }
1613
1614 return undef;
1615}
1616
1617sub findMethod_Class($$$)
1618{
1619 my ($Method, $ClassName, $ClassVersion) = @_;
1620
1621 my $TargetSuffix = getMSuffix($Method);
1622 my $TargetShortName = getMShort($Method);
1623 my $GenericName = getGeneric($ClassName);
1624
1625 my @Candidates = ();
1626
1627 if(defined $Class_Methods{$ClassVersion}{$ClassName}) {
1628 @Candidates = keys(%{$Class_Methods{$ClassVersion}{$ClassName}});
1629 }
1630 elsif(defined $Class_Methods_Generic{$ClassVersion}{$GenericName}) {
1631 @Candidates = keys(%{$Class_Methods_Generic{$ClassVersion}{$GenericName}});
1632 }
1633 else {
1634 return undef;
1635 }
1636
1637 foreach my $Candidate (sort @Candidates)
1638 { # search for method with the same parameters suffix
1639 next if($MethodInfo{$ClassVersion}{$Candidate}{"Constructor"});
1640
1641 if($TargetSuffix eq getMSuffix($Candidate))
1642 {
1643 if($TargetShortName eq getMShort($Candidate)) {
1644 return $Candidate;
1645 }
1646 }
1647 }
1648
1649 return undef;
1650}
1651
1652sub getBaseSignature($)
1653{
1654 my $Method = $_[0];
1655 $Method=~s/\)(.+)\Z/\)/g;
1656 return $Method;
1657}
1658
1659sub prepareData($)
1660{
1661 my $LVer = $_[0];
1662
1663 if(my $MUsed = $In::API{$LVer}{"MethodUsed"})
1664 {
1665 foreach my $M_Id (keys(%{$MUsed}))
1666 {
1667 my $Name = $MUsed->{$M_Id}{"Name"};
1668 $MethodUsed{$LVer}{$Name} = $MUsed->{$M_Id}{"Used"};
1669 }
1670 }
1671
1672 foreach my $TypeId (keys(%{$TypeInfo{$LVer}}))
1673 {
1674 my $TypeAttr = $TypeInfo{$LVer}{$TypeId};
1675 my $TName = $TypeAttr->{"Name"};
1676
1677 $TName_Tid{$LVer}{$TName} = $TypeId;
1678
1679 if(defined $TypeAttr->{"Archive"})
1680 { # declaration
1681 $TName_Tid_Generic{$LVer}{getGeneric($TName)} = $TypeId;
1682 }
1683
1684 if(not $TypeAttr->{"Dep"})
1685 {
1686 if(my $Archive = $TypeAttr->{"Archive"}) {
1687 $LibArchives{$LVer}{$Archive} = 1;
1688 }
1689 }
1690
1691 foreach my $FieldName (keys(%{$TypeAttr->{"Fields"}}))
1692 {
1693 if($TypeAttr->{"Fields"}{$FieldName}{"Access"}=~/public|protected/) {
1694 $Class_Fields{$LVer}{$TName}{$FieldName} = $TypeAttr->{"Fields"}{$FieldName}{"Type"};
1695 }
1696 }
1697 }
1698
1699 foreach my $Method (keys(%{$MethodInfo{$LVer}}))
1700 {
1701 my $Name = $MethodInfo{$LVer}{$Method}{"Name"};
1702 $MethodInfo{$LVer}{$Name} = delete($MethodInfo{$LVer}{$Method});
1703 }
1704
1705 foreach my $Method (keys(%{$MethodInfo{$LVer}}))
1706 {
1707 my $MAttr = $MethodInfo{$LVer}{$Method};
1708
1709 $MAttr->{"Signature"} = getSignature($Method, $LVer, "Full");
1710
1711 if(my $ClassId = $MAttr->{"Class"}
1712 and $MAttr->{"Access"}=~/public|protected/)
1713 {
1714 my $CName = getTypeName($ClassId, $LVer);
1715 $Class_Methods{$LVer}{$CName}{$Method} = 1;
1716 $Class_Methods_Generic{$LVer}{getGeneric($CName)}{$Method} = 1;
1717
1718 if($MAttr->{"Abstract"}) {
1719 $Class_AbstractMethods{$LVer}{$CName}{$Method} = 1;
1720 }
1721 }
1722
1723 if($MAttr->{"Access"}!~/private/)
1724 {
1725 if($MAttr->{"Constructor"}) {
1726 registerUsage($MAttr->{"Class"}, $LVer);
1727 }
1728 else {
1729 registerUsage($MAttr->{"Return"}, $LVer);
1730 }
1731 }
1732
1733 if($LVer==1 and not $MAttr->{"Constructor"}
1734 and my $BaseSign = getBaseSignature($Method)) {
1735 $OldMethodSignature{$BaseSign} = $Method;
1736 }
1737 }
1738}
1739
1740sub mergeMethods()
1741{
1742 foreach my $Method (sort keys(%{$MethodInfo{1}}))
1743 { # compare methods
1744 next if(not defined $MethodInfo{2}{$Method});
1745
1746 if(not $MethodInfo{1}{$Method}{"Archive"}
1747 or not $MethodInfo{2}{$Method}{"Archive"}) {
1748 next;
1749 }
1750
1751 if(not methodFilter($Method, 1)) {
1752 next;
1753 }
1754
1755 my $ClassId1 = $MethodInfo{1}{$Method}{"Class"};
1756 my $Class1_Name = getTypeName($ClassId1, 1);
1757 my $Class1_Type = getTypeType($ClassId1, 1);
1758
1759 $CheckedTypes{$Class1_Name} = 1;
1760 $CheckedMethods{$Method} = 1;
1761
1762 if(not $MethodInfo{1}{$Method}{"Static"}
1763 and $Class1_Type eq "class" and not $Class_Constructed{1}{$ClassId1})
1764 { # class cannot be constructed or inherited by clients
1765 # non-static method cannot be called
1766 next;
1767 }
1768
1769 # checking attributes
1770 if(not $MethodInfo{1}{$Method}{"Static"}
1771 and $MethodInfo{2}{$Method}{"Static"}) {
1772 %{$CompatProblems{$Method}{"Method_Became_Static"}{""}} = ();
1773 }
1774 elsif($MethodInfo{1}{$Method}{"Static"}
1775 and not $MethodInfo{2}{$Method}{"Static"}) {
1776 %{$CompatProblems{$Method}{"Method_Became_NonStatic"}{""}} = ();
1777 }
1778
1779 if(not $MethodInfo{1}{$Method}{"Synchronized"}
1780 and $MethodInfo{2}{$Method}{"Synchronized"}) {
1781 %{$CompatProblems{$Method}{"Method_Became_Synchronized"}{""}} = ();
1782 }
1783 elsif($MethodInfo{1}{$Method}{"Synchronized"}
1784 and not $MethodInfo{2}{$Method}{"Synchronized"}) {
1785 %{$CompatProblems{$Method}{"Method_Became_NonSynchronized"}{""}} = ();
1786 }
1787
1788 if(not $MethodInfo{1}{$Method}{"Final"}
1789 and $MethodInfo{2}{$Method}{"Final"})
1790 {
1791 if($MethodInfo{1}{$Method}{"Static"}) {
1792 %{$CompatProblems{$Method}{"Static_Method_Became_Final"}{""}} = ();
1793 }
1794 else {
1795 %{$CompatProblems{$Method}{"NonStatic_Method_Became_Final"}{""}} = ();
1796 }
1797 }
1798
1799 my $Access1 = $MethodInfo{1}{$Method}{"Access"};
1800 my $Access2 = $MethodInfo{2}{$Method}{"Access"};
1801
1802 if($Access1 eq "public" and $Access2=~/protected|private/
1803 or $Access1 eq "protected" and $Access2=~/private/)
1804 {
1805 %{$CompatProblems{$Method}{"Changed_Method_Access"}{""}} = (
1806 "Old_Value"=>$Access1,
1807 "New_Value"=>$Access2);
1808 }
1809
1810 my $Class2_Type = getTypeType($MethodInfo{2}{$Method}{"Class"}, 2);
1811
1812 if($Class1_Type eq "class"
1813 and $Class2_Type eq "class")
1814 {
1815 if(not $MethodInfo{1}{$Method}{"Abstract"}
1816 and $MethodInfo{2}{$Method}{"Abstract"})
1817 {
1818 %{$CompatProblems{$Method}{"Method_Became_Abstract"}{""}} = ();
1819 %{$CompatProblems{$Method}{"Class_Method_Became_Abstract"}{"this.".getSFormat($Method)}} = (
1820 "Type_Name"=>$Class1_Name,
1821 "Target"=>$Method);
1822 }
1823 elsif($MethodInfo{1}{$Method}{"Abstract"}
1824 and not $MethodInfo{2}{$Method}{"Abstract"})
1825 {
1826 %{$CompatProblems{$Method}{"Method_Became_NonAbstract"}{""}} = ();
1827 %{$CompatProblems{$Method}{"Class_Method_Became_NonAbstract"}{"this.".getSFormat($Method)}} = (
1828 "Type_Name"=>$Class1_Name,
1829 "Target"=>$Method);
1830 }
1831 }
1832 elsif($Class1_Type eq "interface"
1833 and $Class2_Type eq "interface")
1834 {
1835 if(not $MethodInfo{1}{$Method}{"Abstract"}
1836 and $MethodInfo{2}{$Method}{"Abstract"})
1837 {
1838 %{$CompatProblems{$Method}{"Method_Became_NonDefault"}{""}} = ();
1839 %{$CompatProblems{$Method}{"Interface_Method_Became_NonDefault"}{"this.".getSFormat($Method)}} = (
1840 "Type_Name"=>$Class1_Name,
1841 "Target"=>$Method);
1842 }
1843 elsif($MethodInfo{1}{$Method}{"Abstract"}
1844 and not $MethodInfo{2}{$Method}{"Abstract"})
1845 {
1846 %{$CompatProblems{$Method}{"Method_Became_Default"}{""}} = ();
1847 %{$CompatProblems{$Method}{"Interface_Method_Became_Default"}{"this.".getSFormat($Method)}} = (
1848 "Type_Name"=>$Class1_Name,
1849 "Target"=>$Method);
1850 }
1851 }
1852
1853 my %Exceptions_Old = map {getTypeName($_, 1) => $_} keys(%{$MethodInfo{1}{$Method}{"Exceptions"}});
1854 my %Exceptions_New = map {getTypeName($_, 2) => $_} keys(%{$MethodInfo{2}{$Method}{"Exceptions"}});
1855 foreach my $Exception (keys(%Exceptions_Old))
1856 {
1857 if(not $Exceptions_New{$Exception})
1858 {
1859 my $EType = getType($Exceptions_Old{$Exception}, 1);
1860 my $SuperClass = $EType->{"SuperClass"};
1861
1862 if($KnownRuntimeExceptions{$Exception}
1863 or defined $SuperClass and getTypeName($SuperClass, 1) eq "java.lang.RuntimeException")
1864 {
1865 if(not $MethodInfo{1}{$Method}{"Abstract"}
1866 and not $MethodInfo{2}{$Method}{"Abstract"})
1867 {
1868 %{$CompatProblems{$Method}{"Removed_Unchecked_Exception"}{"this.".getSFormat($Exception)}} = (
1869 "Type_Name"=>$Class1_Name,
1870 "Target"=>$Exception);
1871 }
1872 }
1873 else
1874 {
1875 if($MethodInfo{1}{$Method}{"Abstract"}
1876 and $MethodInfo{2}{$Method}{"Abstract"})
1877 {
1878 %{$CompatProblems{$Method}{"Abstract_Method_Removed_Checked_Exception"}{"this.".getSFormat($Exception)}} = (
1879 "Type_Name"=>$Class1_Name,
1880 "Target"=>$Exception);
1881 }
1882 else
1883 {
1884 %{$CompatProblems{$Method}{"NonAbstract_Method_Removed_Checked_Exception"}{"this.".getSFormat($Exception)}} = (
1885 "Type_Name"=>$Class1_Name,
1886 "Target"=>$Exception);
1887 }
1888 }
1889 }
1890 }
1891
1892 foreach my $Exception (keys(%Exceptions_New))
1893 {
1894 if(not $Exceptions_Old{$Exception})
1895 {
1896 my $EType = getType($Exceptions_New{$Exception}, 2);
1897 my $SuperClass = $EType->{"SuperClass"};
1898
1899 if($KnownRuntimeExceptions{$Exception}
1900 or defined $SuperClass and getTypeName($SuperClass, 2) eq "java.lang.RuntimeException")
1901 {
1902 if(not $MethodInfo{1}{$Method}{"Abstract"}
1903 and not $MethodInfo{2}{$Method}{"Abstract"})
1904 {
1905 %{$CompatProblems{$Method}{"Added_Unchecked_Exception"}{"this.".getSFormat($Exception)}} = (
1906 "Type_Name"=>$Class1_Name,
1907 "Target"=>$Exception);
1908 }
1909 }
1910 else
1911 {
1912 if($MethodInfo{1}{$Method}{"Abstract"}
1913 and $MethodInfo{2}{$Method}{"Abstract"})
1914 {
1915 %{$CompatProblems{$Method}{"Abstract_Method_Added_Checked_Exception"}{"this.".getSFormat($Exception)}} = (
1916 "Type_Name"=>$Class1_Name,
1917 "Target"=>$Exception);
1918 }
1919 else
1920 {
1921 %{$CompatProblems{$Method}{"NonAbstract_Method_Added_Checked_Exception"}{"this.".getSFormat($Exception)}} = (
1922 "Type_Name"=>$Class1_Name,
1923 "Target"=>$Exception);
1924 }
1925 }
1926 }
1927 }
1928
1929 if(defined $MethodInfo{1}{$Method}{"Param"})
1930 {
1931 foreach my $ParamPos (sort {int($a) <=> int($b)} keys(%{$MethodInfo{1}{$Method}{"Param"}}))
1932 { # checking parameters
1933 mergeParameters($Method, $ParamPos, $ParamPos);
1934 }
1935 }
1936
1937 # check object type
1938 my $ObjectType1_Id = $MethodInfo{1}{$Method}{"Class"};
1939 my $ObjectType2_Id = $MethodInfo{2}{$Method}{"Class"};
1940 if($ObjectType1_Id and $ObjectType2_Id)
1941 {
1942 my $SubProblems = mergeTypes($ObjectType1_Id, $ObjectType2_Id);
1943 foreach my $SubProblemType (keys(%{$SubProblems}))
1944 {
1945 foreach my $SubLocation (keys(%{$SubProblems->{$SubProblemType}}))
1946 {
1947 my $NewLocation = ($SubLocation)?"this.".$SubLocation:"this";
1948 $CompatProblems{$Method}{$SubProblemType}{$NewLocation} = $SubProblems->{$SubProblemType}{$SubLocation};
1949 }
1950 }
1951 }
1952 # check return type
1953 my $ReturnType1_Id = $MethodInfo{1}{$Method}{"Return"};
1954 my $ReturnType2_Id = $MethodInfo{2}{$Method}{"Return"};
1955 if($ReturnType1_Id and $ReturnType2_Id)
1956 {
1957 my $SubProblems = mergeTypes($ReturnType1_Id, $ReturnType2_Id);
1958 foreach my $SubProblemType (keys(%{$SubProblems}))
1959 {
1960 foreach my $SubLocation (keys(%{$SubProblems->{$SubProblemType}}))
1961 {
1962 my $NewLocation = ($SubLocation)?"retval.".$SubLocation:"retval";
1963 $CompatProblems{$Method}{$SubProblemType}{$NewLocation} = $SubProblems->{$SubProblemType}{$SubLocation};
1964 }
1965 }
1966 }
1967 }
1968}
1969
1970sub mergeParameters($$$)
1971{
1972 my ($Method, $ParamPos1, $ParamPos2) = @_;
1973 if(not $Method or not defined $MethodInfo{1}{$Method}{"Param"}
1974 or not defined $MethodInfo{2}{$Method}{"Param"}) {
1975 return;
1976 }
1977
1978 my $ParamType1_Id = $MethodInfo{1}{$Method}{"Param"}{$ParamPos1}{"Type"};
1979 my $ParamType2_Id = $MethodInfo{2}{$Method}{"Param"}{$ParamPos2}{"Type"};
1980
1981 if(not $ParamType1_Id or not $ParamType2_Id) {
1982 return;
1983 }
1984
1985 my $Parameter_Name = $MethodInfo{1}{$Method}{"Param"}{$ParamPos1}{"Name"};
1986 my $Parameter_Location = ($Parameter_Name)?$Parameter_Name:showPos($ParamPos1)." Parameter";
1987
1988 # checking type declaration changes
1989 my $SubProblems = mergeTypes($ParamType1_Id, $ParamType2_Id);
1990
1991 my $Type1 = getType($ParamType1_Id, 1);
1992 my $Type2 = getType($ParamType2_Id, 2);
1993
1994 if($Type1->{"Name"} ne $Type2->{"Name"})
1995 {
1996 if(index($Type1->{"Name"}, "...")!=-1 and index($Type2->{"Name"}, "[]")!=-1)
1997 {
1998 %{$CompatProblems{$Method}{"Variable_Arity_To_Array"}{$Parameter_Name}} = (
1999 "Type_Name"=>getTypeName($MethodInfo{1}{$Method}{"Class"}, 1),
2000 "Param_Name"=>$Parameter_Name,
2001 "Old_Value"=>$Type1->{"Name"},
2002 "New_Value"=>$Type2->{"Name"});
2003 }
2004 elsif(index($Type1->{"Name"}, "[]")!=-1 and index($Type2->{"Name"}, "...")!=-1)
2005 {
2006 %{$CompatProblems{$Method}{"Array_To_Variable_Arity"}{$Parameter_Name}} = (
2007 "Type_Name"=>getTypeName($MethodInfo{1}{$Method}{"Class"}, 1),
2008 "Param_Name"=>$Parameter_Name,
2009 "Old_Value"=>$Type1->{"Name"},
2010 "New_Value"=>$Type2->{"Name"});
2011 }
2012 }
2013
2014 foreach my $SubProblemType (keys(%{$SubProblems}))
2015 {
2016 foreach my $SubLocation (keys(%{$SubProblems->{$SubProblemType}}))
2017 {
2018 my $NewLocation = ($SubLocation)?$Parameter_Location.".".$SubLocation:$Parameter_Location;
2019 $CompatProblems{$Method}{$SubProblemType}{$NewLocation} = $SubProblems->{$SubProblemType}{$SubLocation};
2020 }
2021 }
2022}
2023
2024sub detectTypeChange($$$$$)
2025{
2026 my ($Ct1, $Ct2, $Type1_Id, $Type2_Id, $Prefix) = @_;
2027 my %LocalProblems = ();
2028
2029 my $Type1 = getType($Type1_Id, 1);
2030 my $Type2 = getType($Type2_Id, 2);
2031
2032 my $Type1_Name = $Type1->{"Name"};
2033 my $Type2_Name = $Type2->{"Name"};
2034
2035 my $Type1_Show = $Type1_Name;
2036 my $Type2_Show = $Type2_Name;
2037
2038 if(defined $Ct1->{"GenericParam"}
2039 and defined $Ct1->{"GenericParam"}{$Type1_Name})
2040 {
2041 $Type1_Name = getTypeName($Ct1->{"GenericParam"}{$Type1_Name}, 1);
2042 $Type1_Show .= " extends ".$Type1_Name;
2043 }
2044
2045 if(defined $Ct2->{"GenericParam"}
2046 and defined $Ct2->{"GenericParam"}{$Type2_Name})
2047 {
2048 $Type2_Name = getTypeName($Ct2->{"GenericParam"}{$Type2_Name}, 2);
2049 $Type2_Show .= " extends ".$Type2_Name;
2050 }
2051
2052 my $Type1_Base = undef;
2053 my $Type2_Base = undef;
2054
2055 if($Type1->{"Type"} eq "array") {
2056 $Type1_Base = getOneStepBaseType($Type1_Id, 1);
2057 }
2058 else {
2059 $Type1_Base = getBaseType($Type1_Id, 1);
2060 }
2061
2062 if($Type2->{"Type"} eq "array") {
2063 $Type2_Base = getOneStepBaseType($Type2_Id, 2);
2064 }
2065 else {
2066 $Type2_Base = getBaseType($Type2_Id, 2);
2067 }
2068
2069 return () if(not $Type1_Name or not $Type2_Name);
2070 return () if(not $Type1_Base->{"Name"} or not $Type2_Base->{"Name"});
2071
2072 if($Type1_Base->{"Name"} ne $Type2_Base->{"Name"} and $Type1_Name eq $Type2_Name)
2073 {# base type change
2074 %{$LocalProblems{"Changed_".$Prefix."_BaseType"}}=(
2075 "Old_Value"=>$Type1_Base->{"Name"},
2076 "New_Value"=>$Type2_Base->{"Name"});
2077 }
2078 elsif($Type1_Name ne $Type2_Name)
2079 {# type change
2080 %{$LocalProblems{"Changed_".$Prefix."_Type"}}=(
2081 "Old_Value"=>$Type1_Show,
2082 "New_Value"=>$Type2_Show);
2083 }
2084 return %LocalProblems;
2085}
2086
2087sub specChars($)
2088{
2089 my $Str = $_[0];
2090 if(not defined $Str
2091 or $Str eq "") {
2092 return "";
2093 }
2094 $Str=~s/\&([^#]|\Z)/&amp;$1/g;
2095 $Str=~s/</&lt;/g;
2096 $Str=~s/\-\>/&#45;&gt;/g; # &minus;
2097 $Str=~s/>/&gt;/g;
2098 $Str=~s/([^ ])( )([^ ])/$1\@ALONE_SP\@$3/g;
2099 $Str=~s/ /&#160;/g; # &nbsp;
2100 $Str=~s/\@ALONE_SP\@/ /g;
2101 $Str=~s/\n/<br\/>/g;
2102 $Str=~s/\"/&quot;/g;
2103 $Str=~s/\'/&#39;/g;
2104 return $Str;
2105}
2106
2107sub blackName($)
2108{
2109 my $N = $_[0];
2110 return "<span class='iname_b'>".$N."</span>";
2111}
2112
2113sub highLight_ItalicColor($$)
2114{
2115 my ($M, $V) = @_;
2116 return getSignature($M, $V, "Full|HTML|Italic|Color");
2117}
2118
2119sub getSignature($$$)
2120{
2121 my ($Method, $LVer, $Kind) = @_;
2122 if(defined $Cache{"getSignature"}{$LVer}{$Method}{$Kind}) {
2123 return $Cache{"getSignature"}{$LVer}{$Method}{$Kind};
2124 }
2125
2126 # settings
2127 my ($Full, $Html, $Simple, $Italic, $Color,
2128 $ShowParams, $ShowClass, $ShowAttr, $Desc,
2129 $ShowReturn, $Target) = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, undef);
2130
2131 if($Kind=~/Full/) {
2132 $Full = 1;
2133 }
2134 if($Kind=~/HTML/) {
2135 $Html = 1;
2136 }
2137 if($Kind=~/Simple/) {
2138 $Simple = 1;
2139 }
2140 if($Kind=~/Italic/) {
2141 $Italic = 1;
2142 }
2143 if($Kind=~/Color/) {
2144 $Color = 1;
2145 }
2146 if($Kind=~/Target=(\d+)/) {
2147 $Target = $1;
2148 }
2149 if($Kind=~/Param/) {
2150 $ShowParams = 1;
2151 }
2152 if($Kind=~/Class/) {
2153 $ShowClass = 1;
2154 }
2155 if($Kind=~/Attr/) {
2156 $ShowAttr = 1;
2157 }
2158 if($Kind=~/Desc/) {
2159 $Desc = 1;
2160 }
2161 if($Kind=~/Return/) {
2162 $ShowReturn = 1;
2163 }
2164
2165 if(not defined $MethodInfo{$LVer}{$Method}{"ShortName"})
2166 { # from java.lang.Object
2167 if($Html) {
2168 return specChars($Method);
2169 }
2170 else {
2171 return $Method;
2172 }
2173 }
2174
2175 my $Signature = "";
2176
2177 my $ShortName = $MethodInfo{$LVer}{$Method}{"ShortName"};
2178
2179 if($Html) {
2180 $ShortName = specChars($ShortName);
2181 }
2182
2183 $Signature .= $ShortName;
2184
2185 if($Full or $ShowClass)
2186 {
2187 my $Class = getTypeName($MethodInfo{$LVer}{$Method}{"Class"}, $LVer);
2188
2189 if($In::Opt{"HideTemplates"}) {
2190 $Class=~s/<.*>//g;
2191 }
2192
2193 if($Html) {
2194 $Class = specChars($Class);
2195 }
2196
2197 $Signature = $Class.".".$Signature;
2198 }
2199 my @Params = ();
2200
2201 if(defined $MethodInfo{$LVer}{$Method}{"Param"})
2202 {
2203 foreach my $PPos (sort {int($a)<=>int($b)}
2204 keys(%{$MethodInfo{$LVer}{$Method}{"Param"}}))
2205 {
2206 my $PTid = $MethodInfo{$LVer}{$Method}{"Param"}{$PPos}{"Type"};
2207 if(my $PTName = getTypeName($PTid, $LVer))
2208 {
2209 if($In::Opt{"HideTemplates"}) {
2210 $PTName=~s/<.*>//g;
2211 }
2212
2213 if(not $In::Opt{"ShowPackages"}) {
2214 $PTName=~s/(\A|\<\s*|\,\s*)[a-z0-9\.]+\./$1/g;
2215 }
2216
2217 if($Html) {
2218 $PTName = specChars($PTName);
2219 }
2220
2221 if($Full or $ShowParams)
2222 {
2223 my $PName = $MethodInfo{$LVer}{$Method}{"Param"}{$PPos}{"Name"};
2224
2225 if($Simple) {
2226 $PName = "<i>$PName</i>";
2227 }
2228 elsif($Html)
2229 {
2230 my $Style = "param";
2231
2232 if(defined $Target
2233 and $Target==$PPos) {
2234 $PName = "<span class='focus_p'>$PName</span>";
2235 }
2236 elsif($Color) {
2237 $PName = "<span class='color_p'>$PName</span>";
2238 }
2239 else {
2240 $PName = "<i>$PName</i>";
2241 }
2242 }
2243
2244 push(@Params, $PTName." ".$PName);
2245 }
2246 else {
2247 push(@Params, $PTName);
2248 }
2249 }
2250 }
2251 }
2252
2253 if($Simple) {
2254 $Signature = "<b>".$Signature."</b>";
2255 }
2256
2257 if($Html and not $Simple)
2258 {
2259 $Signature .= "&#160;";
2260 if($Desc) {
2261 $Signature .= "<span class='sym_pd'>";
2262 }
2263 else {
2264 $Signature .= "<span class='sym_p'>";
2265 }
2266 if(@Params)
2267 {
2268 foreach my $Pos (0 .. $#Params)
2269 {
2270 my $Name = "";
2271
2272 if($Pos==0) {
2273 $Name .= "(&#160;";
2274 }
2275
2276 $Name .= $Params[$Pos];
2277
2278 $Name = "<span>".$Name."</span>";
2279
2280 if($Pos==$#Params) {
2281 $Name .= "&#160;)";
2282 }
2283 else {
2284 $Name .= ", ";
2285 }
2286
2287 $Signature .= $Name;
2288 }
2289 }
2290 else {
2291 $Signature .= "(&#160;)";
2292 }
2293 $Signature .= "</span>";
2294 }
2295 else
2296 {
2297 if(@Params) {
2298 $Signature .= " ( ".join(", ", @Params)." )";
2299 }
2300 else {
2301 $Signature .= " ( )";
2302 }
2303 }
2304
2305 if($Full or $ShowAttr)
2306 {
2307 if($MethodInfo{$LVer}{$Method}{"Static"}) {
2308 $Signature .= " [static]";
2309 }
2310 elsif($MethodInfo{$LVer}{$Method}{"Abstract"}) {
2311 $Signature .= " [abstract]";
2312 }
2313 }
2314
2315 if($Full)
2316 {
2317 if($In::Opt{"ShowAccess"})
2318 {
2319 if(my $Access = $MethodInfo{$LVer}{$Method}{"Access"})
2320 {
2321 if($Access ne "public") {
2322 $Signature .= " [".$Access."]";
2323 }
2324 }
2325 }
2326 }
2327
2328 if($Full or $ShowReturn)
2329 {
2330 if(my $ReturnId = $MethodInfo{$LVer}{$Method}{"Return"})
2331 {
2332 my $RName = getTypeName($ReturnId, $LVer);
2333
2334 if($In::Opt{"HideTemplates"}) {
2335 $RName=~s/<.*>//g;
2336 }
2337
2338 if(not $In::Opt{"ShowPackages"}) {
2339 $RName=~s/(\A|\<\s*|\,\s*)[a-z0-9\.]+\./$1/g;
2340 }
2341
2342 if($Desc) {
2343 $Signature = "<b>".specChars($RName)."</b> ".$Signature;
2344 }
2345 elsif($Simple) {
2346 $Signature .= " <b>:</b> ".specChars($RName);
2347 }
2348 elsif($Html) {
2349 $Signature .= "<span class='sym_p nowrap'> &#160;<b>:</b>&#160;&#160;".specChars($RName)."</span>";
2350 }
2351 else {
2352 $Signature .= " : ".$RName;
2353 }
2354 }
2355 }
2356
2357 if($Full)
2358 {
2359 if(not $In::Opt{"SkipDeprecated"})
2360 {
2361 if($MethodInfo{$LVer}{$Method}{"Deprecated"}) {
2362 $Signature .= " *DEPRECATED*";
2363 }
2364 }
2365 }
2366
2367 $Signature=~s/java\.lang\.//g;
2368
2369 if($Html)
2370 {
2371 if(not $In::Opt{"SkipDeprecated"}) {
2372 $Signature=~s!(\*deprecated\*)!<span class='deprecated'>$1</span>!ig;
2373 }
2374
2375 $Signature=~s!(\[static\]|\[abstract\]|\[public\]|\[private\]|\[protected\])!<span class='attr'>$1</span>!g;
2376 }
2377
2378 if($Simple) {
2379 $Signature=~s/\[\]/\[ \]/g;
2380 }
2381 elsif($Html)
2382 {
2383 $Signature=~s!\[\]![&#160;]!g;
2384 $Signature=~s!operator=!operator&#160;=!g;
2385 }
2386
2387 return ($Cache{"getSignature"}{$LVer}{$Method}{$Kind} = $Signature);
2388}
2389
2390sub getReportHeader($)
2391{
2392 my $Level = $_[0];
2393 my $Report_Header = "<h1>";
2394 if($Level eq "Source") {
2395 $Report_Header .= "Source compatibility";
2396 }
2397 elsif($Level eq "Binary") {
2398 $Report_Header .= "Binary compatibility";
2399 }
2400 else {
2401 $Report_Header .= "API compatibility";
2402 }
2403 $Report_Header .= " report for the <span style='color:Blue;'>".$In::Opt{"TargetTitle"}."</span> library between <span style='color:Red;'>".$In::Desc{1}{"Version"}."</span> and <span style='color:Red;'>".$In::Desc{2}{"Version"}."</span> versions";
2404 if($In::Opt{"ClientPath"}) {
2405 $Report_Header .= " (concerning portability of the client: <span style='color:Blue;'>".getFilename($In::Opt{"ClientPath"})."</span>)";
2406 }
2407 $Report_Header .= "</h1>\n";
2408 return $Report_Header;
2409}
2410
2411sub getSourceInfo()
2412{
2413 my $CheckedArchives = "<a name='Checked_Archives'></a>";
2414 if($In::Opt{"OldStyle"}) {
2415 $CheckedArchives .= "<h2>Java Archives (".keys(%{$LibArchives{1}}).")</h2>";
2416 }
2417 else {
2418 $CheckedArchives .= "<h2>Java Archives <span class='gray'>&nbsp;".keys(%{$LibArchives{1}})."&nbsp;</span></h2>";
2419 }
2420 $CheckedArchives .= "\n<hr/><div class='jar_list'>\n";
2421 foreach my $ArchivePath (sort {lc($a) cmp lc($b)} keys(%{$LibArchives{1}})) {
2422 $CheckedArchives .= getFilename($ArchivePath)."<br/>\n";
2423 }
2424 $CheckedArchives .= "</div><br/>$TOP_REF<br/>\n";
2425 return $CheckedArchives;
2426}
2427
2428sub getTypeProblemsCount($$)
2429{
2430 my ($TargetSeverity, $Level) = @_;
2431 my $Type_Problems_Count = 0;
2432
2433 foreach my $Type_Name (sort keys(%{$TypeChanges{$Level}}))
2434 {
2435 my %Kinds_Target = ();
2436 foreach my $Kind (sort keys(%{$TypeChanges{$Level}{$Type_Name}}))
2437 {
2438 if($CompatRules{$Level}{$Kind}{"Severity"} ne $TargetSeverity) {
2439 next;
2440 }
2441
2442 foreach my $Location (sort keys(%{$TypeChanges{$Level}{$Type_Name}{$Kind}}))
2443 {
2444 my $Target = $TypeChanges{$Level}{$Type_Name}{$Kind}{$Location}{"Target"};
2445
2446 if($Kinds_Target{$Kind}{$Target}) {
2447 next;
2448 }
2449
2450 $Kinds_Target{$Kind}{$Target} = 1;
2451 $Type_Problems_Count += 1;
2452 }
2453 }
2454 }
2455
2456 return $Type_Problems_Count;
2457}
2458
2459sub showNum($)
2460{
2461 if($_[0])
2462 {
2463 my $Num = cutNum($_[0], 2, 0);
2464 if($Num eq "0")
2465 {
2466 foreach my $P (3 .. 7)
2467 {
2468 $Num = cutNum($_[0], $P, 1);
2469 if($Num ne "0") {
2470 last;
2471 }
2472 }
2473 }
2474 if($Num eq "0") {
2475 $Num = $_[0];
2476 }
2477 return $Num;
2478 }
2479 return $_[0];
2480}
2481
2482sub cutNum($$$)
2483{
2484 my ($num, $digs_to_cut, $z) = @_;
2485 if($num!~/\./)
2486 {
2487 $num .= ".";
2488 foreach (1 .. $digs_to_cut-1) {
2489 $num .= "0";
2490 }
2491 }
2492 elsif($num=~/\.(.+)\Z/ and length($1)<$digs_to_cut-1)
2493 {
2494 foreach (1 .. $digs_to_cut - 1 - length($1)) {
2495 $num .= "0";
2496 }
2497 }
2498 elsif($num=~/\d+\.(\d){$digs_to_cut,}/) {
2499 $num=sprintf("%.".($digs_to_cut-1)."f", $num);
2500 }
2501 $num=~s/\.[0]+\Z//g;
2502 if($z) {
2503 $num=~s/(\.[1-9]+)[0]+\Z/$1/g;
2504 }
2505 return $num;
2506}
2507
2508sub getSummary($)
2509{
2510 my $Level = $_[0];
2511 my ($Added, $Removed, $M_Problems_High, $M_Problems_Medium, $M_Problems_Low,
2512 $T_Problems_High, $T_Problems_Medium, $T_Problems_Low, $M_Other, $T_Other) = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
2513
2514 %{$RESULT{$Level}} = (
2515 "Problems"=>0,
2516 "Warnings"=>0,
2517 "Affected"=>0);
2518
2519 # check rules
2520 foreach my $Method (sort keys(%CompatProblems))
2521 {
2522 foreach my $Kind (keys(%{$CompatProblems{$Method}}))
2523 {
2524 if(not defined $CompatRules{"Binary"}{$Kind} and not defined $CompatRules{"Source"}{$Kind})
2525 { # unknown rule
2526 if(not $UnknownRules{$Level}{$Kind})
2527 { # only one warning
2528 printMsg("WARNING", "unknown rule \"$Kind\" (\"$Level\")");
2529 $UnknownRules{$Level}{$Kind}=1;
2530 }
2531 }
2532 }
2533 }
2534
2535 foreach my $Method (sort keys(%CompatProblems))
2536 {
2537 foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
2538 {
2539 if($CompatRules{$Level}{$Kind}{"Kind"} eq "Methods")
2540 {
2541 my $Severity = $CompatRules{$Level}{$Kind}{"Severity"};
2542 foreach my $Loc (sort keys(%{$CompatProblems{$Method}{$Kind}}))
2543 {
2544 if($Kind eq "Added_Method")
2545 {
2546 if($Level eq "Source")
2547 {
2548 if($ChangedReturnFromVoid{$Method}) {
2549 next;
2550 }
2551 }
2552 $Added+=1;
2553 }
2554 elsif($Kind eq "Removed_Method")
2555 {
2556 if($Level eq "Source")
2557 {
2558 if($ChangedReturnFromVoid{$Method}) {
2559 next;
2560 }
2561 }
2562 $Removed+=1;
2563 $TotalAffected{$Level}{$Method} = $Severity;
2564 }
2565 else
2566 {
2567 if($Severity eq "Safe") {
2568 $M_Other += 1;
2569 }
2570 elsif($Severity eq "High") {
2571 $M_Problems_High+=1;
2572 }
2573 elsif($Severity eq "Medium") {
2574 $M_Problems_Medium+=1;
2575 }
2576 elsif($Severity eq "Low") {
2577 $M_Problems_Low+=1;
2578 }
2579 if(($Severity ne "Low" or $In::Opt{"StrictCompat"})
2580 and $Severity ne "Safe") {
2581 $TotalAffected{$Level}{$Method} = $Severity;
2582 }
2583 }
2584 }
2585 }
2586 }
2587 }
2588
2589 my %MethodTypeIndex = ();
2590
2591 foreach my $Method (sort keys(%CompatProblems))
2592 {
2593 foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
2594 {
2595 if($CompatRules{$Level}{$Kind}{"Kind"} eq "Types")
2596 {
2597 my $Severity = $CompatRules{$Level}{$Kind}{"Severity"};
2598 if(($Severity ne "Low" or $In::Opt{"StrictCompat"})
2599 and $Severity ne "Safe")
2600 {
2601 if(my $Sev = $TotalAffected{$Level}{$Method})
2602 {
2603 if($Severity_Val{$Severity}>$Severity_Val{$Sev}) {
2604 $TotalAffected{$Level}{$Method} = $Severity;
2605 }
2606 }
2607 else {
2608 $TotalAffected{$Level}{$Method} = $Severity;
2609 }
2610 }
2611
2612 my $MK = $CompatProblems{$Method}{$Kind};
2613 my (@Locs1, @Locs2) = ();
2614 foreach my $Loc (sort {length($a)<=>length($b)} sort keys(%{$MK}))
2615 {
2616 if(index($Loc, "retval")==0 or index($Loc, "this")==0) {
2617 push(@Locs2, $Loc);
2618 }
2619 else {
2620 push(@Locs1, $Loc);
2621 }
2622 }
2623 foreach my $Loc (@Locs1, @Locs2)
2624 {
2625 my $Type = $MK->{$Loc}{"Type_Name"};
2626 my $Target = $MK->{$Loc}{"Target"};
2627
2628 if(defined $MethodTypeIndex{$Method}{$Type}{$Kind}{$Target})
2629 { # one location for one type and target
2630 next;
2631 }
2632 $MethodTypeIndex{$Method}{$Type}{$Kind}{$Target} = 1;
2633
2634 $TypeChanges{$Level}{$Type}{$Kind}{$Loc} = $MK->{$Loc};
2635 $TypeProblemsIndex{$Level}{$Type}{$Kind}{$Loc}{$Method} = 1;
2636 }
2637 }
2638 }
2639 }
2640
2641 %MethodTypeIndex = (); # clear memory
2642
2643 $T_Problems_High = getTypeProblemsCount("High", $Level);
2644 $T_Problems_Medium = getTypeProblemsCount("Medium", $Level);
2645 $T_Problems_Low = getTypeProblemsCount("Low", $Level);
2646 $T_Other = getTypeProblemsCount("Safe", $Level);
2647
2648 my $SCount = keys(%CheckedMethods)-$Added;
2649 if($SCount)
2650 {
2651 my %Weight = (
2652 "High" => 100,
2653 "Medium" => 50,
2654 "Low" => 25
2655 );
2656 foreach (keys(%{$TotalAffected{$Level}})) {
2657 $RESULT{$Level}{"Affected"}+=$Weight{$TotalAffected{$Level}{$_}};
2658 }
2659 $RESULT{$Level}{"Affected"} = $RESULT{$Level}{"Affected"}/$SCount;
2660 }
2661 else {
2662 $RESULT{$Level}{"Affected"} = 0;
2663 }
2664 $RESULT{$Level}{"Affected"} = showNum($RESULT{$Level}{"Affected"});
2665 if($RESULT{$Level}{"Affected"}>=100) {
2666 $RESULT{$Level}{"Affected"} = 100;
2667 }
2668
2669 my ($TestInfo, $TestResults, $Problem_Summary) = ();
2670
2671 # test info
2672 $TestInfo .= "<h2>Test Info</h2><hr/>\n";
2673 $TestInfo .= "<table class='summary'>\n";
2674 $TestInfo .= "<tr><th>Library Name</th><td>".$In::Opt{"TargetTitle"}."</td></tr>\n";
2675 $TestInfo .= "<tr><th>Version #1</th><td>".$In::Desc{1}{"Version"}."</td></tr>\n";
2676 $TestInfo .= "<tr><th>Version #2</th><td>".$In::Desc{2}{"Version"}."</td></tr>\n";
2677
2678 if($In::Opt{"JoinReport"})
2679 {
2680 if($Level eq "Binary") {
2681 $TestInfo .= "<tr><th>Subject</th><td width='150px'>Binary Compatibility</td></tr>\n"; # Run-time
2682 }
2683 if($Level eq "Source") {
2684 $TestInfo .= "<tr><th>Subject</th><td width='150px'>Source Compatibility</td></tr>\n"; # Build-time
2685 }
2686 }
2687 $TestInfo .= "</table>\n";
2688
2689 # test results
2690 $TestResults .= "<h2>Test Results</h2><hr/>\n";
2691 $TestResults .= "<table class='summary'>\n";
2692
2693 my $Checked_Archives_Link = "0";
2694 $Checked_Archives_Link = "<a href='#Checked_Archives' style='color:Blue;'>".keys(%{$LibArchives{1}})."</a>" if(keys(%{$LibArchives{1}})>0);
2695
2696 $TestResults .= "<tr><th>Total JARs</th><td>$Checked_Archives_Link</td></tr>\n";
2697 $TestResults .= "<tr><th>Total Methods / Classes</th><td>".keys(%CheckedMethods)." / ".keys(%CheckedTypes)."</td></tr>\n";
2698
2699 $RESULT{$Level}{"Problems"} += $Removed+$M_Problems_High+$T_Problems_High+$T_Problems_Medium+$M_Problems_Medium;
2700 if($In::Opt{"StrictCompat"}) {
2701 $RESULT{$Level}{"Problems"}+=$T_Problems_Low+$M_Problems_Low;
2702 }
2703 else {
2704 $RESULT{$Level}{"Warnings"}+=$T_Problems_Low+$M_Problems_Low;
2705 }
2706
2707 my $META_DATA = "kind:".lc($Level).";";
2708 $META_DATA .= $RESULT{$Level}{"Problems"}?"verdict:incompatible;":"verdict:compatible;";
2709 $TestResults .= "<tr><th>Compatibility</th>\n";
2710
2711 my $BC_Rate = showNum(100 - $RESULT{$Level}{"Affected"});
2712
2713 if($RESULT{$Level}{"Problems"})
2714 {
2715 my $Cl = "incompatible";
2716 if($BC_Rate>=90) {
2717 $Cl = "warning";
2718 }
2719 elsif($BC_Rate>=80) {
2720 $Cl = "almost_compatible";
2721 }
2722
2723 $TestResults .= "<td class=\'$Cl\'>".$BC_Rate."%</td>\n";
2724 }
2725 else
2726 {
2727 $TestResults .= "<td class=\'compatible\'>100%</td>\n";
2728 }
2729
2730 $TestResults .= "</tr>\n";
2731 $TestResults .= "</table>\n";
2732
2733 $META_DATA .= "affected:".$RESULT{$Level}{"Affected"}.";";# in percents
2734
2735 # Problem Summary
2736 $Problem_Summary .= "<h2>Problem Summary</h2><hr/>\n";
2737 $Problem_Summary .= "<table class='summary'>\n";
2738 $Problem_Summary .= "<tr><th></th><th style='text-align:center;'>Severity</th><th style='text-align:center;'>Count</th></tr>\n";
2739
2740 my $Added_Link = "0";
2741 if($Added>0)
2742 {
2743 if($In::Opt{"ShortMode"}) {
2744 $Added_Link = $Added;
2745 }
2746 else
2747 {
2748 if($In::Opt{"JoinReport"}) {
2749 $Added_Link = "<a href='#".$Level."_Added' style='color:Blue;'>$Added</a>";
2750 }
2751 else {
2752 $Added_Link = "<a href='#Added' style='color:Blue;'>$Added</a>";
2753 }
2754 }
2755 }
2756 $META_DATA .= "added:$Added;";
2757 $Problem_Summary .= "<tr><th>Added Methods</th><td>-</td><td".getStyle("M", "Added", $Added).">$Added_Link</td></tr>\n";
2758
2759 my $Removed_Link = "0";
2760 if($Removed>0)
2761 {
2762 if($In::Opt{"ShortMode"}) {
2763 $Removed_Link = $Removed;
2764 }
2765 else
2766 {
2767 if($In::Opt{"JoinReport"}) {
2768 $Removed_Link = "<a href='#".$Level."_Removed' style='color:Blue;'>$Removed</a>"
2769 }
2770 else {
2771 $Removed_Link = "<a href='#Removed' style='color:Blue;'>$Removed</a>"
2772 }
2773 }
2774 }
2775 $META_DATA .= "removed:$Removed;";
2776 $Problem_Summary .= "<tr><th>Removed Methods</th>";
2777 $Problem_Summary .= "<td>High</td><td".getStyle("M", "Removed", $Removed).">$Removed_Link</td></tr>\n";
2778
2779 my $TH_Link = "0";
2780 $TH_Link = "<a href='#".getAnchor("Type", $Level, "High")."' style='color:Blue;'>$T_Problems_High</a>" if($T_Problems_High>0);
2781 $META_DATA .= "type_problems_high:$T_Problems_High;";
2782 $Problem_Summary .= "<tr><th rowspan='3'>Problems with<br/>Data Types</th>";
2783 $Problem_Summary .= "<td>High</td><td".getStyle("T", "High", $T_Problems_High).">$TH_Link</td></tr>\n";
2784
2785 my $TM_Link = "0";
2786 $TM_Link = "<a href='#".getAnchor("Type", $Level, "Medium")."' style='color:Blue;'>$T_Problems_Medium</a>" if($T_Problems_Medium>0);
2787 $META_DATA .= "type_problems_medium:$T_Problems_Medium;";
2788 $Problem_Summary .= "<tr><td>Medium</td><td".getStyle("T", "Medium", $T_Problems_Medium).">$TM_Link</td></tr>\n";
2789
2790 my $TL_Link = "0";
2791 $TL_Link = "<a href='#".getAnchor("Type", $Level, "Low")."' style='color:Blue;'>$T_Problems_Low</a>" if($T_Problems_Low>0);
2792 $META_DATA .= "type_problems_low:$T_Problems_Low;";
2793 $Problem_Summary .= "<tr><td>Low</td><td".getStyle("T", "Low", $T_Problems_Low).">$TL_Link</td></tr>\n";
2794
2795 my $MH_Link = "0";
2796 $MH_Link = "<a href='#".getAnchor("Method", $Level, "High")."' style='color:Blue;'>$M_Problems_High</a>" if($M_Problems_High>0);
2797 $META_DATA .= "method_problems_high:$M_Problems_High;";
2798 $Problem_Summary .= "<tr><th rowspan='3'>Problems with<br/>Methods</th>";
2799 $Problem_Summary .= "<td>High</td><td".getStyle("M", "High", $M_Problems_High).">$MH_Link</td></tr>\n";
2800
2801 my $MM_Link = "0";
2802 $MM_Link = "<a href='#".getAnchor("Method", $Level, "Medium")."' style='color:Blue;'>$M_Problems_Medium</a>" if($M_Problems_Medium>0);
2803 $META_DATA .= "method_problems_medium:$M_Problems_Medium;";
2804 $Problem_Summary .= "<tr><td>Medium</td><td".getStyle("M", "Medium", $M_Problems_Medium).">$MM_Link</td></tr>\n";
2805
2806 my $ML_Link = "0";
2807 $ML_Link = "<a href='#".getAnchor("Method", $Level, "Low")."' style='color:Blue;'>$M_Problems_Low</a>" if($M_Problems_Low>0);
2808 $META_DATA .= "method_problems_low:$M_Problems_Low;";
2809 $Problem_Summary .= "<tr><td>Low</td><td".getStyle("M", "Low", $M_Problems_Low).">$ML_Link</td></tr>\n";
2810
2811 # Safe Changes
2812 if($T_Other)
2813 {
2814 my $TS_Link = "<a href='#".getAnchor("Type", $Level, "Safe")."' style='color:Blue;'>$T_Other</a>";
2815 $Problem_Summary .= "<tr><th>Other Changes<br/>in Data Types</th><td>-</td><td".getStyle("T", "Safe", $T_Other).">$TS_Link</td></tr>\n";
2816 }
2817
2818 if($M_Other)
2819 {
2820 my $MS_Link = "<a href='#".getAnchor("Method", $Level, "Safe")."' style='color:Blue;'>$M_Other</a>";
2821 $Problem_Summary .= "<tr><th>Other Changes<br/>in Methods</th><td>-</td><td".getStyle("M", "Safe", $M_Other).">$MS_Link</td></tr>\n";
2822 }
2823 $META_DATA .= "checked_methods:".keys(%CheckedMethods).";";
2824 $META_DATA .= "checked_types:".keys(%CheckedTypes).";";
2825 $META_DATA .= "tool_version:$TOOL_VERSION";
2826 $Problem_Summary .= "</table>\n";
2827
2828 my $AnyChanged = ($Added or $Removed or $M_Problems_High or $M_Problems_Medium or $M_Problems_Low or
2829 $T_Problems_High or $T_Problems_Medium or $T_Problems_Low or $M_Other or $T_Other);
2830
2831 return ($TestInfo.$TestResults.$Problem_Summary, $META_DATA, $AnyChanged);
2832}
2833
2834sub getStyle($$$)
2835{
2836 my ($Subj, $Act, $Num) = @_;
2837 my %Style = (
2838 "Added"=>"new",
2839 "Removed"=>"failed",
2840 "Safe"=>"passed",
2841 "Low"=>"warning",
2842 "Medium"=>"failed",
2843 "High"=>"failed"
2844 );
2845
2846 if($Num>0) {
2847 return " class='".$Style{$Act}."'";
2848 }
2849
2850 return "";
2851}
2852
2853sub getAnchor($$$)
2854{
2855 my ($Kind, $Level, $Severity) = @_;
2856 if($In::Opt{"JoinReport"})
2857 {
2858 if($Severity eq "Safe") {
2859 return "Other_".$Level."_Changes_In_".$Kind."s";
2860 }
2861 else {
2862 return $Kind."_".$Level."_Problems_".$Severity;
2863 }
2864 }
2865 else
2866 {
2867 if($Severity eq "Safe") {
2868 return "Other_Changes_In_".$Kind."s";
2869 }
2870 else {
2871 return $Kind."_Problems_".$Severity;
2872 }
2873 }
2874}
2875
2876sub getReportAdded($)
2877{
2878 if($In::Opt{"ShortMode"}) {
2879 return "";
2880 }
2881
2882 my $Level = $_[0];
2883 my ($ADDED_METHODS, %MethodAddedInArchiveClass);
2884 foreach my $Method (sort keys(%CompatProblems))
2885 {
2886 foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
2887 {
2888 if($Kind eq "Added_Method")
2889 {
2890 my $ArchiveName = $MethodInfo{2}{$Method}{"Archive"};
2891 my $ClassName = getShortName($MethodInfo{2}{$Method}{"Class"}, 2);
2892 if($Level eq "Source")
2893 {
2894 if($ChangedReturnFromVoid{$Method}) {
2895 next;
2896 }
2897 }
2898 $MethodAddedInArchiveClass{$ArchiveName}{$ClassName}{$Method} = 1;
2899 }
2900 }
2901 }
2902 my $Added_Number = 0;
2903 foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%MethodAddedInArchiveClass))
2904 {
2905 foreach my $ClassName (sort {lc($a) cmp lc($b)} keys(%{$MethodAddedInArchiveClass{$ArchiveName}}))
2906 {
2907 my %NameSpace_Method = ();
2908 foreach my $Method (keys(%{$MethodAddedInArchiveClass{$ArchiveName}{$ClassName}})) {
2909 $NameSpace_Method{$MethodInfo{2}{$Method}{"Package"}}{$Method} = 1;
2910 }
2911
2912 my $ShowClass = $ClassName;
2913 $ShowClass=~s/<.*>//g;
2914
2915 foreach my $NameSpace (sort keys(%NameSpace_Method))
2916 {
2917 $ADDED_METHODS .= "<span class='jar'>$ArchiveName</span>, <span class='cname'>".specChars($ShowClass).".class</span><br/>\n";
2918
2919 if($NameSpace) {
2920 $ADDED_METHODS .= "<span class='pkg_t'>package</span> <span class='pkg'>$NameSpace</span><br/>\n";
2921 }
2922
2923 if($In::Opt{"Compact"}) {
2924 $ADDED_METHODS .= "<div class='symbols'>";
2925 }
2926
2927 my @SortedMethods = sort {lc($MethodInfo{2}{$a}{"Signature"}) cmp lc($MethodInfo{2}{$b}{"Signature"})} sort keys(%{$NameSpace_Method{$NameSpace}});
2928 foreach my $Method (@SortedMethods)
2929 {
2930 $Added_Number += 1;
2931
2932 my $Signature = undef;
2933
2934 if($In::Opt{"Compact"}) {
2935 $Signature = getSignature($Method, 2, "Full|HTML|Simple");
2936 }
2937 else {
2938 $Signature = highLight_ItalicColor($Method, 2);
2939 }
2940
2941 if($NameSpace) {
2942 $Signature=~s/(\W|\A)\Q$NameSpace\E\.(\w)/$1$2/g;
2943 }
2944
2945 if($In::Opt{"Compact"}) {
2946 $ADDED_METHODS .= "&nbsp;".$Signature."<br/>\n";
2947 }
2948 else {
2949 $ADDED_METHODS .= insertIDs($ContentSpanStart.$Signature.$ContentSpanEnd."<br/>\n".$ContentDivStart."<span class='mngl'>".specChars($Method)."</span><br/><br/>".$ContentDivEnd."\n");
2950 }
2951 }
2952
2953 if($In::Opt{"Compact"}) {
2954 $ADDED_METHODS .= "</div>";
2955 }
2956
2957 $ADDED_METHODS .= "<br/>\n";
2958 }
2959
2960 }
2961 }
2962 if($ADDED_METHODS)
2963 {
2964 my $Anchor = "<a name='Added'></a>";
2965 if($In::Opt{"JoinReport"}) {
2966 $Anchor = "<a name='".$Level."_Added'></a>";
2967 }
2968 if($In::Opt{"OldStyle"}) {
2969 $ADDED_METHODS = "<h2>Added Methods ($Added_Number)</h2><hr/>\n".$ADDED_METHODS;
2970 }
2971 else {
2972 $ADDED_METHODS = "<h2>Added Methods <span".getStyle("M", "Added", $Added_Number).">&nbsp;$Added_Number&nbsp;</span></h2><hr/>\n".$ADDED_METHODS;
2973 }
2974 $ADDED_METHODS = $Anchor.$ADDED_METHODS.$TOP_REF."<br/>\n";
2975 }
2976 return $ADDED_METHODS;
2977}
2978
2979sub getReportRemoved($)
2980{
2981 if($In::Opt{"ShortMode"}) {
2982 return "";
2983 }
2984
2985 my $Level = $_[0];
2986 my ($REMOVED_METHODS, %MethodRemovedFromArchiveClass);
2987 foreach my $Method (sort keys(%CompatProblems))
2988 {
2989 foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
2990 {
2991 if($Kind eq "Removed_Method")
2992 {
2993 if($Level eq "Source")
2994 {
2995 if($ChangedReturnFromVoid{$Method}) {
2996 next;
2997 }
2998 }
2999 my $ArchiveName = $MethodInfo{1}{$Method}{"Archive"};
3000 my $ClassName = getShortName($MethodInfo{1}{$Method}{"Class"}, 1);
3001 $MethodRemovedFromArchiveClass{$ArchiveName}{$ClassName}{$Method} = 1;
3002 }
3003 }
3004 }
3005 my $Removed_Number = 0;
3006 foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%MethodRemovedFromArchiveClass))
3007 {
3008 foreach my $ClassName (sort {lc($a) cmp lc($b)} keys(%{$MethodRemovedFromArchiveClass{$ArchiveName}}))
3009 {
3010 my %NameSpace_Method = ();
3011 foreach my $Method (keys(%{$MethodRemovedFromArchiveClass{$ArchiveName}{$ClassName}}))
3012 {
3013 $NameSpace_Method{$MethodInfo{1}{$Method}{"Package"}}{$Method} = 1;
3014 }
3015
3016 my $ShowClass = $ClassName;
3017 $ShowClass=~s/<.*>//g;
3018
3019 foreach my $NameSpace (sort keys(%NameSpace_Method))
3020 {
3021 $REMOVED_METHODS .= "<span class='jar'>$ArchiveName</span>, <span class='cname'>".specChars($ShowClass).".class</span><br/>\n";
3022
3023 if($NameSpace) {
3024 $REMOVED_METHODS .= "<span class='pkg_t'>package</span> <span class='pkg'>$NameSpace</span><br/>\n";
3025 }
3026
3027 if($In::Opt{"Compact"}) {
3028 $REMOVED_METHODS .= "<div class='symbols'>";
3029 }
3030
3031 my @SortedMethods = sort {lc($MethodInfo{1}{$a}{"Signature"}) cmp lc($MethodInfo{1}{$b}{"Signature"})} sort keys(%{$NameSpace_Method{$NameSpace}});
3032 foreach my $Method (@SortedMethods)
3033 {
3034 $Removed_Number += 1;
3035
3036 my $Signature = undef;
3037
3038 if($In::Opt{"Compact"}) {
3039 $Signature = getSignature($Method, 1, "Full|HTML|Simple");
3040 }
3041 else {
3042 $Signature = highLight_ItalicColor($Method, 1);
3043 }
3044
3045 if($NameSpace) {
3046 $Signature=~s/(\W|\A)\Q$NameSpace\E\.(\w)/$1$2/g;
3047 }
3048
3049 if($In::Opt{"Compact"}) {
3050 $REMOVED_METHODS .= "&nbsp;".$Signature."<br/>\n";
3051 }
3052 else {
3053 $REMOVED_METHODS .= insertIDs($ContentSpanStart.$Signature.$ContentSpanEnd."<br/>\n".$ContentDivStart."<span class='mngl'>".specChars($Method)."</span><br/><br/>".$ContentDivEnd."\n");
3054 }
3055 }
3056
3057 if($In::Opt{"Compact"}) {
3058 $REMOVED_METHODS .= "</div>";
3059 }
3060
3061 $REMOVED_METHODS .= "<br/>\n";
3062 }
3063 }
3064 }
3065 if($REMOVED_METHODS)
3066 {
3067 my $Anchor = "<a name='Removed'></a><a name='Withdrawn'></a>";
3068 if($In::Opt{"JoinReport"}) {
3069 $Anchor = "<a name='".$Level."_Removed'></a><a name='".$Level."_Withdrawn'></a>";
3070 }
3071 if($In::Opt{"OldStyle"}) {
3072 $REMOVED_METHODS = "<h2>Removed Methods ($Removed_Number)</h2><hr/>\n".$REMOVED_METHODS;
3073 }
3074 else {
3075 $REMOVED_METHODS = "<h2>Removed Methods <span".getStyle("M", "Removed", $Removed_Number).">&nbsp;$Removed_Number&nbsp;</span></h2><hr/>\n".$REMOVED_METHODS;
3076 }
3077 $REMOVED_METHODS = $Anchor.$REMOVED_METHODS.$TOP_REF."<br/>\n";
3078 }
3079 return $REMOVED_METHODS;
3080}
3081
3082sub readRules($)
3083{
3084 my $Kind = $_[0];
3085 if(not -f $RULES_PATH{$Kind}) {
3086 exitStatus("Module_Error", "can't access \'".$RULES_PATH{$Kind}."\'");
3087 }
3088 my $Content = readFile($RULES_PATH{$Kind});
3089 while(my $Rule = parseTag(\$Content, "rule"))
3090 {
3091 my $RId = parseTag(\$Rule, "id");
3092 my @Properties = ("Severity", "Change", "Effect", "Overcome", "Kind");
3093 foreach my $Prop (@Properties) {
3094 if(my $Value = parseTag(\$Rule, lc($Prop)))
3095 {
3096 $Value=~s/\n[ ]*//;
3097 $CompatRules{$Kind}{$RId}{$Prop} = $Value;
3098 }
3099 }
3100 if($CompatRules{$Kind}{$RId}{"Kind"}=~/\A(Methods|Parameters)\Z/) {
3101 $CompatRules{$Kind}{$RId}{"Kind"} = "Methods";
3102 }
3103 else { # Types, Fields
3104 $CompatRules{$Kind}{$RId}{"Kind"} = "Types";
3105 }
3106 }
3107}
3108
3109sub addMarkup($)
3110{
3111 my $Content = $_[0];
3112
3113 # auto-markup
3114 $Content=~s/\n[ ]*//; # spaces
3115 $Content=~s!([2-9]\))!<br/>$1!g; # 1), 2), ...
3116 if($Content=~/\ANOTE:/)
3117 { # notes
3118 $Content=~s!(NOTE):!<b>$1</b>:!g;
3119 }
3120 else {
3121 $Content=~s!(NOTE):!<br/><br/><b>$1</b>:!g;
3122 }
3123
3124 my @Keywords = (
3125 "static",
3126 "abstract",
3127 "default",
3128 "final",
3129 "synchronized"
3130 );
3131
3132 my $MKeys = join("|", @Keywords);
3133 foreach (@Keywords) {
3134 $MKeys .= "|non-".$_;
3135 }
3136
3137 $Content=~s!(became\s*)($MKeys)([^\w-]|\Z)!$1<b>$2</b>$3!ig; # intrinsic types, modifiers
3138
3139 # Markdown
3140 $Content=~s!\*\*([\w\-\@]+)\*\*!<b>$1</b>!ig;
3141 $Content=~s!\*([\w\-]+)\*!<i>$1</i>!ig;
3142
3143 return $Content;
3144}
3145
3146sub applyMacroses($$$$$$)
3147{
3148 my ($Level, $Subj, $Kind, $Content, $Problem, $AddAttr) = @_;
3149
3150 $Content = addMarkup($Content);
3151
3152 # macros
3153 foreach my $Attr (sort {$b cmp $a} (keys(%{$Problem}), keys(%{$AddAttr})))
3154 {
3155 my $Macro = "\@".lc($Attr);
3156 my $Value = undef;
3157
3158 if(defined $Problem->{$Attr}) {
3159 $Value = $Problem->{$Attr};
3160 }
3161 else {
3162 $Value = $AddAttr->{$Attr};
3163 }
3164
3165 if(not defined $Value
3166 or $Value eq "") {
3167 next;
3168 }
3169
3170 if(index($Content, $Macro)==-1) {
3171 next;
3172 }
3173
3174 if($Attr eq "Param_Pos") {
3175 $Value = showPos($Value);
3176 }
3177
3178 if($Attr eq "Invoked") {
3179 $Value = blackName(specChars($Value));
3180 }
3181 elsif($Value=~/\s/) {
3182 $Value = "<span class='value'>".specChars($Value)."</span>";
3183 }
3184 else
3185 {
3186 my $Fmt = "Class|HTML|Desc";
3187
3188 if($Attr ne "Invoked_By")
3189 {
3190 if($Attr eq "Method_Short"
3191 or $Kind!~/Overridden|Moved_Up/) {
3192 $Fmt = "HTML|Desc";
3193 }
3194 }
3195
3196 if($Subj eq "Change") {
3197 $Fmt .= "|Return";
3198 }
3199
3200 if(defined $MethodInfo{1}{$Value}
3201 and defined $MethodInfo{1}{$Value}{"ShortName"}) {
3202 $Value = blackName(getSignature($Value, 1, $Fmt));
3203 }
3204 elsif(defined $MethodInfo{2}{$Value}
3205 and defined $MethodInfo{2}{$Value}{"ShortName"}) {
3206 $Value = blackName(getSignature($Value, 2, $Fmt));
3207 }
3208 else
3209 {
3210 $Value = specChars($Value);
3211 if($Attr ne "Type_Type") {
3212 $Value = "<b>".$Value."</b>";
3213 }
3214 }
3215 }
3216 $Content=~s/\Q$Macro\E/$Value/g;
3217 }
3218
3219 if($Content=~/(\A|[^\@\w])(\@\w+)/)
3220 {
3221 if(not $IncompleteRules{$Level}{$Kind})
3222 { # only one warning
3223 printMsg("WARNING", "incomplete $2 in the rule \"$Kind\" (\"$Level\")");
3224 $IncompleteRules{$Level}{$Kind} = 1;
3225 }
3226 }
3227
3228 return $Content;
3229}
3230
3231sub getReportMethodProblems($$)
3232{
3233 my ($TargetSeverity, $Level) = @_;
3234 my $METHOD_PROBLEMS = "";
3235 my (%ReportMap, %MethodChanges) = ();
3236
3237 foreach my $Method (sort keys(%CompatProblems))
3238 {
3239 my $ArchiveName = $MethodInfo{1}{$Method}{"Archive"};
3240 my $ClassName = getShortName($MethodInfo{1}{$Method}{"Class"}, 1);
3241
3242 foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
3243 {
3244 if($CompatRules{$Level}{$Kind}{"Kind"} eq "Methods")
3245 {
3246 if($Kind eq "Added_Method"
3247 or $Kind eq "Removed_Method") {
3248 next;
3249 }
3250
3251 if(my $Severity = $CompatRules{$Level}{$Kind}{"Severity"})
3252 {
3253 if($Severity ne $TargetSeverity) {
3254 next;
3255 }
3256
3257 $MethodChanges{$Method}{$Kind} = $CompatProblems{$Method}{$Kind};
3258 $ReportMap{$ArchiveName}{$ClassName}{$Method} = 1;
3259 }
3260 }
3261 }
3262 }
3263 my $ProblemsNum = 0;
3264 foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
3265 {
3266 foreach my $ClassName (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$ArchiveName}}))
3267 {
3268 my %NameSpace_Method = ();
3269 foreach my $Method (keys(%{$ReportMap{$ArchiveName}{$ClassName}})) {
3270 $NameSpace_Method{$MethodInfo{1}{$Method}{"Package"}}{$Method} = 1;
3271 }
3272
3273 my $ShowClass = $ClassName;
3274 $ShowClass=~s/<.*>//g;
3275
3276 foreach my $NameSpace (sort keys(%NameSpace_Method))
3277 {
3278 $METHOD_PROBLEMS .= "<span class='jar'>$ArchiveName</span>, <span class='cname'>".specChars($ShowClass).".class</span><br/>\n";
3279 if($NameSpace) {
3280 $METHOD_PROBLEMS .= "<span class='pkg_t'>package</span> <span class='pkg'>$NameSpace</span><br/>\n";
3281 }
3282
3283 my @SortedMethods = sort {lc($MethodInfo{1}{$a}{"Signature"}) cmp lc($MethodInfo{1}{$b}{"Signature"})} sort keys(%{$NameSpace_Method{$NameSpace}});
3284 foreach my $Method (@SortedMethods)
3285 {
3286 my %AddAttr = ();
3287
3288 $AddAttr{"Method_Short"} = $Method;
3289 $AddAttr{"Class"} = getTypeName($MethodInfo{1}{$Method}{"Class"}, 1);
3290
3291 my $METHOD_REPORT = "";
3292 my $ProblemNum = 1;
3293 foreach my $Kind (sort keys(%{$MethodChanges{$Method}}))
3294 {
3295 foreach my $Loc (sort keys(%{$MethodChanges{$Method}{$Kind}}))
3296 {
3297 my $ProblemAttr = $MethodChanges{$Method}{$Kind}{$Loc};
3298
3299 if(my $Change = applyMacroses($Level, "Change", $Kind, $CompatRules{$Level}{$Kind}{"Change"}, $ProblemAttr, \%AddAttr))
3300 {
3301 my $Effect = applyMacroses($Level, "Effect", $Kind, $CompatRules{$Level}{$Kind}{"Effect"}, $ProblemAttr, \%AddAttr);
3302 $METHOD_REPORT .= "<tr>\n<th>$ProblemNum</th>\n<td>".$Change."</td>\n<td>".$Effect."</td>\n</tr>\n";
3303 $ProblemNum += 1;
3304 $ProblemsNum += 1;
3305 }
3306 }
3307 }
3308 $ProblemNum -= 1;
3309 if($METHOD_REPORT)
3310 {
3311 my $ShowMethod = highLight_ItalicColor($Method, 1);
3312 if($NameSpace)
3313 {
3314 $METHOD_REPORT = cutNs($METHOD_REPORT, $NameSpace);
3315 $ShowMethod = cutNs($ShowMethod, $NameSpace);
3316 }
3317
3318 $METHOD_PROBLEMS .= $ContentSpanStart."<span class='ext'>[+]</span> ".$ShowMethod;
3319 if($In::Opt{"OldStyle"}) {
3320 $METHOD_PROBLEMS .= " ($ProblemNum)";
3321 }
3322 else {
3323 $METHOD_PROBLEMS .= " <span".getStyle("M", $TargetSeverity, $ProblemNum).">&nbsp;$ProblemNum&nbsp;</span>";
3324 }
3325 $METHOD_PROBLEMS .= $ContentSpanEnd."<br/>\n";
3326 $METHOD_PROBLEMS .= $ContentDivStart;
3327
3328 if(not $In::Opt{"Compact"}) {
3329 $METHOD_PROBLEMS .= "<span class='mngl pleft'>".specChars($Method)."</span><br/>\n";
3330 }
3331
3332 $METHOD_PROBLEMS .= "<table class='ptable'><tr><th width='2%'></th><th width='47%'>Change</th><th>Effect</th></tr>$METHOD_REPORT</table><br/>$ContentDivEnd\n";
3333
3334 }
3335 }
3336
3337 $METHOD_PROBLEMS .= "<br/>";
3338 }
3339 }
3340 }
3341 if($METHOD_PROBLEMS)
3342 {
3343 $METHOD_PROBLEMS = insertIDs($METHOD_PROBLEMS);
3344
3345 my $Title = "Problems with Methods, $TargetSeverity Severity";
3346 if($TargetSeverity eq "Safe")
3347 { # Safe Changes
3348 $Title = "Other Changes in Methods";
3349 }
3350 if($In::Opt{"OldStyle"}) {
3351 $METHOD_PROBLEMS = "<h2>$Title ($ProblemsNum)</h2><hr/>\n".$METHOD_PROBLEMS;
3352 }
3353 else {
3354 $METHOD_PROBLEMS = "<h2>$Title <span".getStyle("M", $TargetSeverity, $ProblemsNum).">&nbsp;$ProblemsNum&nbsp;</span></h2><hr/>\n".$METHOD_PROBLEMS;
3355 }
3356 $METHOD_PROBLEMS = "<a name='".getAnchor("Method", $Level, $TargetSeverity)."'></a>\n".$METHOD_PROBLEMS;
3357 $METHOD_PROBLEMS .= $TOP_REF."<br/>\n";
3358 }
3359 return $METHOD_PROBLEMS;
3360}
3361
3362sub showType($$$)
3363{
3364 my ($Name, $Html, $LVer) = @_;
3365 my $TType = $TypeInfo{$LVer}{$TName_Tid{$LVer}{$Name}}{"Type"};
3366 if($Html) {
3367 $Name = "<span class='ttype'>".$TType."</span> ".specChars($Name);
3368 }
3369 else {
3370 $Name = $TType." ".$Name;
3371 }
3372 return $Name;
3373}
3374
3375sub getReportTypeProblems($$)
3376{
3377 my ($TargetSeverity, $Level) = @_;
3378 my $TYPE_PROBLEMS = "";
3379
3380 my %ReportMap = ();
3381 my %TypeChanges_Sev = ();
3382
3383 foreach my $TypeName (keys(%{$TypeChanges{$Level}}))
3384 {
3385 my $ArchiveName = $TypeInfo{1}{$TName_Tid{1}{$TypeName}}{"Archive"};
3386
3387 foreach my $Kind (keys(%{$TypeChanges{$Level}{$TypeName}}))
3388 {
3389 if($CompatRules{$Level}{$Kind}{"Severity"} ne $TargetSeverity) {
3390 next;
3391 }
3392
3393 foreach my $Loc (keys(%{$TypeChanges{$Level}{$TypeName}{$Kind}}))
3394 {
3395 $ReportMap{$ArchiveName}{$TypeName} = 1;
3396 $TypeChanges_Sev{$TypeName}{$Kind}{$Loc} = $TypeChanges{$Level}{$TypeName}{$Kind}{$Loc};
3397 }
3398 }
3399 }
3400
3401 my $ProblemsNum = 0;
3402 foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
3403 {
3404 my %NameSpace_Type = ();
3405 foreach my $TypeName (keys(%{$ReportMap{$ArchiveName}})) {
3406 $NameSpace_Type{$TypeInfo{1}{$TName_Tid{1}{$TypeName}}{"Package"}}{$TypeName} = 1;
3407 }
3408 foreach my $NameSpace (sort keys(%NameSpace_Type))
3409 {
3410 $TYPE_PROBLEMS .= "<span class='jar'>$ArchiveName</span><br/>\n";
3411 if($NameSpace) {
3412 $TYPE_PROBLEMS .= "<span class='pkg_t'>package</span> <span class='pkg'>".$NameSpace."</span><br/>\n";
3413 }
3414
3415 my @SortedTypes = sort {lc(showType($a, 0, 1)) cmp lc(showType($b, 0, 1))} keys(%{$NameSpace_Type{$NameSpace}});
3416 foreach my $TypeName (@SortedTypes)
3417 {
3418 my $TypeId = $TName_Tid{1}{$TypeName};
3419
3420 my $ProblemNum = 1;
3421 my $TYPE_REPORT = "";
3422 my (%Kinds_Locations, %Kinds_Target) = ();
3423
3424 foreach my $Kind (sort keys(%{$TypeChanges_Sev{$TypeName}}))
3425 {
3426 foreach my $Location (sort keys(%{$TypeChanges_Sev{$TypeName}{$Kind}}))
3427 {
3428 $Kinds_Locations{$Kind}{$Location} = 1;
3429
3430 my $Target = $TypeChanges_Sev{$TypeName}{$Kind}{$Location}{"Target"};
3431 if($Kinds_Target{$Kind}{$Target}) {
3432 next;
3433 }
3434 $Kinds_Target{$Kind}{$Target} = 1;
3435
3436 my %AddAttr = ();
3437
3438 if($Kind=~/Method/)
3439 {
3440 if(defined $MethodInfo{1}{$Target} and $MethodInfo{1}{$Target}{"Name"})
3441 {
3442 $AddAttr{"Method_Short"} = $Target;
3443 $AddAttr{"Class"} = getTypeName($MethodInfo{1}{$Target}{"Class"}, 1);
3444 }
3445 elsif(defined $MethodInfo{2}{$Target} and $MethodInfo{2}{$Target}{"Name"})
3446 {
3447 $AddAttr{"Method_Short"} = $Target;
3448 $AddAttr{"Class"} = getTypeName($MethodInfo{2}{$Target}{"Class"}, 2);
3449 }
3450 }
3451
3452 my $ProblemAttr = $TypeChanges_Sev{$TypeName}{$Kind}{$Location};
3453
3454 if(my $Change = applyMacroses($Level, "Change", $Kind, $CompatRules{$Level}{$Kind}{"Change"}, $ProblemAttr, \%AddAttr))
3455 {
3456 my $Effect = applyMacroses($Level, "Effect", $Kind, $CompatRules{$Level}{$Kind}{"Effect"}, $ProblemAttr, \%AddAttr);
3457
3458 $TYPE_REPORT .= "<tr>\n<th>$ProblemNum</th>\n<td>".$Change."</td>\n<td>".$Effect."</td>\n</tr>\n";
3459 $ProblemNum += 1;
3460 $ProblemsNum += 1;
3461 }
3462 }
3463 }
3464 $ProblemNum -= 1;
3465 if($TYPE_REPORT)
3466 {
3467 my $Affected = "";
3468 if(not defined $TypeInfo{1}{$TypeId}{"Annotation"}) {
3469 $Affected = getAffectedMethods($Level, $TypeName, \%Kinds_Locations);
3470 }
3471
3472 my $ShowType = showType($TypeName, 1, 1);
3473 if($NameSpace)
3474 {
3475 $TYPE_REPORT = cutNs($TYPE_REPORT, $NameSpace);
3476 $ShowType = cutNs($ShowType, $NameSpace);
3477 $Affected = cutNs($Affected, $NameSpace);
3478 }
3479
3480 $TYPE_PROBLEMS .= $ContentSpanStart."<span class='ext'>[+]</span> ".$ShowType;
3481 if($In::Opt{"OldStyle"}) {
3482 $TYPE_PROBLEMS .= " ($ProblemNum)";
3483 }
3484 else {
3485 $TYPE_PROBLEMS .= " <span".getStyle("T", $TargetSeverity, $ProblemNum).">&nbsp;$ProblemNum&nbsp;</span>";
3486 }
3487 $TYPE_PROBLEMS .= $ContentSpanEnd."<br/>\n";
3488 $TYPE_PROBLEMS .= $ContentDivStart."<table class='ptable'><tr>";
3489 $TYPE_PROBLEMS .= "<th width='2%'></th><th width='47%'>Change</th><th>Effect</th>";
3490 $TYPE_PROBLEMS .= "</tr>$TYPE_REPORT</table>".$Affected."<br/><br/>$ContentDivEnd\n";
3491 }
3492 }
3493
3494 $TYPE_PROBLEMS .= "<br/>";
3495 }
3496 }
3497 if($TYPE_PROBLEMS)
3498 {
3499 $TYPE_PROBLEMS = insertIDs($TYPE_PROBLEMS);
3500
3501 my $Title = "Problems with Data Types, $TargetSeverity Severity";
3502 if($TargetSeverity eq "Safe")
3503 { # Safe Changes
3504 $Title = "Other Changes in Data Types";
3505 }
3506 if($In::Opt{"OldStyle"}) {
3507 $TYPE_PROBLEMS = "<h2>$Title ($ProblemsNum)</h2><hr/>\n".$TYPE_PROBLEMS;
3508 }
3509 else {
3510 $TYPE_PROBLEMS = "<h2>$Title <span".getStyle("T", $TargetSeverity, $ProblemsNum).">&nbsp;$ProblemsNum&nbsp;</span></h2><hr/>\n".$TYPE_PROBLEMS;
3511 }
3512 $TYPE_PROBLEMS = "<a name='".getAnchor("Type", $Level, $TargetSeverity)."'></a>\n".$TYPE_PROBLEMS;
3513 $TYPE_PROBLEMS .= $TOP_REF."<br/>\n";
3514 }
3515 return $TYPE_PROBLEMS;
3516}
3517
3518sub cutNs($$)
3519{
3520 my ($N, $Ns) = @_;
3521 $N=~s/(\W|\A)\Q$Ns\E\.(\w)/$1$2/g;
3522 return $N;
3523}
3524
3525sub getAffectedMethods($$$)
3526{
3527 my ($Level, $Target_TypeName, $Kinds_Locations) = @_;
3528
3529 my $LIMIT = 10;
3530 if(defined $In::Opt{"AffectLimit"}) {
3531 $LIMIT = $In::Opt{"AffectLimit"};
3532 }
3533
3534 my %SymSel = ();
3535
3536 foreach my $Kind (sort keys(%{$Kinds_Locations}))
3537 {
3538 my @Locs = sort {(index($a, "retval")!=-1) cmp (index($b, "retval")!=-1)} sort {length($a)<=>length($b)} sort keys(%{$Kinds_Locations->{$Kind}});
3539
3540 foreach my $Loc (@Locs)
3541 {
3542 foreach my $Method (keys(%{$TypeProblemsIndex{$Level}{$Target_TypeName}{$Kind}{$Loc}}))
3543 {
3544 if($Method eq ".client_method") {
3545 next;
3546 }
3547
3548 if(not defined $SymSel{$Method})
3549 {
3550 $SymSel{$Method}{"Kind"} = $Kind;
3551 $SymSel{$Method}{"Loc"} = $Loc;
3552 }
3553 }
3554 }
3555 }
3556
3557 my $Total = keys(%SymSel);
3558
3559 if(not $Total) {
3560 return "";
3561 }
3562
3563 my $Affected = "";
3564 my $SNum = 0;
3565
3566 foreach my $Method (sort {lc($a) cmp lc($b)} keys(%SymSel))
3567 {
3568 my $Kind = $SymSel{$Method}{"Kind"};
3569 my $Loc = $SymSel{$Method}{"Loc"};
3570
3571 my $Desc = getAffectDesc($Method, $Kind, $Loc, $Level);
3572 my $PName = getParamName($Loc);
3573 my $Pos = getParamPos($PName, $Method, 1);
3574
3575 $Affected .= "<span class='iname_a'>".getSignature($Method, 1, "HTML|Italic|Param|Class|Target=".$Pos)."</span><br/>";
3576 $Affected .= "<div class='affect'>".$Desc."</div>\n";
3577
3578 if(++$SNum>=$LIMIT) {
3579 last;
3580 }
3581 }
3582
3583 if($Total>$LIMIT) {
3584 $Affected .= " <b>...</b>\n<br/>\n"; # and others ...
3585 }
3586
3587 $Affected = "<div class='affected'>".$Affected."</div>";
3588 if($Affected)
3589 {
3590 my $Per = showNum($Total*100/keys(%CheckedMethods));
3591 $Affected = $ContentDivStart.$Affected.$ContentDivEnd;
3592 $Affected = $ContentSpanStart_Affected."[+] affected methods: $Total ($Per\%)".$ContentSpanEnd.$Affected;
3593 }
3594
3595 return $Affected;
3596}
3597
3598sub getAffectDesc($$$$)
3599{
3600 my ($Method, $Kind, $Location, $Level) = @_;
3601 my %Affect = %{$CompatProblems{$Method}{$Kind}{$Location}};
3602 my $New_Value = $Affect{"New_Value"};
3603 my $Type_Name = $Affect{"Type_Name"};
3604 my @Sentence_Parts = ();
3605
3606 $Location=~s/\.[^.]+?\Z//;
3607
3608 my $TypeAttr = getType($MethodInfo{1}{$Method}{"Class"}, 1);
3609 my $Type_Type = $TypeAttr->{"Type"};
3610
3611 my $ABSTRACT_M = $MethodInfo{1}{$Method}{"Abstract"}?" abstract":"";
3612 my $ABSTRACT_C = $TypeAttr->{"Abstract"}?" abstract":"";
3613 my $METHOD_TYPE = $MethodInfo{1}{$Method}{"Constructor"}?"constructor":"method";
3614
3615 if($Kind eq "Class_Overridden_Method" or $Kind eq "Class_Method_Moved_Up_Hierarchy") {
3616 return "Method '".getSignature($New_Value, 2, "Class|HTML|Italic")."' will be called instead of this method in a client program.";
3617 }
3618 elsif($CompatRules{$Level}{$Kind}{"Kind"} eq "Types")
3619 {
3620 my %MInfo = %{$MethodInfo{1}{$Method}};
3621
3622 if($Location eq "this") {
3623 return "This$ABSTRACT_M $METHOD_TYPE is from \'".specChars($Type_Name)."\'$ABSTRACT_C $Type_Type.";
3624 }
3625
3626 my $TypeID = undef;
3627
3628 if($Location=~/retval/)
3629 { # return value
3630 if($Location=~/\./) {
3631 push(@Sentence_Parts, "Field \'".specChars($Location)."\' in the return value");
3632 }
3633 else {
3634 push(@Sentence_Parts, "Return value");
3635 }
3636
3637 $TypeID = $MInfo{"Return"};
3638 }
3639 elsif($Location=~/this/)
3640 { # "this" reference
3641 push(@Sentence_Parts, "Field \'".specChars($Location)."\' in the object");
3642
3643 $TypeID = $MInfo{"Class"};
3644 }
3645 else
3646 { # parameters
3647 my $PName = getParamName($Location);
3648 my $PPos = getParamPos($PName, $Method, 1);
3649
3650 if($Location=~/\./) {
3651 push(@Sentence_Parts, "Field \'".specChars($Location)."\' in ".showPos($PPos)." parameter");
3652 }
3653 else {
3654 push(@Sentence_Parts, showPos($PPos)." parameter");
3655 }
3656 if($PName) {
3657 push(@Sentence_Parts, "\'$PName\'");
3658 }
3659
3660 if(defined $MInfo{"Param"}) {
3661 $TypeID = $MInfo{"Param"}{$PPos}{"Type"};
3662 }
3663 }
3664 push(@Sentence_Parts, " of this$ABSTRACT_M method");
3665
3666 my $Location_T = $Location;
3667 $Location_T=~s/\A\w+(\.|\Z)//; # location in type
3668
3669 my $TypeID_Problem = $TypeID;
3670 if($Location_T) {
3671 $TypeID_Problem = getFieldType($Location_T, $TypeID, 1);
3672 }
3673
3674 if($TypeInfo{1}{$TypeID_Problem}{"Name"} eq $Type_Name) {
3675 push(@Sentence_Parts, "is of type \'".specChars($Type_Name)."\'.");
3676 }
3677 else {
3678 push(@Sentence_Parts, "has base type \'".specChars($Type_Name)."\'.");
3679 }
3680 }
3681 return join(" ", @Sentence_Parts);
3682}
3683
3684sub getParamPos($$$)
3685{
3686 my ($Name, $Method, $LVer) = @_;
3687
3688 if(defined $MethodInfo{$LVer}{$Method}
3689 and defined $MethodInfo{$LVer}{$Method}{"Param"})
3690 {
3691 my $Info = $MethodInfo{$LVer}{$Method};
3692 foreach (keys(%{$Info->{"Param"}}))
3693 {
3694 if($Info->{"Param"}{$_}{"Name"} eq $Name)
3695 {
3696 return $_;
3697 }
3698 }
3699 }
3700
3701 return undef;
3702}
3703
3704sub getParamName($)
3705{
3706 my $Loc = $_[0];
3707 $Loc=~s/\..*//g;
3708 return $Loc;
3709}
3710
3711sub getFieldType($$$)
3712{
3713 my ($Location, $TypeId, $LVer) = @_;
3714
3715 my @Fields = split(/\./, $Location);
3716
3717 foreach my $Name (@Fields)
3718 {
3719 my $TInfo = getBaseType($TypeId, $LVer);
3720
3721 foreach my $N (keys(%{$TInfo->{"Fields"}}))
3722 {
3723 if($N eq $Name)
3724 {
3725 $TypeId = $TInfo->{"Fields"}{$N}{"Type"};
3726 last;
3727 }
3728 }
3729 }
3730
3731 return $TypeId;
3732}
3733
3734sub writeReport($$)
3735{
3736 my ($Level, $Report) = @_;
3737 my $RPath = getReportPath($Level);
3738 writeFile($RPath, $Report);
3739}
3740
3741sub createReport()
3742{
3743 if($In::Opt{"JoinReport"}) {
3744 writeReport("Join", getReport("Join"));
3745 }
3746 elsif($In::Opt{"DoubleReport"})
3747 { # default
3748 writeReport("Binary", getReport("Binary"));
3749 writeReport("Source", getReport("Source"));
3750 }
3751 elsif($In::Opt{"BinaryOnly"})
3752 { # --binary
3753 writeReport("Binary", getReport("Binary"));
3754 }
3755 elsif($In::Opt{"SourceOnly"})
3756 { # --source
3757 writeReport("Source", getReport("Source"));
3758 }
3759}
3760
3761sub getCssStyles($)
3762{
3763 my $Level = $_[0];
3764
3765 my $CssStyles = readModule("Css", "Report.css");
3766
3767 if($Level eq "Join" or $In::Opt{"ExternCss"}) {
3768 $CssStyles .= readModule("Css", "Tabs.css");
3769 }
3770
3771 return $CssStyles;
3772}
3773
3774sub getJsScript($)
3775{
3776 my $Level = $_[0];
3777
3778 my $JScripts = readModule("Js", "Sections.js");
3779
3780 if($Level eq "Join" or $In::Opt{"ExternJs"}) {
3781 $JScripts .= readModule("Js", "Tabs.js");
3782 }
3783
3784 return $JScripts;
3785}
3786
3787sub getReport($)
3788{
3789 my $Level = $_[0];
3790
3791 my $CssStyles = getCssStyles($Level);
3792 my $JScripts = getJsScript($Level);
3793
3794 if(defined $In::Opt{"ExternCss"}) {
3795 writeFile($In::Opt{"ExternCss"}, $CssStyles);
3796 }
3797
3798 if(defined $In::Opt{"ExternJs"}) {
3799 writeFile($In::Opt{"ExternJs"}, $JScripts);
3800 }
3801
3802 if($Level eq "Join")
3803 {
3804 my $Title = $In::Opt{"TargetTitle"}.": ".$In::Desc{1}{"Version"}." to ".$In::Desc{2}{"Version"}." compatibility report";
3805 my $Keywords = $In::Opt{"TargetTitle"}.", compatibility";
3806 my $Description = "Compatibility report for the ".$In::Opt{"TargetTitle"}." library between ".$In::Desc{1}{"Version"}." and ".$In::Desc{2}{"Version"}." versions";
3807
3808 my ($BSummary, $BMetaData, $BAnyChanged) = getSummary("Binary");
3809 my ($SSummary, $SMetaData, $SAnyChanged) = getSummary("Source");
3810
3811 my $Report = "<!-\- $BMetaData -\->\n<!-\- $SMetaData -\->\n".composeHTML_Head($Level, $Title, $Keywords, $Description, $CssStyles, $JScripts, ($BAnyChanged or $SAnyChanged))."<body><a name='Source'></a><a name='Binary'></a><a name='Top'></a>";
3812
3813 $Report .= getReportHeader("Join");
3814 $Report .= "<br/><div class='tabset'>\n";
3815 $Report .= "<a id='BinaryID' href='#BinaryTab' class='tab active'>Binary<br/>Compatibility</a>\n";
3816 $Report .= "<a id='SourceID' href='#SourceTab' style='margin-left:3px' class='tab disabled'>Source<br/>Compatibility</a>\n";
3817 $Report .= "</div>\n";
3818
3819 $Report .= "<div id='BinaryTab' class='tab'>\n$BSummary\n".getReportAdded("Binary").getReportRemoved("Binary").getReportProblems("High", "Binary").getReportProblems("Medium", "Binary").getReportProblems("Low", "Binary").getReportProblems("Safe", "Binary").getSourceInfo()."<br/><br/><br/></div>";
3820
3821 $Report .= "<div id='SourceTab' class='tab'>\n$SSummary\n".getReportAdded("Source").getReportRemoved("Source").getReportProblems("High", "Source").getReportProblems("Medium", "Source").getReportProblems("Low", "Source").getReportProblems("Safe", "Source").getSourceInfo()."<br/><br/><br/></div>";
3822
3823 $Report .= getReportFooter();
3824 $Report .= "\n</body></html>";
3825 return $Report;
3826 }
3827 else
3828 {
3829 my ($Summary, $MetaData, $AnyChanged) = getSummary($Level);
3830
3831 my $Title = $In::Opt{"TargetTitle"}.": ".$In::Desc{1}{"Version"}." to ".$In::Desc{2}{"Version"}." ".lc($Level)." compatibility report";
3832 my $Keywords = $In::Opt{"TargetTitle"}.", ".lc($Level).", compatibility";
3833 my $Description = "$Level compatibility report for the ".$In::Opt{"TargetTitle"}." library between ".$In::Desc{1}{"Version"}." and ".$In::Desc{2}{"Version"}." versions";
3834
3835 my $Report = "<!-\- $MetaData -\->\n".composeHTML_Head($Level, $Title, $Keywords, $Description, $CssStyles, $JScripts, $AnyChanged)."<body><a name='Top'></a>";
3836 $Report .= getReportHeader($Level)."\n".$Summary."\n";
3837 $Report .= getReportAdded($Level).getReportRemoved($Level);
3838 $Report .= getReportProblems("High", $Level).getReportProblems("Medium", $Level).getReportProblems("Low", $Level).getReportProblems("Safe", $Level);
3839 $Report .= getSourceInfo()."<br/><br/><br/>\n";
3840 $Report .= getReportFooter();
3841 $Report .= "\n</body></html>";
3842 return $Report;
3843 }
3844}
3845
3846sub getReportFooter()
3847{
3848 my $Footer = "";
3849 $Footer .= "<hr/>";
3850 $Footer .= "<div class='footer' align='right'><i>Generated by ";
3851 $Footer .= "<a href='".$HomePage{"Dev"}."'>Java API Compliance Checker</a> $TOOL_VERSION &#160;";
3852 $Footer .= "</i></div>";
3853 $Footer .= "<br/>";
3854 return $Footer;
3855}
3856
3857sub getReportProblems($$)
3858{
3859 my ($Priority, $Level) = @_;
3860 my $Report = getReportTypeProblems($Priority, $Level);
3861 if(my $MProblems = getReportMethodProblems($Priority, $Level)) {
3862 $Report .= $MProblems;
3863 }
3864 if($Report)
3865 {
3866 if($In::Opt{"JoinReport"})
3867 {
3868 if($Priority eq "Safe") {
3869 $Report = "<a name=\'Other_".$Level."_Changes\'></a>".$Report;
3870 }
3871 else {
3872 $Report = "<a name=\'".$Priority."_Risk_".$Level."_Problems\'></a>".$Report;
3873 }
3874 }
3875 else
3876 {
3877 if($Priority eq "Safe") {
3878 $Report = "<a name=\'Other_Changes\'></a>".$Report;
3879 }
3880 else {
3881 $Report = "<a name=\'".$Priority."_Risk_Problems\'></a>".$Report;
3882 }
3883 }
3884 }
3885 return $Report;
3886}
3887
3888sub composeHTML_Head($$$$$$$)
3889{
3890 my ($Level, $Title, $Keywords, $Description, $Styles, $Scripts, $AnyChanged) = @_;
3891
3892 my $Head = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
3893 $Head .= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n";
3894 $Head .= "<head>\n";
3895 $Head .= "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
3896 $Head .= "<meta name=\"keywords\" content=\"$Keywords\" />\n";
3897 $Head .= "<meta name=\"description\" content=\"$Description\" />\n";
3898
3899 if(not $AnyChanged) {
3900 $Head .= "<meta name=\"robots\" content=\"noindex\" />\n";
3901 }
3902
3903 my $RPath = getReportPath($Level);
3904
3905 if(defined $In::Opt{"ExternCss"}) {
3906 $Head .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"".getRelPath($In::Opt{"ExternCss"}, $RPath)."\" />\n";
3907 }
3908
3909 if(defined $In::Opt{"ExternJs"}) {
3910 $Head .= "<script type=\"text/javascript\" src=\"".getRelPath($In::Opt{"ExternJs"}, $RPath)."\"></script>\n";
3911 }
3912
3913 $Head .= "<title>$Title</title>\n";
3914
3915 if(not defined $In::Opt{"ExternCss"}) {
3916 $Head .= "<style type=\"text/css\">\n$Styles\n</style>\n";
3917 }
3918
3919 if(not defined $In::Opt{"ExternJs"}) {
3920 $Head .= "<script type=\"text/javascript\" language=\"JavaScript\">\n<!--\n$Scripts\n-->\n</script>\n";
3921 }
3922
3923 $Head .= "</head>\n";
3924
3925 return $Head;
3926}
3927
3928sub insertIDs($)
3929{
3930 my $Text = $_[0];
3931 while($Text=~/CONTENT_ID/)
3932 {
3933 if(int($Content_Counter)%2)
3934 {
3935 $ContentID -= 1;
3936 }
3937 $Text=~s/CONTENT_ID/c_$ContentID/;
3938 $ContentID += 1;
3939 $Content_Counter += 1;
3940 }
3941 return $Text;
3942}
3943
3944sub registerUsage($$)
3945{
3946 my ($TypeId, $LVer) = @_;
3947 $Class_Constructed{$LVer}{$TypeId} = 1;
3948 if(my $BaseId = $TypeInfo{$LVer}{$TypeId}{"BaseType"}) {
3949 $Class_Constructed{$LVer}{$BaseId} = 1;
3950 }
3951}
3952
3953sub checkVoidMethod($)
3954{
3955 my $Method = $_[0];
3956
3957 if($Method=~s/\)(.+)\Z/\)V/g) {
3958 return $Method;
3959 }
3960
3961 return undef;
3962}
3963
3964sub detectAdded()
3965{
3966 foreach my $Method (keys(%{$MethodInfo{2}}))
3967 {
3968 if(not defined $MethodInfo{1}{$Method})
3969 {
3970 if(not methodFilter($Method, 2)) {
3971 next;
3972 }
3973
3974 my $Class = getType($MethodInfo{2}{$Method}{"Class"}, 2);
3975
3976 $CheckedTypes{$Class->{"Name"}} = 1;
3977 $CheckedMethods{$Method} = 1;
3978
3979 if(not $MethodInfo{2}{$Method}{"Constructor"}
3980 and my $Overridden = findMethod($Method, 2, $Class->{"Name"}, 2))
3981 {
3982 if(defined $MethodInfo{1}{$Overridden}
3983 and $Class->{"Type"} eq "class"
3984 and ($TName_Tid{1}{$Class->{"Name"}} or $TName_Tid_Generic{1}{getGeneric($Class->{"Name"})}))
3985 { # class should exist in previous version
3986 %{$CompatProblems{$Overridden}{"Class_Overridden_Method"}{"this.".getSFormat($Method)}}=(
3987 "Type_Name"=>$Class->{"Name"},
3988 "Target"=>$MethodInfo{2}{$Method}{"Signature"},
3989 "Old_Value"=>$Overridden,
3990 "New_Value"=>$Method);
3991 }
3992 }
3993 if($MethodInfo{2}{$Method}{"Abstract"}) {
3994 $AddedMethod_Abstract{$Class->{"Name"}}{$Method} = 1;
3995 }
3996
3997 if(not ($MethodInfo{2}{$Method}{"Access"} eq "protected" and $Class->{"Final"})) {
3998 %{$CompatProblems{$Method}{"Added_Method"}{""}} = ();
3999 }
4000
4001 if(not $MethodInfo{2}{$Method}{"Constructor"})
4002 {
4003 my $VoidMethod = checkVoidMethod($Method);
4004 my $ReturnType = getTypeName($MethodInfo{2}{$Method}{"Return"}, 2);
4005
4006 if(defined $Class->{"GenericParam"}
4007 and defined $Class->{"GenericParam"}{$ReturnType}) {
4008 $ReturnType = getTypeName($Class->{"GenericParam"}{$ReturnType}, 2);
4009 }
4010
4011 if(defined $MethodInfo{1}{$VoidMethod}
4012 and $ReturnType ne "void")
4013 { # return value type changed from void
4014 $ChangedReturnFromVoid{$VoidMethod} = 1;
4015 $ChangedReturnFromVoid{$Method} = 1;
4016
4017 %{$CompatProblems{$VoidMethod}{"Changed_Method_Return_From_Void"}{""}}=(
4018 "New_Value"=>getTypeName($MethodInfo{2}{$Method}{"Return"}, 2)
4019 );
4020 }
4021 elsif(my $OldMethod = $OldMethodSignature{getBaseSignature($Method)})
4022 {
4023 if($OldMethod ne $Method)
4024 {
4025 my $OldReturnType = getTypeName($MethodInfo{1}{$OldMethod}{"Return"}, 1);
4026
4027 %{$CompatProblems{$OldMethod}{"Changed_Method_Return"}{""}}=(
4028 "Old_Value"=>$OldReturnType,
4029 "New_Value"=>$ReturnType
4030 );
4031 }
4032 }
4033 }
4034 }
4035 }
4036}
4037
4038sub detectRemoved()
4039{
4040 foreach my $Method (keys(%{$MethodInfo{1}}))
4041 {
4042 if(not defined $MethodInfo{2}{$Method})
4043 {
4044 if(not methodFilter($Method, 1)) {
4045 next;
4046 }
4047
4048 my $Class = getType($MethodInfo{1}{$Method}{"Class"}, 1);
4049
4050 $CheckedTypes{$Class->{"Name"}} = 1;
4051 $CheckedMethods{$Method} = 1;
4052
4053 if(not $MethodInfo{1}{$Method}{"Constructor"}
4054 and my $MovedUp = findMethod($Method, 1, $Class->{"Name"}, 2))
4055 {
4056 if($Class->{"Type"} eq "class"
4057 and not $MethodInfo{1}{$Method}{"Abstract"}
4058 and ($TName_Tid{2}{$Class->{"Name"}} or $TName_Tid_Generic{2}{getGeneric($Class->{"Name"})}))
4059 {# class should exist in newer version
4060 %{$CompatProblems{$Method}{"Class_Method_Moved_Up_Hierarchy"}{"this.".getSFormat($MovedUp)}}=(
4061 "Type_Name"=>$Class->{"Name"},
4062 "Target"=>$MethodInfo{2}{$MovedUp}{"Signature"},
4063 "Old_Value"=>$Method,
4064 "New_Value"=>$MovedUp);
4065 }
4066 }
4067 else
4068 {
4069 if($MethodInfo{1}{$Method}{"Abstract"}) {
4070 $RemovedMethod_Abstract{$Class->{"Name"}}{$Method} = 1;
4071 }
4072
4073 if(not ($MethodInfo{1}{$Method}{"Access"} eq "protected" and $Class->{"Final"})) {
4074 %{$CompatProblems{$Method}{"Removed_Method"}{""}} = ();
4075 }
4076 }
4077 }
4078 }
4079}
4080
4081sub getArchivePaths($$)
4082{
4083 my ($Dest, $LVer) = @_;
4084 if(-f $Dest) {
4085 return ($Dest);
4086 }
4087 elsif(-d $Dest)
4088 {
4089 $Dest=~s/[\/\\]+\Z//g;
4090 next if(not $Dest);
4091
4092 my @Archives = ();
4093 foreach my $Path (cmdFind($Dest, "", "*\\.jar"))
4094 {
4095 next if(ignorePath($Path, $Dest));
4096 push(@Archives, realpath_F($Path));
4097 }
4098 return @Archives;
4099 }
4100 return ();
4101}
4102
4103sub isCyclical($$) {
4104 return (grep {$_ eq $_[1]} @{$_[0]});
4105}
4106
4107sub mergeAPIs($$)
4108{
4109 my ($LVer, $Dep) = @_;
4110
4111 foreach my $TId (keys(%{$Dep->{"TypeInfo"}}))
4112 {
4113 $TypeInfo{$LVer}{$TId} = $Dep->{"TypeInfo"}{$TId};
4114 $TypeInfo{$LVer}{$TId}{"Dep"} = 1;
4115 }
4116
4117 my $MInfo = $Dep->{"MethodInfo"};
4118 foreach my $M_Id (keys(%{$MInfo}))
4119 {
4120 if(my $Name = $MInfo->{$M_Id}{"Name"})
4121 {
4122 $MethodInfo{$LVer}{$Name} = $MInfo->{$M_Id};
4123 $MethodInfo{$LVer}{$Name}{"Dep"} = 1;
4124 }
4125 }
4126}
4127
4128sub readAPIDump($$$)
4129{
4130 my ($LVer, $Path, $Subj) = @_;
4131
4132 if(not $In::Opt{"CountMethods"}) {
4133 printMsg("INFO", "Reading API dump ($LVer) ...");
4134 }
4135
4136 my $FilePath = "";
4137 if(isDump_U($Path))
4138 { # input *.dump
4139 $FilePath = $Path;
4140 }
4141 else
4142 { # input *.dump.tar.gz
4143 $FilePath = unpackDump($Path);
4144 if(not isDump_U($FilePath)) {
4145 exitStatus("Invalid_Dump", "specified API dump \'$Path\' is not valid, try to recreate it");
4146 }
4147 }
4148
4149 my $APIRef = {};
4150
4151 open(DUMP, $FilePath);
4152 local $/ = undef;
4153 my $Content = <DUMP>;
4154 close(DUMP);
4155
4156 if(getDirname($FilePath) eq $In::Opt{"Tmp"}."/unpack")
4157 { # remove temp file
4158 unlink($FilePath);
4159 }
4160
4161 if($Content!~/};\s*\Z/) {
4162 exitStatus("Invalid_Dump", "specified API dump \'$Path\' is not valid, try to recreate it");
4163 }
4164
4165 $APIRef = eval($Content);
4166
4167 if(not $APIRef) {
4168 exitStatus("Error", "internal error - eval() procedure seem to not working correctly, try to remove 'use strict' and try again");
4169 }
4170
4171 my $APIVer = $APIRef->{"API_DUMP_VERSION"};
4172
4173 if($APIVer)
4174 {
4175 if(cmpVersions($APIVer, $API_DUMP_VERSION)>0)
4176 { # future formats
4177 exitStatus("Dump_Version", "version of the API dump is newer than version of the tool");
4178 }
4179
4180 if(cmpVersions($APIVer, $API_DUMP_VERSION)<0)
4181 { # old formats
4182 printMsg("WARNING", "version of the API dump is older than version of the tool");
4183 }
4184 }
4185
4186 if(cmpVersions($APIVer, $API_DUMP_VERSION_MIN)<0)
4187 { # obsolete formats
4188 exitStatus("Dump_Version", "version of the API dump is too old and unsupported anymore, please regenerate it");
4189 }
4190
4191 if($Subj ne "Dep")
4192 {
4193 $In::Desc{$LVer}{"Version"} = $APIRef->{"LibraryVersion"};
4194 $In::Desc{$LVer}{"Dump"} = 1;
4195 }
4196
4197 return $APIRef;
4198}
4199
4200sub checkVersionNum($$)
4201{
4202 my ($LVer, $Path) = @_;
4203
4204 if($In::Desc{$LVer}{"TargetVersion"}) {
4205 return;
4206 }
4207
4208 if($Path!~/\.jar\Z/i) {
4209 return;
4210 }
4211
4212 my $Ver = undef;
4213
4214 if(not defined $Ver) {
4215 $Ver = getManifestVersion(getAbsPath($Path));
4216 }
4217
4218 if(not defined $Ver) {
4219 $Ver = getPkgVersion(getFilename($Path));
4220 }
4221
4222 if(not defined $Ver) {
4223 $Ver = parseVersion($Path);
4224 }
4225
4226 if(not defined $Ver)
4227 {
4228 if($In::Opt{"DumpAPI"})
4229 {
4230 $Ver = "XYZ";
4231 }
4232 else
4233 {
4234 if($LVer==1) {
4235 $Ver = "X";
4236 }
4237 else {
4238 $Ver = "Y";
4239 }
4240 }
4241 }
4242
4243 $In::Desc{$LVer}{"TargetVersion"} = $Ver;
4244
4245 if($In::Opt{"DumpAPI"}) {
4246 printMsg("WARNING", "set version number to $Ver (use -vnum option to change it)");
4247 }
4248 else {
4249 printMsg("WARNING", "set #$LVer version number to $Ver (use --v$LVer=NUM option to change it)");
4250 }
4251}
4252
4253sub getManifestVersion($)
4254{
4255 my $Path = $_[0];
4256
4257 my $JarCmd = getCmdPath("jar");
4258 if(not $JarCmd) {
4259 exitStatus("Not_Found", "can't find \"jar\" command");
4260 }
4261
4262 my $TmpDir = $In::Opt{"Tmp"};
4263
4264 chdir($TmpDir);
4265 system($JarCmd." -xf \"$Path\" META-INF 2>null");
4266 chdir($In::Opt{"OrigDir"});
4267
4268 my $Manifest = $TmpDir."/META-INF/MANIFEST.MF";
4269
4270 if(-f $Manifest)
4271 {
4272 if(my $Content = readFile($Manifest))
4273 {
4274 if($Content=~/(\A|\s)Implementation\-Version:\s*(.+)(\s|\Z)/i) {
4275 return $2;
4276 }
4277 }
4278 }
4279 return undef;
4280}
4281
4282sub parseVersion($)
4283{
4284 my $Str = $_[0];
4285
4286 if(not $Str) {
4287 return undef;
4288 }
4289
4290 if($Str=~/(\/|\\|\w|\A)[\-\_]*(\d+[\d\.\-]+\d+|\d+)/) {
4291 return $2;
4292 }
4293 return undef;
4294}
4295
4296sub getPkgVersion($)
4297{
4298 my $Name = $_[0];
4299 $Name=~s/\.\w+\Z//;
4300 if($Name=~/\A(.+[a-z])[\-\_](v|ver|)(\d.+?)\Z/i)
4301 { # libsample-N
4302 # libsample-vN
4303 return ($1, $3);
4304 }
4305 elsif($Name=~/\A(.+?)(\d[\d\.]*)\Z/i)
4306 { # libsampleN
4307 return ($1, $2);
4308 }
4309 elsif($Name=~/\A(.+)[\-\_](v|ver|)(\d.+?)\Z/i)
4310 { # libsample-N
4311 # libsample-vN
4312 return ($1, $3);
4313 }
4314 elsif($Name=~/\A([a-z_\-]+)(\d.+?)\Z/i)
4315 { # libsampleNb
4316 return ($1, $2);
4317 }
4318 return (undef, undef);
4319}
4320
4321sub dumpSorting($)
4322{
4323 my $Hash = $_[0];
4324 return [] if(not $Hash);
4325 my @Keys = keys(%{$Hash});
4326 return [] if($#Keys<0);
4327 if($Keys[0]=~/\A\d+\Z/)
4328 { # numbers
4329 return [sort {int($a)<=>int($b)} @Keys];
4330 }
4331 else
4332 { # strings
4333 return [sort {$a cmp $b} @Keys];
4334 }
4335}
4336
4337sub printStatMsg($)
4338{
4339 my $Level = $_[0];
4340 printMsg("INFO", "Total ".lc($Level)." compatibility problems: ".$RESULT{$Level}{"Problems"}.", warnings: ".$RESULT{$Level}{"Warnings"});
4341}
4342
4343sub printReport()
4344{
4345 printMsg("INFO", "Creating compatibility report ...");
4346 createReport();
4347 if($In::Opt{"JoinReport"} or $In::Opt{"DoubleReport"})
4348 {
4349 if($RESULT{"Binary"}{"Problems"}
4350 or $RESULT{"Source"}{"Problems"})
4351 {
4352 printMsg("INFO", "Binary compatibility: ".(100-$RESULT{"Binary"}{"Affected"})."\%");
4353 printMsg("INFO", "Source compatibility: ".(100-$RESULT{"Source"}{"Affected"})."\%");
4354 }
4355 else
4356 {
4357 printMsg("INFO", "Binary compatibility: 100\%");
4358 printMsg("INFO", "Source compatibility: 100\%");
4359 }
4360 printStatMsg("Binary");
4361 printStatMsg("Source");
4362 }
4363 elsif($In::Opt{"BinaryOnly"})
4364 {
4365 if($RESULT{"Binary"}{"Problems"}) {
4366 printMsg("INFO", "Binary compatibility: ".(100-$RESULT{"Binary"}{"Affected"})."\%");
4367 }
4368 else {
4369 printMsg("INFO", "Binary compatibility: 100\%");
4370 }
4371 printStatMsg("Binary");
4372 }
4373 elsif($In::Opt{"SourceOnly"})
4374 {
4375 if($RESULT{"Source"}{"Problems"}) {
4376 printMsg("INFO", "Source compatibility: ".(100-$RESULT{"Source"}{"Affected"})."\%");
4377 }
4378 else {
4379 printMsg("INFO", "Source compatibility: 100\%");
4380 }
4381 printStatMsg("Source");
4382 }
4383 if($In::Opt{"JoinReport"})
4384 {
4385 printMsg("INFO", "Report: ".getReportPath("Join"));
4386 }
4387 elsif($In::Opt{"DoubleReport"})
4388 { # default
4389 printMsg("INFO", "Report (BC): ".getReportPath("Binary"));
4390 printMsg("INFO", "Report (SC): ".getReportPath("Source"));
4391 }
4392 elsif($In::Opt{"BinaryOnly"})
4393 { # --binary
4394 printMsg("INFO", "Report: ".getReportPath("Binary"));
4395 }
4396 elsif($In::Opt{"SourceOnly"})
4397 { # --source
4398 printMsg("INFO", "Report: ".getReportPath("Source"));
4399 }
4400}
4401
4402sub getReportPath($)
4403{
4404 my $Level = $_[0];
4405 my $Dir = "compat_reports/".$In::Opt{"TargetLib"}."/".$In::Desc{1}{"Version"}."_to_".$In::Desc{2}{"Version"};
4406 if($Level eq "Binary")
4407 {
4408 if($In::Opt{"BinaryReportPath"})
4409 { # --bin-report-path
4410 return $In::Opt{"BinaryReportPath"};
4411 }
4412 elsif($In::Opt{"OutputReportPath"})
4413 { # --report-path
4414 return $In::Opt{"OutputReportPath"};
4415 }
4416 else
4417 { # default
4418 return $Dir."/bin_compat_report.html";
4419 }
4420 }
4421 elsif($Level eq "Source")
4422 {
4423 if($In::Opt{"SourceReportPath"})
4424 { # --src-report-path
4425 return $In::Opt{"SourceReportPath"};
4426 }
4427 elsif($In::Opt{"OutputReportPath"})
4428 { # --report-path
4429 return $In::Opt{"OutputReportPath"};
4430 }
4431 else
4432 { # default
4433 return $Dir."/src_compat_report.html";
4434 }
4435 }
4436 else
4437 {
4438 if($In::Opt{"OutputReportPath"})
4439 { # --report-path
4440 return $In::Opt{"OutputReportPath"};
4441 }
4442 else
4443 { # default
4444 return $Dir."/compat_report.html";
4445 }
4446 }
4447}
4448
4449sub unpackDump($)
4450{
4451 my $Path = $_[0];
4452
4453 if(isDump_U($Path)) {
4454 return $Path;
4455 }
4456
4457 my $TmpDir = $In::Opt{"Tmp"};
4458
4459 $Path = getAbsPath($Path);
4460 $Path = pathFmt($Path);
4461
4462 my ($Dir, $FileName) = sepPath($Path);
4463 my $UnpackDir = $TmpDir."/unpack";
4464 if(-d $UnpackDir) {
4465 rmtree($UnpackDir);
4466 }
4467 mkpath($UnpackDir);
4468
4469 if($FileName=~s/\Q.zip\E\Z//g)
4470 { # *.zip
4471 my $UnzipCmd = getCmdPath("unzip");
4472 if(not $UnzipCmd) {
4473 exitStatus("Not_Found", "can't find \"unzip\" command");
4474 }
4475 chdir($UnpackDir);
4476 system("$UnzipCmd \"$Path\" >contents.txt");
4477 chdir($In::Opt{"OrigDir"});
4478 if($?) {
4479 exitStatus("Error", "can't extract \'$Path\'");
4480 }
4481
4482 my @Contents = ();
4483 foreach (split("\n", readFile("$UnpackDir/contents.txt")))
4484 {
4485 if(/inflating:\s*([^\s]+)/) {
4486 push(@Contents, $1);
4487 }
4488 }
4489 if(not @Contents) {
4490 exitStatus("Error", "can't extract \'$Path\'");
4491 }
4492 return join_P($UnpackDir, $Contents[0]);
4493 }
4494 elsif($FileName=~s/\Q.tar.gz\E\Z//g)
4495 { # *.tar.gz
4496 if($In::Opt{"OS"} eq "windows")
4497 { # -xvzf option is not implemented in tar.exe (2003)
4498 # use "gzip.exe -k -d -f" + "tar.exe -xvf" instead
4499 my $TarCmd = getCmdPath("tar");
4500 if(not $TarCmd) {
4501 exitStatus("Not_Found", "can't find \"tar\" command");
4502 }
4503 my $GzipCmd = getCmdPath("gzip");
4504 if(not $GzipCmd) {
4505 exitStatus("Not_Found", "can't find \"gzip\" command");
4506 }
4507 chdir($UnpackDir);
4508 qx/$GzipCmd -k -d -f "$Path"/; # keep input files (-k)
4509 if($?) {
4510 exitStatus("Error", "can't extract \'$Path\'");
4511 }
4512 my @Contents = qx/$TarCmd -xvf "$Dir\\$FileName.tar"/;
4513 chdir($In::Opt{"OrigDir"});
4514 if($? or not @Contents) {
4515 exitStatus("Error", "can't extract \'$Path\'");
4516 }
4517 unlink($Dir."/".$FileName.".tar");
4518 chomp $Contents[0];
4519 return join_P($UnpackDir, $Contents[0]);
4520 }
4521 else
4522 { # Linux, Unix, OS X
4523 my $TarCmd = getCmdPath("tar");
4524 if(not $TarCmd) {
4525 exitStatus("Not_Found", "can't find \"tar\" command");
4526 }
4527 chdir($UnpackDir);
4528 my @Contents = qx/$TarCmd -xvzf "$Path" 2>&1/;
4529 chdir($In::Opt{"OrigDir"});
4530 if($? or not @Contents) {
4531 exitStatus("Error", "can't extract \'$Path\'");
4532 }
4533 $Contents[0]=~s/^x //; # OS X
4534 chomp $Contents[0];
4535 return join_P($UnpackDir, $Contents[0]);
4536 }
4537 }
4538}
4539
4540sub createArchive($$)
4541{
4542 my ($Path, $To) = @_;
4543 if(not $To) {
4544 $To = ".";
4545 }
4546
4547 my $TmpDir = $In::Opt{"Tmp"};
4548
4549 my ($From, $Name) = sepPath($Path);
4550 if($In::Opt{"OS"} eq "windows")
4551 { # *.zip
4552 my $ZipCmd = getCmdPath("zip");
4553 if(not $ZipCmd) {
4554 exitStatus("Not_Found", "can't find \"zip\"");
4555 }
4556 my $Pkg = $To."/".$Name.".zip";
4557 unlink($Pkg);
4558 chdir($To);
4559 system("$ZipCmd -j \"$Name.zip\" \"$Path\" >\"$TmpDir/null\"");
4560 if($?)
4561 { # cannot allocate memory (or other problems with "zip")
4562 chdir($In::Opt{"OrigDir"});
4563 exitStatus("Error", "can't pack the API dump: ".$!);
4564 }
4565 chdir($In::Opt{"OrigDir"});
4566 unlink($Path);
4567 return $Pkg;
4568 }
4569 else
4570 { # *.tar.gz
4571 my $TarCmd = getCmdPath("tar");
4572 if(not $TarCmd) {
4573 exitStatus("Not_Found", "can't find \"tar\"");
4574 }
4575 my $GzipCmd = getCmdPath("gzip");
4576 if(not $GzipCmd) {
4577 exitStatus("Not_Found", "can't find \"gzip\"");
4578 }
4579 my $Pkg = abs_path($To)."/".$Name.".tar.gz";
4580 if(-e $Pkg) {
4581 unlink($Pkg);
4582 }
4583 system($TarCmd, "-C", $From, "-czf", $Pkg, $Name);
4584 if($?)
4585 { # cannot allocate memory (or other problems with "tar")
4586 exitStatus("Error", "can't pack the API dump: ".$!);
4587 }
4588 unlink($Path);
4589 return $To."/".$Name.".tar.gz";
4590 }
4591}
4592
4593sub initAliases($)
4594{
4595 my $LVer = $_[0];
4596
4597 initAPI($LVer);
4598
4599 $MethodInfo{$LVer} = $In::API{$LVer}{"MethodInfo"};
4600 $TypeInfo{$LVer} = $In::API{$LVer}{"TypeInfo"};
4601 $TName_Tid{$LVer} = $In::API{$LVer}{"TName_Tid"};
4602
4603 initAliases_TypeAttr($LVer);
4604}
4605
4606sub createAPIFile($$)
4607{
4608 my ($LVer, $DescPath) = @_;
4609
4610 if(not -e $DescPath) {
4611 exitStatus("Access_Error", "can't access \'$DescPath\'");
4612 }
4613
4614 detectDefaultPaths("bin", "java");
4615
4616 if(isDump($DescPath))
4617 {
4618 $In::API{$LVer} = readAPIDump($LVer, $DescPath, "Main");
4619 initAliases($LVer);
4620
4621 if(my $V = $In::Desc{$LVer}{"TargetVersion"}) {
4622 $In::Desc{$LVer}{"Version"} = $V;
4623 }
4624 else {
4625 $In::Desc{$LVer}{"Version"} = $In::API{$LVer}{"LibraryVersion"};
4626 }
4627 }
4628 else
4629 {
4630 loadModule("APIDump");
4631
4632 checkVersionNum($LVer, $DescPath);
4633 readDesc(createDesc($DescPath, $LVer), $LVer);
4634
4635 initLogging($LVer);
4636
4637 createAPIDump($LVer);
4638 }
4639
4640 if(my $Dep = $In::Desc{$LVer}{"DepDump"}) {
4641 mergeAPIs($LVer, readAPIDump($LVer, $Dep, "Dep"));
4642 }
4643
4644 printMsg("INFO", "Creating library API dump ...");
4645
4646 $In::API{$LVer}{"API_DUMP_VERSION"} = $API_DUMP_VERSION;
4647 $In::API{$LVer}{"JAPI_COMPLIANCE_CHECKER_VERSION"} = $TOOL_VERSION;
4648
4649 foreach ("TName_Tid") {
4650 delete($In::API{$LVer}{$_});
4651 }
4652
4653 my $DumpPath = "api_dumps/".$In::Opt{"TargetLib"}."/".$In::Desc{$LVer}{"Version"}."/API.dump";
4654 if($In::Opt{"OutputDumpPath"})
4655 { # user defined path
4656 $DumpPath = $In::Opt{"OutputDumpPath"};
4657 }
4658
4659 my $ArExt = $In::Opt{"Ar"};
4660 my $Archive = ($DumpPath=~s/\Q.$ArExt\E\Z//g);
4661
4662 if($Archive)
4663 {
4664 my $TarCmd = getCmdPath("tar");
4665 if(not $TarCmd) {
4666 exitStatus("Not_Found", "can't find \"tar\"");
4667 }
4668 my $GzipCmd = getCmdPath("gzip");
4669 if(not $GzipCmd) {
4670 exitStatus("Not_Found", "can't find \"gzip\"");
4671 }
4672 }
4673
4674 my ($DDir, $DName) = sepPath($DumpPath);
4675 my $DPath = $In::Opt{"Tmp"}."/".$DName;
4676 if(not $Archive) {
4677 $DPath = $DumpPath;
4678 }
4679
4680 mkpath($DDir);
4681
4682 open(DUMP, ">", $DPath) || die ("can't open file \'$DPath\': $!\n");
4683 print DUMP Dumper($In::API{$LVer});
4684 close(DUMP);
4685
4686 if(not -s $DPath) {
4687 exitStatus("Error", "can't create API dump because something is going wrong with the Data::Dumper module");
4688 }
4689
4690 if($Archive) {
4691 $DumpPath = createArchive($DPath, $DDir);
4692 }
4693
4694 if($In::Opt{"OutputDumpPath"}) {
4695 printMsg("INFO", "Dump path: ".$In::Opt{"OutputDumpPath"});
4696 }
4697 else {
4698 printMsg("INFO", "Dump path: $DumpPath");
4699 }
4700 exit(0);
4701}
4702
4703sub compareInit()
4704{
4705 if(not $In::Desc{1}{"Path"}) {
4706 exitStatus("Error", "-old option is not specified");
4707 }
4708 if(not -e $In::Desc{1}{"Path"}) {
4709 exitStatus("Access_Error", "can't access \'".$In::Desc{1}{"Path"}."\'");
4710 }
4711 if(not $In::Desc{2}{"Path"}) {
4712 exitStatus("Error", "-new option is not specified");
4713 }
4714 if(not -e $In::Desc{2}{"Path"}) {
4715 exitStatus("Access_Error", "can't access \'".$In::Desc{2}{"Path"}."\'");
4716 }
4717
4718 if($In::Opt{"Quick"})
4719 {
4720 $CompatRules{"Binary"}{"Interface_Added_Super_Interface"}{"Severity"} = "Low";
4721 $CompatRules{"Binary"}{"Abstract_Class_Added_Super_Abstract_Class"}{"Severity"} = "Low";
4722 $CompatRules{"Binary"}{"Abstract_Class_Added_Super_Interface"}{"Severity"} = "Low";
4723 $CompatRules{"Binary"}{"Abstract_Class_Added_Abstract_Method"}{"Severity"} = "Low";
4724 $CompatRules{"Binary"}{"Interface_Added_Abstract_Method"}{"Severity"} = "Low";
4725 }
4726
4727 printMsg("INFO", "Preparing, please wait ...");
4728
4729 detectDefaultPaths("bin", undef);
4730
4731 if(isDump($In::Desc{1}{"Path"}))
4732 {
4733 $In::API{1} = readAPIDump(1, $In::Desc{1}{"Path"}, "Main");
4734 initAliases(1);
4735
4736 if(my $V = $In::Desc{1}{"TargetVersion"}) {
4737 $In::Desc{1}{"Version"} = $V;
4738 }
4739 else {
4740 $In::Desc{1}{"Version"} = $In::API{1}{"LibraryVersion"};
4741 }
4742 }
4743 else
4744 {
4745 loadModule("APIDump");
4746
4747 checkVersionNum(1, $In::Desc{1}{"Path"});
4748 readDesc(createDesc($In::Desc{1}{"Path"}, 1), 1);
4749
4750 initLogging(1);
4751 detectDefaultPaths(undef, "java");
4752 createAPIDump(1);
4753 }
4754
4755 if(my $Dep = $In::Desc{1}{"DepDump"}) {
4756 mergeAPIs(1, readAPIDump(1, $Dep, "Dep"));
4757 }
4758
4759 if(isDump($In::Desc{2}{"Path"}))
4760 {
4761 $In::API{2} = readAPIDump(2, $In::Desc{2}{"Path"}, "Main");
4762 initAliases(2);
4763
4764 if(my $V = $In::Desc{2}{"TargetVersion"}) {
4765 $In::Desc{2}{"Version"} = $V;
4766 }
4767 else {
4768 $In::Desc{2}{"Version"} = $In::API{2}{"LibraryVersion"};
4769 }
4770 }
4771 else
4772 {
4773 loadModule("APIDump");
4774
4775 checkVersionNum(2, $In::Desc{2}{"Path"});
4776 readDesc(createDesc($In::Desc{2}{"Path"}, 2), 2);
4777
4778 initLogging(2);
4779 detectDefaultPaths(undef, "java");
4780 createAPIDump(2);
4781 }
4782
4783 if(my $Dep = $In::Desc{2}{"DepDump"}) {
4784 mergeAPIs(2, readAPIDump(2, $Dep, "Dep"));
4785 }
4786
4787 prepareData(1);
4788 prepareData(2);
4789
4790 foreach my $Inv (keys(%{$MethodUsed{2}}))
4791 {
4792 foreach my $M (keys(%{$MethodUsed{2}{$Inv}}))
4793 {
4794 my $InvType = $MethodUsed{2}{$Inv}{$M};
4795
4796 if($InvType ne "static"
4797 and index($Inv, "<init>")==-1)
4798 {
4799 my $CName = $Inv;
4800 $CName=~s/\A\"\[L(.+);"/$1/g;
4801 $CName=~s/#/./g;
4802
4803 if($CName=~/\A(.+?)\./)
4804 {
4805 $CName = $1;
4806 if($CName!~/\"/)
4807 {
4808 $CName=~s!/!.!g;
4809 $ClassMethod_AddedUsed{$CName}{$Inv} = $M;
4810 }
4811 }
4812 }
4813
4814 if(not defined $MethodInfo{1}{$M}) {
4815 delete($MethodUsed{2}{$Inv}{$M});
4816 }
4817 }
4818 }
4819
4820 foreach my $ClassName (keys(%ClassMethod_AddedUsed))
4821 {
4822 foreach my $MethodName (keys(%{$ClassMethod_AddedUsed{$ClassName}}))
4823 {
4824 if(defined $MethodInfo{1}{$MethodName}
4825 or defined $MethodInfo{2}{$MethodName}
4826 or defined $MethodUsed{1}{$MethodName}
4827 or findMethod($MethodName, 2, $ClassName, 1))
4828 { # abstract method added by the new super-class (abstract) or super-interface
4829 delete($ClassMethod_AddedUsed{$ClassName}{$MethodName});
4830 }
4831 }
4832 if(not keys(%{$ClassMethod_AddedUsed{$ClassName}})) {
4833 delete($ClassMethod_AddedUsed{$ClassName});
4834 }
4835 }
4836}
4837
4838sub scenario()
4839{
4840 setTarget("default");
4841
4842 initAliases(1);
4843 initAliases(2);
4844
4845 $In::Opt{"OrigDir"} = cwd();
4846 $In::Opt{"Tmp"} = tempdir(CLEANUP=>1);
4847 $In::Opt{"Reproducible"} = 1;
4848
4849 $In::Opt{"JoinReport"} = 1;
4850 $In::Opt{"DoubleReport"} = 0;
4851
4852 if($In::Opt{"BinaryOnly"} and $In::Opt{"SourceOnly"})
4853 { # both --binary and --source
4854 # is the default mode
4855 $In::Opt{"DoubleReport"} = 1;
4856 $In::Opt{"JoinReport"} = 0;
4857 $In::Opt{"BinaryOnly"} = 0;
4858 $In::Opt{"SourceOnly"} = 0;
4859 if($In::Opt{"OutputReportPath"})
4860 { # --report-path
4861 $In::Opt{"DoubleReport"} = 0;
4862 $In::Opt{"JoinReport"} = 1;
4863 }
4864 }
4865 elsif($In::Opt{"BinaryOnly"} or $In::Opt{"SourceOnly"})
4866 { # --binary or --source
4867 $In::Opt{"DoubleReport"} = 0;
4868 $In::Opt{"JoinReport"} = 0;
4869 }
4870 if(defined $In::Opt{"Help"})
4871 {
4872 helpMsg();
4873 exit(0);
4874 }
4875 if(defined $In::Opt{"ShowVersion"})
4876 {
4877 printMsg("INFO", "Java API Compliance Checker (JAPICC) $TOOL_VERSION\nCopyright (C) 2017 Andrey Ponomarenko's ABI Laboratory\nLicense: LGPL or GPL <http://www.gnu.org/licenses/>\nThis program is free software: you can redistribute it and/or modify it.\n\nWritten by Andrey Ponomarenko.");
4878 exit(0);
4879 }
4880 if(defined $In::Opt{"DumpVersion"})
4881 {
4882 printMsg("INFO", $TOOL_VERSION);
4883 exit(0);
4884 }
4885 $Data::Dumper::Sortkeys = 1;
4886
4887 # FIXME: can't pass \&dumpSorting - cause a segfault sometimes
4888 if($In::Opt{"SortDump"})
4889 {
4890 $Data::Dumper::Useperl = 1;
4891 $Data::Dumper::Sortkeys = \&dumpSorting;
4892 }
4893
4894 if(defined $In::Opt{"TestTool"})
4895 {
4896 detectDefaultPaths("bin", "java");
4897 loadModule("RegTests");
4898 testTool();
4899 exit(0);
4900 }
4901
4902 if(defined $In::Opt{"ShortMode"})
4903 {
4904 if(not defined $In::Opt{"AffectLimit"}) {
4905 $In::Opt{"AffectLimit"} = 10;
4906 }
4907 }
4908
4909 if(not $In::Opt{"TargetLib"} and not $In::Opt{"CountMethods"})
4910 {
4911 if($In::Opt{"DumpAPI"})
4912 {
4913 if($In::Opt{"DumpAPI"}=~/\.jar\Z/)
4914 { # short usage
4915 my ($Name, $Version) = getPkgVersion(getFilename($In::Opt{"DumpAPI"}));
4916 if($Name and $Version ne "")
4917 {
4918 $In::Opt{"TargetLib"} = $Name;
4919 if(not $In::Desc{1}{"TargetVersion"}) {
4920 $In::Desc{1}{"TargetVersion"} = $Version;
4921 }
4922 }
4923 }
4924 }
4925 else
4926 {
4927 if($In::Desc{1}{"Path"}=~/\.jar\Z/ and $In::Desc{2}{"Path"}=~/\.jar\Z/)
4928 { # short usage
4929 my ($Name1, $Version1) = getPkgVersion(getFilename($In::Desc{1}{"Path"}));
4930 my ($Name2, $Version2) = getPkgVersion(getFilename($In::Desc{2}{"Path"}));
4931
4932 if($Name1 and $Version1 ne ""
4933 and $Version2 ne "")
4934 {
4935 $In::Opt{"TargetLib"} = $Name1;
4936 if(not $In::Desc{1}{"TargetVersion"}) {
4937 $In::Desc{1}{"TargetVersion"} = $Version1;
4938 }
4939 if(not $In::Desc{2}{"TargetVersion"}) {
4940 $In::Desc{2}{"TargetVersion"} = $Version2;
4941 }
4942 }
4943 }
4944 }
4945
4946 if(not $In::Opt{"TargetLib"}) {
4947 exitStatus("Error", "library name is not selected (option --lib=NAME)");
4948 }
4949 }
4950 else
4951 { # validate library name
4952 if($In::Opt{"TargetLib"}=~/[\*\/\\]/) {
4953 exitStatus("Error", "\"\\\", \"\/\" and \"*\" symbols are not allowed in the library name");
4954 }
4955 }
4956 if(not $In::Opt{"TargetTitle"}) {
4957 $In::Opt{"TargetTitle"} = $In::Opt{"TargetLib"};
4958 }
4959 if(my $ClassListPath = $In::Opt{"ClassListPath"})
4960 {
4961 if(not -f $ClassListPath) {
4962 exitStatus("Access_Error", "can't access file \'$ClassListPath\'");
4963 }
4964 foreach my $Class (split(/\n/, readFile($ClassListPath)))
4965 {
4966 $Class=~s/\//./g;
4967 $In::Opt{"ClassList_User"}{$Class} = 1;
4968 }
4969 }
4970 if(my $AnnotationsListPath = $In::Opt{"AnnotationsListPath"})
4971 {
4972 if(not -f $AnnotationsListPath) {
4973 exitStatus("Access_Error", "can't access file \'$AnnotationsListPath\'");
4974 }
4975 foreach my $Annotation (split(/\n/, readFile($AnnotationsListPath)))
4976 {
4977 $In::Opt{"AnnotationList_User"}{$Annotation} = 1;
4978 }
4979 }
4980 if(my $SkipAnnotationsListPath = $In::Opt{"SkipAnnotationsListPath"})
4981 {
4982 if(not -f $SkipAnnotationsListPath) {
4983 exitStatus("Access_Error", "can't access file \'$SkipAnnotationsListPath\'");
4984 }
4985 foreach my $Annotation (split(/\n/, readFile($SkipAnnotationsListPath)))
4986 {
4987 $In::Opt{"SkipAnnotationList_User"}{$Annotation} = 1;
4988 }
4989 }
4990 if(my $SkipClassesList = $In::Opt{"SkipClassesList"})
4991 {
4992 if(not -f $SkipClassesList) {
4993 exitStatus("Access_Error", "can't access file \'$SkipClassesList\'");
4994 }
4995 foreach my $Class (split(/\n/, readFile($SkipClassesList)))
4996 {
4997 $Class=~s/\//./g;
4998 $In::Opt{"SkipClasses"}{$Class} = 1;
4999 }
5000 }
5001 if(my $SkipPackagesList = $In::Opt{"SkipPackagesList"})
5002 {
5003 if(not -f $SkipPackagesList) {
5004 exitStatus("Access_Error", "can't access file \'$SkipPackagesList\'");
5005 }
5006 foreach my $Package (split(/\n/, readFile($SkipPackagesList)))
5007 {
5008 $In::Desc{1}{"SkipPackages"}{$Package} = 1;
5009 $In::Desc{2}{"SkipPackages"}{$Package} = 1;
5010 }
5011 }
5012 if(my $ClientPath = $In::Opt{"ClientPath"})
5013 {
5014 if($ClientPath=~/\.class\Z/) {
5015 exitStatus("Error", "input file is not a java archive");
5016 }
5017
5018 if(-f $ClientPath)
5019 {
5020 detectDefaultPaths("bin", undef);
5021 loadModule("APIDump");
5022
5023 readArchive(0, $ClientPath)
5024 }
5025 else {
5026 exitStatus("Access_Error", "can't access file \'$ClientPath\'");
5027 }
5028 }
5029
5030 if(my $DPath = $In::Opt{"CountMethods"})
5031 {
5032 if(not -e $DPath) {
5033 exitStatus("Access_Error", "can't access \'$DPath\'");
5034 }
5035
5036 $In::API{1} = readAPIDump(1, $DPath, "Main");
5037 initAliases(1);
5038
5039 my $Count = 0;
5040 foreach my $Method (keys(%{$MethodInfo{1}}))
5041 {
5042 $Count += methodFilter($Method, 1);
5043 }
5044
5045 printMsg("INFO", $Count);
5046 exit(0);
5047 }
5048
5049 if($In::Opt{"DumpAPI"})
5050 {
5051 createAPIFile(1, $In::Opt{"DumpAPI"});
5052 exit(0);
5053 }
5054
5055 compareInit();
5056
5057 readRules("Binary");
5058 readRules("Source");
5059
5060 detectAdded();
5061 detectRemoved();
5062
5063 printMsg("INFO", "Comparing classes ...");
5064 mergeClasses();
5065 mergeMethods();
5066
5067 printReport();
5068
5069 if($RESULT{"Source"}{"Problems"} + $RESULT{"Binary"}{"Problems"}) {
5070 exit(getErrorCode("Incompatible"));
5071 }
5072 else {
5073 exit(getErrorCode("Compatible"));
5074 }
5075}
5076
5077scenario();
Note: See TracBrowser for help on using the repository browser.