";
}
else {
$Signature .= "";
}
if(@Params)
{
foreach my $Pos (0 .. $#Params)
{
my $Name = "";
if($Pos==0) {
$Name .= "( ";
}
$Name .= $Params[$Pos];
$Name = "".$Name."";
if($Pos==$#Params) {
$Name .= " )";
}
else {
$Name .= ", ";
}
$Signature .= $Name;
}
}
else {
$Signature .= "( )";
}
$Signature .= "";
}
else
{
if(@Params) {
$Signature .= " ( ".join(", ", @Params)." )";
}
else {
$Signature .= " ( )";
}
}
if($Full or $ShowAttr)
{
if($MethodInfo{$LVer}{$Method}{"Static"}) {
$Signature .= " [static]";
}
elsif($MethodInfo{$LVer}{$Method}{"Abstract"}) {
$Signature .= " [abstract]";
}
}
if($Full)
{
if($In::Opt{"ShowAccess"})
{
if(my $Access = $MethodInfo{$LVer}{$Method}{"Access"})
{
if($Access ne "public") {
$Signature .= " [".$Access."]";
}
}
}
}
if($Full or $ShowReturn)
{
if(my $ReturnId = $MethodInfo{$LVer}{$Method}{"Return"})
{
my $RName = getTypeName($ReturnId, $LVer);
if($In::Opt{"HideTemplates"}) {
$RName=~s/<.*>//g;
}
if(not $In::Opt{"ShowPackages"}) {
$RName=~s/(\A|\<\s*|\,\s*)[a-z0-9\.]+\./$1/g;
}
if($Desc) {
$Signature = "".specChars($RName)." ".$Signature;
}
elsif($Simple) {
$Signature .= " : ".specChars($RName);
}
elsif($Html) {
$Signature .= " : ".specChars($RName)."";
}
else {
$Signature .= " : ".$RName;
}
}
}
if($Full)
{
if(not $In::Opt{"SkipDeprecated"})
{
if($MethodInfo{$LVer}{$Method}{"Deprecated"}) {
$Signature .= " *DEPRECATED*";
}
}
}
$Signature=~s/java\.lang\.//g;
if($Html)
{
if(not $In::Opt{"SkipDeprecated"}) {
$Signature=~s!(\*deprecated\*)!$1!ig;
}
$Signature=~s!(\[static\]|\[abstract\]|\[public\]|\[private\]|\[protected\])!$1!g;
}
if($Simple) {
$Signature=~s/\[\]/\[ \]/g;
}
elsif($Html)
{
$Signature=~s!\[\]![ ]!g;
$Signature=~s!operator=!operator =!g;
}
return ($Cache{"getSignature"}{$LVer}{$Method}{$Kind} = $Signature);
}
sub getReportHeader($)
{
my $Level = $_[0];
my $Report_Header = "";
if($Level eq "Source") {
$Report_Header .= "Source compatibility";
}
elsif($Level eq "Binary") {
$Report_Header .= "Binary compatibility";
}
else {
$Report_Header .= "API compatibility";
}
$Report_Header .= " report for the ".$In::Opt{"TargetTitle"}." library between ".$In::Desc{1}{"Version"}." and ".$In::Desc{2}{"Version"}." versions";
if($In::Opt{"ClientPath"}) {
$Report_Header .= " (concerning portability of the client: ".getFilename($In::Opt{"ClientPath"}).")";
}
$Report_Header .= "
\n";
return $Report_Header;
}
sub getSourceInfo()
{
my $CheckedArchives = "";
if($In::Opt{"OldStyle"}) {
$CheckedArchives .= "Java Archives (".keys(%{$LibArchives{1}}).")
";
}
else {
$CheckedArchives .= "Java Archives ".keys(%{$LibArchives{1}})."
";
}
$CheckedArchives .= "\n
\n";
foreach my $ArchivePath (sort {lc($a) cmp lc($b)} keys(%{$LibArchives{1}})) {
$CheckedArchives .= getFilename($ArchivePath)."
\n";
}
$CheckedArchives .= "
$TOP_REF
\n";
return $CheckedArchives;
}
sub getTypeProblemsCount($$)
{
my ($TargetSeverity, $Level) = @_;
my $Type_Problems_Count = 0;
foreach my $Type_Name (sort keys(%{$TypeChanges{$Level}}))
{
my %Kinds_Target = ();
foreach my $Kind (sort keys(%{$TypeChanges{$Level}{$Type_Name}}))
{
if($CompatRules{$Level}{$Kind}{"Severity"} ne $TargetSeverity) {
next;
}
foreach my $Location (sort keys(%{$TypeChanges{$Level}{$Type_Name}{$Kind}}))
{
my $Target = $TypeChanges{$Level}{$Type_Name}{$Kind}{$Location}{"Target"};
if($Kinds_Target{$Kind}{$Target}) {
next;
}
$Kinds_Target{$Kind}{$Target} = 1;
$Type_Problems_Count += 1;
}
}
}
return $Type_Problems_Count;
}
sub showNum($)
{
if($_[0])
{
my $Num = cutNum($_[0], 2, 0);
if($Num eq "0")
{
foreach my $P (3 .. 7)
{
$Num = cutNum($_[0], $P, 1);
if($Num ne "0") {
last;
}
}
}
if($Num eq "0") {
$Num = $_[0];
}
return $Num;
}
return $_[0];
}
sub cutNum($$$)
{
my ($num, $digs_to_cut, $z) = @_;
if($num!~/\./)
{
$num .= ".";
foreach (1 .. $digs_to_cut-1) {
$num .= "0";
}
}
elsif($num=~/\.(.+)\Z/ and length($1)<$digs_to_cut-1)
{
foreach (1 .. $digs_to_cut - 1 - length($1)) {
$num .= "0";
}
}
elsif($num=~/\d+\.(\d){$digs_to_cut,}/) {
$num=sprintf("%.".($digs_to_cut-1)."f", $num);
}
$num=~s/\.[0]+\Z//g;
if($z) {
$num=~s/(\.[1-9]+)[0]+\Z/$1/g;
}
return $num;
}
sub getSummary($)
{
my $Level = $_[0];
my ($Added, $Removed, $M_Problems_High, $M_Problems_Medium, $M_Problems_Low,
$T_Problems_High, $T_Problems_Medium, $T_Problems_Low, $M_Other, $T_Other) = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
%{$RESULT{$Level}} = (
"Problems"=>0,
"Warnings"=>0,
"Affected"=>0);
# check rules
foreach my $Method (sort keys(%CompatProblems))
{
foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
{
if(not defined $CompatRules{"Binary"}{$Kind} and not defined $CompatRules{"Source"}{$Kind})
{ # unknown rule
if(not $UnknownRules{$Level}{$Kind})
{ # only one warning
printMsg("WARNING", "unknown rule \"$Kind\" (\"$Level\")");
$UnknownRules{$Level}{$Kind} = 1;
}
}
}
}
foreach my $Method (sort keys(%CompatProblems))
{
foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
{
if($CompatRules{$Level}{$Kind}{"Kind"} eq "Methods")
{
my $Severity = $CompatRules{$Level}{$Kind}{"Severity"};
foreach my $Loc (sort keys(%{$CompatProblems{$Method}{$Kind}}))
{
if($Kind eq "Added_Method")
{
if($Level eq "Source")
{
if($ChangedReturnFromVoid{$Method}) {
next;
}
}
$Added+=1;
}
elsif($Kind eq "Removed_Method")
{
if($Level eq "Source")
{
if($ChangedReturnFromVoid{$Method}) {
next;
}
}
$Removed+=1;
$TotalAffected{$Level}{$Method} = $Severity;
}
else
{
if($Severity eq "Safe") {
$M_Other += 1;
}
elsif($Severity eq "High") {
$M_Problems_High+=1;
}
elsif($Severity eq "Medium") {
$M_Problems_Medium+=1;
}
elsif($Severity eq "Low") {
$M_Problems_Low+=1;
}
if(($Severity ne "Low" or $In::Opt{"StrictCompat"})
and $Severity ne "Safe") {
$TotalAffected{$Level}{$Method} = $Severity;
}
}
}
}
}
}
my %MethodTypeIndex = ();
foreach my $Method (sort keys(%CompatProblems))
{
foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
{
if($CompatRules{$Level}{$Kind}{"Kind"} eq "Types")
{
my $Severity = $CompatRules{$Level}{$Kind}{"Severity"};
if(($Severity ne "Low" or $In::Opt{"StrictCompat"})
and $Severity ne "Safe")
{
if(my $Sev = $TotalAffected{$Level}{$Method})
{
if($Severity_Val{$Severity}>$Severity_Val{$Sev}) {
$TotalAffected{$Level}{$Method} = $Severity;
}
}
else {
$TotalAffected{$Level}{$Method} = $Severity;
}
}
my $MK = $CompatProblems{$Method}{$Kind};
my (@Locs1, @Locs2) = ();
foreach my $Loc (sort {length($a)<=>length($b)} sort keys(%{$MK}))
{
if(index($Loc, "retval")==0 or index($Loc, "this")==0) {
push(@Locs2, $Loc);
}
else {
push(@Locs1, $Loc);
}
}
foreach my $Loc (@Locs1, @Locs2)
{
my $Type = $MK->{$Loc}{"Type_Name"};
my $Target = $MK->{$Loc}{"Target"};
if(defined $MethodTypeIndex{$Method}{$Type}{$Kind}{$Target})
{ # one location for one type and target
next;
}
$MethodTypeIndex{$Method}{$Type}{$Kind}{$Target} = 1;
$TypeChanges{$Level}{$Type}{$Kind}{$Loc} = $MK->{$Loc};
$TypeProblemsIndex{$Level}{$Type}{$Kind}{$Loc}{$Method} = 1;
}
}
}
}
%MethodTypeIndex = (); # clear memory
$T_Problems_High = getTypeProblemsCount("High", $Level);
$T_Problems_Medium = getTypeProblemsCount("Medium", $Level);
$T_Problems_Low = getTypeProblemsCount("Low", $Level);
$T_Other = getTypeProblemsCount("Safe", $Level);
my $SCount = keys(%CheckedMethods)-$Added;
if($SCount)
{
my %Weight = (
"High" => 100,
"Medium" => 50,
"Low" => 25
);
foreach (keys(%{$TotalAffected{$Level}})) {
$RESULT{$Level}{"Affected"}+=$Weight{$TotalAffected{$Level}{$_}};
}
$RESULT{$Level}{"Affected"} = $RESULT{$Level}{"Affected"}/$SCount;
}
else {
$RESULT{$Level}{"Affected"} = 0;
}
$RESULT{$Level}{"Affected"} = showNum($RESULT{$Level}{"Affected"});
if($RESULT{$Level}{"Affected"}>=100) {
$RESULT{$Level}{"Affected"} = 100;
}
my ($TestInfo, $TestResults, $Problem_Summary) = ();
# test info
$TestInfo .= "Test Info
\n";
$TestInfo .= "\n";
$TestInfo .= "Library Name | ".$In::Opt{"TargetTitle"}." |
\n";
$TestInfo .= "Version #1 | ".$In::Desc{1}{"Version"}." |
\n";
$TestInfo .= "Version #2 | ".$In::Desc{2}{"Version"}." |
\n";
if($In::Opt{"JoinReport"})
{
if($Level eq "Binary") {
$TestInfo .= "Subject | Binary Compatibility |
\n"; # Run-time
}
if($Level eq "Source") {
$TestInfo .= "Subject | Source Compatibility |
\n"; # Build-time
}
}
$TestInfo .= "
\n";
# test results
$TestResults .= "Test Results
\n";
$TestResults .= "\n";
my $Checked_Archives_Link = "0";
$Checked_Archives_Link = "".keys(%{$LibArchives{1}})."" if(keys(%{$LibArchives{1}})>0);
$TestResults .= "Total Java Modules | $Checked_Archives_Link |
\n";
$TestResults .= "Total Methods / Classes | ".keys(%CheckedMethods)." / ".keys(%CheckedTypes)." |
\n";
$RESULT{$Level}{"Problems"} += $Removed+$M_Problems_High+$T_Problems_High+$T_Problems_Medium+$M_Problems_Medium;
if($In::Opt{"StrictCompat"}) {
$RESULT{$Level}{"Problems"}+=$T_Problems_Low+$M_Problems_Low;
}
else {
$RESULT{$Level}{"Warnings"}+=$T_Problems_Low+$M_Problems_Low;
}
my $META_DATA = "kind:".lc($Level).";";
$META_DATA .= $RESULT{$Level}{"Problems"}?"verdict:incompatible;":"verdict:compatible;";
$TestResults .= "Compatibility | \n";
my $BC_Rate = showNum(100 - $RESULT{$Level}{"Affected"});
if($RESULT{$Level}{"Problems"})
{
my $Cl = "incompatible";
if($BC_Rate>=90) {
$Cl = "warning";
}
elsif($BC_Rate>=80) {
$Cl = "almost_compatible";
}
$TestResults .= "".$BC_Rate."% | \n";
}
else
{
$TestResults .= "100% | \n";
}
$TestResults .= "
\n";
$TestResults .= "
\n";
$META_DATA .= "affected:".$RESULT{$Level}{"Affected"}.";";# in percents
# Problem Summary
$Problem_Summary .= "Problem Summary
\n";
$Problem_Summary .= "\n";
$Problem_Summary .= " | Severity | Count |
\n";
my $Added_Link = "0";
if($Added>0)
{
if($In::Opt{"ShortMode"}) {
$Added_Link = $Added;
}
else
{
if($In::Opt{"JoinReport"}) {
$Added_Link = "$Added";
}
else {
$Added_Link = "$Added";
}
}
}
$META_DATA .= "added:$Added;";
$Problem_Summary .= "Added Methods | - | $Added_Link |
\n";
my $Removed_Link = "0";
if($Removed>0)
{
if($In::Opt{"ShortMode"}) {
$Removed_Link = $Removed;
}
else
{
if($In::Opt{"JoinReport"}) {
$Removed_Link = "$Removed"
}
else {
$Removed_Link = "$Removed"
}
}
}
$META_DATA .= "removed:$Removed;";
$Problem_Summary .= "Removed Methods | ";
$Problem_Summary .= "High | $Removed_Link |
\n";
my $TH_Link = "0";
$TH_Link = "$T_Problems_High" if($T_Problems_High>0);
$META_DATA .= "type_problems_high:$T_Problems_High;";
$Problem_Summary .= "Problems with Data Types | ";
$Problem_Summary .= "High | $TH_Link |
\n";
my $TM_Link = "0";
$TM_Link = "$T_Problems_Medium" if($T_Problems_Medium>0);
$META_DATA .= "type_problems_medium:$T_Problems_Medium;";
$Problem_Summary .= "Medium | $TM_Link |
\n";
my $TL_Link = "0";
$TL_Link = "$T_Problems_Low" if($T_Problems_Low>0);
$META_DATA .= "type_problems_low:$T_Problems_Low;";
$Problem_Summary .= "Low | $TL_Link |
\n";
my $MH_Link = "0";
$MH_Link = "$M_Problems_High" if($M_Problems_High>0);
$META_DATA .= "method_problems_high:$M_Problems_High;";
$Problem_Summary .= "Problems with Methods | ";
$Problem_Summary .= "High | $MH_Link |
\n";
my $MM_Link = "0";
$MM_Link = "$M_Problems_Medium" if($M_Problems_Medium>0);
$META_DATA .= "method_problems_medium:$M_Problems_Medium;";
$Problem_Summary .= "Medium | $MM_Link |
\n";
my $ML_Link = "0";
$ML_Link = "$M_Problems_Low" if($M_Problems_Low>0);
$META_DATA .= "method_problems_low:$M_Problems_Low;";
$Problem_Summary .= "Low | $ML_Link |
\n";
# Safe Changes
if($T_Other)
{
my $TS_Link = "$T_Other";
$Problem_Summary .= "Other Changes in Data Types | - | $TS_Link |
\n";
}
if($M_Other)
{
my $MS_Link = "$M_Other";
$Problem_Summary .= "Other Changes in Methods | - | $MS_Link |
\n";
}
$META_DATA .= "checked_methods:".keys(%CheckedMethods).";";
$META_DATA .= "checked_types:".keys(%CheckedTypes).";";
$META_DATA .= "tool_version:$TOOL_VERSION";
$Problem_Summary .= "
\n";
my $AnyChanged = ($Added or $Removed or $M_Problems_High or $M_Problems_Medium or $M_Problems_Low or
$T_Problems_High or $T_Problems_Medium or $T_Problems_Low or $M_Other or $T_Other);
return ($TestInfo.$TestResults.$Problem_Summary, $META_DATA, $AnyChanged);
}
sub getStyle($$$)
{
my ($Subj, $Act, $Num) = @_;
my %Style = (
"Added"=>"new",
"Removed"=>"failed",
"Safe"=>"passed",
"Low"=>"warning",
"Medium"=>"failed",
"High"=>"failed"
);
if($Num>0) {
return " class='".$Style{$Act}."'";
}
return "";
}
sub getAnchor($$$)
{
my ($Kind, $Level, $Severity) = @_;
if($In::Opt{"JoinReport"})
{
if($Severity eq "Safe") {
return "Other_".$Level."_Changes_In_".$Kind."s";
}
else {
return $Kind."_".$Level."_Problems_".$Severity;
}
}
else
{
if($Severity eq "Safe") {
return "Other_Changes_In_".$Kind."s";
}
else {
return $Kind."_Problems_".$Severity;
}
}
}
sub getReportAdded($)
{
if($In::Opt{"ShortMode"}) {
return "";
}
my $Level = $_[0];
my ($ADDED_METHODS, %MethodAddedInArchiveClass);
foreach my $Method (sort keys(%CompatProblems))
{
foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
{
if($Kind eq "Added_Method")
{
my $ArchiveName = $MethodInfo{2}{$Method}{"Archive"};
my $ClassName = getShortName($MethodInfo{2}{$Method}{"Class"}, 2);
if($Level eq "Source")
{
if($ChangedReturnFromVoid{$Method}) {
next;
}
}
$MethodAddedInArchiveClass{$ArchiveName}{$ClassName}{$Method} = 1;
}
}
}
my $Added_Number = 0;
foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%MethodAddedInArchiveClass))
{
foreach my $ClassName (sort {lc($a) cmp lc($b)} keys(%{$MethodAddedInArchiveClass{$ArchiveName}}))
{
my %NameSpace_Method = ();
foreach my $Method (keys(%{$MethodAddedInArchiveClass{$ArchiveName}{$ClassName}})) {
$NameSpace_Method{$MethodInfo{2}{$Method}{"Package"}}{$Method} = 1;
}
my $ShowClass = $ClassName;
$ShowClass=~s/<.*>//g;
foreach my $NameSpace (sort keys(%NameSpace_Method))
{
$ADDED_METHODS .= "$ArchiveName, ".specChars($ShowClass).".class
\n";
if($NameSpace) {
$ADDED_METHODS .= "package $NameSpace
\n";
}
if($In::Opt{"Compact"}) {
$ADDED_METHODS .= "";
}
my @SortedMethods = sort {lc($MethodInfo{2}{$a}{"Signature"}) cmp lc($MethodInfo{2}{$b}{"Signature"})} sort keys(%{$NameSpace_Method{$NameSpace}});
foreach my $Method (@SortedMethods)
{
$Added_Number += 1;
my $Signature = undef;
if($In::Opt{"Compact"}) {
$Signature = getSignature($Method, 2, "Full|HTML|Simple");
}
else {
$Signature = highLight_ItalicColor($Method, 2);
}
if($NameSpace) {
$Signature=~s/(\W|\A)\Q$NameSpace\E\.(\w)/$1$2/g;
}
if($In::Opt{"Compact"}) {
$ADDED_METHODS .= " ".$Signature."
\n";
}
else {
$ADDED_METHODS .= insertIDs($ContentSpanStart.$Signature.$ContentSpanEnd."
\n".$ContentDivStart."".specChars($Method)."
".$ContentDivEnd."\n");
}
}
if($In::Opt{"Compact"}) {
$ADDED_METHODS .= "
";
}
$ADDED_METHODS .= "
\n";
}
}
}
if($ADDED_METHODS)
{
my $Anchor = "";
if($In::Opt{"JoinReport"}) {
$Anchor = "";
}
if($In::Opt{"OldStyle"}) {
$ADDED_METHODS = "Added Methods ($Added_Number)
\n".$ADDED_METHODS;
}
else {
$ADDED_METHODS = "Added Methods $Added_Number
\n".$ADDED_METHODS;
}
$ADDED_METHODS = $Anchor.$ADDED_METHODS.$TOP_REF."
\n";
}
return $ADDED_METHODS;
}
sub getReportRemoved($)
{
if($In::Opt{"ShortMode"}) {
return "";
}
my $Level = $_[0];
my ($REMOVED_METHODS, %MethodRemovedFromArchiveClass);
foreach my $Method (sort keys(%CompatProblems))
{
foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
{
if($Kind eq "Removed_Method")
{
if($Level eq "Source")
{
if($ChangedReturnFromVoid{$Method}) {
next;
}
}
my $ArchiveName = $MethodInfo{1}{$Method}{"Archive"};
my $ClassName = getShortName($MethodInfo{1}{$Method}{"Class"}, 1);
$MethodRemovedFromArchiveClass{$ArchiveName}{$ClassName}{$Method} = 1;
}
}
}
my $Removed_Number = 0;
foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%MethodRemovedFromArchiveClass))
{
foreach my $ClassName (sort {lc($a) cmp lc($b)} keys(%{$MethodRemovedFromArchiveClass{$ArchiveName}}))
{
my %NameSpace_Method = ();
foreach my $Method (keys(%{$MethodRemovedFromArchiveClass{$ArchiveName}{$ClassName}}))
{
$NameSpace_Method{$MethodInfo{1}{$Method}{"Package"}}{$Method} = 1;
}
my $ShowClass = $ClassName;
$ShowClass=~s/<.*>//g;
foreach my $NameSpace (sort keys(%NameSpace_Method))
{
$REMOVED_METHODS .= "$ArchiveName, ".specChars($ShowClass).".class
\n";
if($NameSpace) {
$REMOVED_METHODS .= "package $NameSpace
\n";
}
if($In::Opt{"Compact"}) {
$REMOVED_METHODS .= "";
}
my @SortedMethods = sort {lc($MethodInfo{1}{$a}{"Signature"}) cmp lc($MethodInfo{1}{$b}{"Signature"})} sort keys(%{$NameSpace_Method{$NameSpace}});
foreach my $Method (@SortedMethods)
{
$Removed_Number += 1;
my $Signature = undef;
if($In::Opt{"Compact"}) {
$Signature = getSignature($Method, 1, "Full|HTML|Simple");
}
else {
$Signature = highLight_ItalicColor($Method, 1);
}
if($NameSpace) {
$Signature=~s/(\W|\A)\Q$NameSpace\E\.(\w)/$1$2/g;
}
if($In::Opt{"Compact"}) {
$REMOVED_METHODS .= " ".$Signature."
\n";
}
else {
$REMOVED_METHODS .= insertIDs($ContentSpanStart.$Signature.$ContentSpanEnd."
\n".$ContentDivStart."".specChars($Method)."
".$ContentDivEnd."\n");
}
}
if($In::Opt{"Compact"}) {
$REMOVED_METHODS .= "
";
}
$REMOVED_METHODS .= "
\n";
}
}
}
if($REMOVED_METHODS)
{
my $Anchor = "";
if($In::Opt{"JoinReport"}) {
$Anchor = "";
}
if($In::Opt{"OldStyle"}) {
$REMOVED_METHODS = "Removed Methods ($Removed_Number)
\n".$REMOVED_METHODS;
}
else {
$REMOVED_METHODS = "Removed Methods $Removed_Number
\n".$REMOVED_METHODS;
}
$REMOVED_METHODS = $Anchor.$REMOVED_METHODS.$TOP_REF."
\n";
}
return $REMOVED_METHODS;
}
sub readRules($)
{
my $Kind = $_[0];
if(not -f $RULES_PATH{$Kind}) {
exitStatus("Module_Error", "can't access \'".$RULES_PATH{$Kind}."\'");
}
my $Content = readFile($RULES_PATH{$Kind});
while(my $Rule = parseTag(\$Content, "rule"))
{
my $RId = parseTag(\$Rule, "id");
my @Properties = ("Severity", "Change", "Effect", "Overcome", "Kind");
foreach my $Prop (@Properties) {
if(my $Value = parseTag(\$Rule, lc($Prop)))
{
$Value=~s/\n[ ]*//;
$CompatRules{$Kind}{$RId}{$Prop} = $Value;
}
}
if($CompatRules{$Kind}{$RId}{"Kind"}=~/\A(Methods|Parameters)\Z/) {
$CompatRules{$Kind}{$RId}{"Kind"} = "Methods";
}
else { # Types, Fields
$CompatRules{$Kind}{$RId}{"Kind"} = "Types";
}
}
}
sub addMarkup($)
{
my $Content = $_[0];
# auto-markup
$Content=~s/\n[ ]*//; # spaces
$Content=~s!([2-9]\))!
$1!g; # 1), 2), ...
if($Content=~/\ANOTE:/)
{ # notes
$Content=~s!(NOTE):!$1:!g;
}
else {
$Content=~s!(NOTE):!
$1:!g;
}
my @Keywords = (
"static",
"abstract",
"default",
"final",
"synchronized"
);
my $MKeys = join("|", @Keywords);
foreach (@Keywords) {
$MKeys .= "|non-".$_;
}
$Content=~s!(became\s*)($MKeys)([^\w-]|\Z)!$1$2$3!ig; # intrinsic types, modifiers
# Markdown
$Content=~s!\*\*([\w\-\@]+)\*\*!$1!ig;
$Content=~s!\*([\w\-]+)\*!$1!ig;
return $Content;
}
sub applyMacroses($$$$$$)
{
my ($Level, $Subj, $Kind, $Content, $Problem, $AddAttr) = @_;
$Content = addMarkup($Content);
# macros
foreach my $Attr (sort {$b cmp $a} (keys(%{$Problem}), keys(%{$AddAttr})))
{
my $Macro = "\@".lc($Attr);
my $Value = undef;
if(defined $Problem->{$Attr}) {
$Value = $Problem->{$Attr};
}
else {
$Value = $AddAttr->{$Attr};
}
if(not defined $Value
or $Value eq "") {
next;
}
if(index($Content, $Macro)==-1) {
next;
}
if($Attr eq "Param_Pos") {
$Value = showPos($Value);
}
if($Attr eq "Invoked") {
$Value = blackName(specChars($Value));
}
elsif($Value=~/\s/) {
$Value = "".specChars($Value)."";
}
else
{
my $Fmt = "Class|HTML|Desc";
if($Attr ne "Invoked_By")
{
if($Attr eq "Method_Short"
or $Kind!~/Overridden|Moved_Up/) {
$Fmt = "HTML|Desc";
}
}
if($Subj eq "Change") {
$Fmt .= "|Return";
}
if(defined $MethodInfo{1}{$Value}
and defined $MethodInfo{1}{$Value}{"ShortName"}) {
$Value = blackName(getSignature($Value, 1, $Fmt));
}
elsif(defined $MethodInfo{2}{$Value}
and defined $MethodInfo{2}{$Value}{"ShortName"}) {
$Value = blackName(getSignature($Value, 2, $Fmt));
}
else
{
$Value = specChars($Value);
if($Attr ne "Type_Type") {
$Value = "".$Value."";
}
}
}
$Content=~s/\Q$Macro\E/$Value/g;
}
if($Content=~/(\A|[^\@\w])(\@\w+)/)
{
if(not $IncompleteRules{$Level}{$Kind})
{ # only one warning
printMsg("WARNING", "incomplete $2 in the rule \"$Kind\" (\"$Level\")");
$IncompleteRules{$Level}{$Kind} = 1;
}
}
return $Content;
}
sub getReportMethodProblems($$)
{
my ($TargetSeverity, $Level) = @_;
my $METHOD_PROBLEMS = "";
my (%ReportMap, %MethodChanges) = ();
foreach my $Method (sort keys(%CompatProblems))
{
my $ArchiveName = $MethodInfo{1}{$Method}{"Archive"};
my $ClassName = getShortName($MethodInfo{1}{$Method}{"Class"}, 1);
foreach my $Kind (sort keys(%{$CompatProblems{$Method}}))
{
if($CompatRules{$Level}{$Kind}{"Kind"} eq "Methods")
{
if($Kind eq "Added_Method"
or $Kind eq "Removed_Method") {
next;
}
if(my $Severity = $CompatRules{$Level}{$Kind}{"Severity"})
{
if($Severity ne $TargetSeverity) {
next;
}
$MethodChanges{$Method}{$Kind} = $CompatProblems{$Method}{$Kind};
$ReportMap{$ArchiveName}{$ClassName}{$Method} = 1;
}
}
}
}
my $ProblemsNum = 0;
foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
foreach my $ClassName (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$ArchiveName}}))
{
my %NameSpace_Method = ();
foreach my $Method (keys(%{$ReportMap{$ArchiveName}{$ClassName}})) {
$NameSpace_Method{$MethodInfo{1}{$Method}{"Package"}}{$Method} = 1;
}
my $ShowClass = $ClassName;
$ShowClass=~s/<.*>//g;
foreach my $NameSpace (sort keys(%NameSpace_Method))
{
$METHOD_PROBLEMS .= "$ArchiveName, ".specChars($ShowClass).".class
\n";
if($NameSpace) {
$METHOD_PROBLEMS .= "package $NameSpace
\n";
}
my @SortedMethods = sort {lc($MethodInfo{1}{$a}{"Signature"}) cmp lc($MethodInfo{1}{$b}{"Signature"})} sort keys(%{$NameSpace_Method{$NameSpace}});
foreach my $Method (@SortedMethods)
{
my %AddAttr = ();
$AddAttr{"Method_Short"} = $Method;
$AddAttr{"Class"} = getTypeName($MethodInfo{1}{$Method}{"Class"}, 1);
my $METHOD_REPORT = "";
my $ProblemNum = 1;
foreach my $Kind (sort keys(%{$MethodChanges{$Method}}))
{
foreach my $Loc (sort keys(%{$MethodChanges{$Method}{$Kind}}))
{
my $ProblemAttr = $MethodChanges{$Method}{$Kind}{$Loc};
if(my $Change = applyMacroses($Level, "Change", $Kind, $CompatRules{$Level}{$Kind}{"Change"}, $ProblemAttr, \%AddAttr))
{
my $Effect = applyMacroses($Level, "Effect", $Kind, $CompatRules{$Level}{$Kind}{"Effect"}, $ProblemAttr, \%AddAttr);
$METHOD_REPORT .= "\n$ProblemNum | \n".$Change." | \n".$Effect." | \n
\n";
$ProblemNum += 1;
$ProblemsNum += 1;
}
}
}
$ProblemNum -= 1;
if($METHOD_REPORT)
{
my $ShowMethod = highLight_ItalicColor($Method, 1);
if($NameSpace)
{
$METHOD_REPORT = cutNs($METHOD_REPORT, $NameSpace);
$ShowMethod = cutNs($ShowMethod, $NameSpace);
}
$METHOD_PROBLEMS .= $ContentSpanStart."[+] ".$ShowMethod;
if($In::Opt{"OldStyle"}) {
$METHOD_PROBLEMS .= " ($ProblemNum)";
}
else {
$METHOD_PROBLEMS .= " $ProblemNum ";
}
$METHOD_PROBLEMS .= $ContentSpanEnd."
\n";
$METHOD_PROBLEMS .= $ContentDivStart;
if(not $In::Opt{"Compact"}) {
$METHOD_PROBLEMS .= "".specChars($Method)."
\n";
}
$METHOD_PROBLEMS .= " | Change | Effect |
$METHOD_REPORT
$ContentDivEnd\n";
}
}
$METHOD_PROBLEMS .= "
";
}
}
}
if($METHOD_PROBLEMS)
{
$METHOD_PROBLEMS = insertIDs($METHOD_PROBLEMS);
my $Title = "Problems with Methods, $TargetSeverity Severity";
if($TargetSeverity eq "Safe")
{ # Safe Changes
$Title = "Other Changes in Methods";
}
if($In::Opt{"OldStyle"}) {
$METHOD_PROBLEMS = "$Title ($ProblemsNum)
\n".$METHOD_PROBLEMS;
}
else {
$METHOD_PROBLEMS = "$Title $ProblemsNum
\n".$METHOD_PROBLEMS;
}
$METHOD_PROBLEMS = "\n".$METHOD_PROBLEMS;
$METHOD_PROBLEMS .= $TOP_REF."
\n";
}
return $METHOD_PROBLEMS;
}
sub showType($$$)
{
my ($Name, $Html, $LVer) = @_;
my $TInfo = $TypeInfo{$LVer}{$TName_Tid{$LVer}{$Name}};
my $TType = $TInfo->{"Type"};
if($TInfo->{"Annotation"}) {
$TType = '@'.$TType;
}
if($Html) {
$Name = "".$TType." ".specChars($Name);
}
else {
$Name = $TType." ".$Name;
}
return $Name;
}
sub getReportTypeProblems($$)
{
my ($TargetSeverity, $Level) = @_;
my $TYPE_PROBLEMS = "";
my %ReportMap = ();
my %TypeChanges_Sev = ();
foreach my $TypeName (keys(%{$TypeChanges{$Level}}))
{
my $ArchiveName = $TypeInfo{1}{$TName_Tid{1}{$TypeName}}{"Archive"};
foreach my $Kind (keys(%{$TypeChanges{$Level}{$TypeName}}))
{
if($CompatRules{$Level}{$Kind}{"Severity"} ne $TargetSeverity) {
next;
}
foreach my $Loc (keys(%{$TypeChanges{$Level}{$TypeName}{$Kind}}))
{
$ReportMap{$ArchiveName}{$TypeName} = 1;
$TypeChanges_Sev{$TypeName}{$Kind}{$Loc} = $TypeChanges{$Level}{$TypeName}{$Kind}{$Loc};
}
}
}
my $ProblemsNum = 0;
foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
my %NameSpace_Type = ();
foreach my $TypeName (keys(%{$ReportMap{$ArchiveName}})) {
$NameSpace_Type{$TypeInfo{1}{$TName_Tid{1}{$TypeName}}{"Package"}}{$TypeName} = 1;
}
foreach my $NameSpace (sort keys(%NameSpace_Type))
{
$TYPE_PROBLEMS .= "$ArchiveName
\n";
if($NameSpace) {
$TYPE_PROBLEMS .= "package ".$NameSpace."
\n";
}
my @SortedTypes = sort {lc(showType($a, 0, 1)) cmp lc(showType($b, 0, 1))} keys(%{$NameSpace_Type{$NameSpace}});
foreach my $TypeName (@SortedTypes)
{
my $TypeId = $TName_Tid{1}{$TypeName};
my $ProblemNum = 1;
my $TYPE_REPORT = "";
my (%Kinds_Locations, %Kinds_Target) = ();
foreach my $Kind (sort keys(%{$TypeChanges_Sev{$TypeName}}))
{
foreach my $Location (sort keys(%{$TypeChanges_Sev{$TypeName}{$Kind}}))
{
$Kinds_Locations{$Kind}{$Location} = 1;
my $Target = $TypeChanges_Sev{$TypeName}{$Kind}{$Location}{"Target"};
if($Kinds_Target{$Kind}{$Target}) {
next;
}
$Kinds_Target{$Kind}{$Target} = 1;
my %AddAttr = ();
if($Kind=~/Method/)
{
if(defined $MethodInfo{1}{$Target} and $MethodInfo{1}{$Target}{"Name"})
{
$AddAttr{"Method_Short"} = $Target;
$AddAttr{"Class"} = getTypeName($MethodInfo{1}{$Target}{"Class"}, 1);
}
elsif(defined $MethodInfo{2}{$Target} and $MethodInfo{2}{$Target}{"Name"})
{
$AddAttr{"Method_Short"} = $Target;
$AddAttr{"Class"} = getTypeName($MethodInfo{2}{$Target}{"Class"}, 2);
}
}
my $ProblemAttr = $TypeChanges_Sev{$TypeName}{$Kind}{$Location};
if(my $Change = applyMacroses($Level, "Change", $Kind, $CompatRules{$Level}{$Kind}{"Change"}, $ProblemAttr, \%AddAttr))
{
my $Effect = applyMacroses($Level, "Effect", $Kind, $CompatRules{$Level}{$Kind}{"Effect"}, $ProblemAttr, \%AddAttr);
$TYPE_REPORT .= "\n$ProblemNum | \n".$Change." | \n".$Effect." | \n
\n";
$ProblemNum += 1;
$ProblemsNum += 1;
}
}
}
$ProblemNum -= 1;
if($TYPE_REPORT)
{
my $Affected = "";
if(not defined $TypeInfo{1}{$TypeId}{"Annotation"}) {
$Affected = getAffectedMethods($Level, $TypeName, \%Kinds_Locations);
}
my $ShowType = showType($TypeName, 1, 1);
if($NameSpace)
{
$TYPE_REPORT = cutNs($TYPE_REPORT, $NameSpace);
$ShowType = cutNs($ShowType, $NameSpace);
$Affected = cutNs($Affected, $NameSpace);
}
$TYPE_PROBLEMS .= $ContentSpanStart."[+] ".$ShowType;
if($In::Opt{"OldStyle"}) {
$TYPE_PROBLEMS .= " ($ProblemNum)";
}
else {
$TYPE_PROBLEMS .= " $ProblemNum ";
}
$TYPE_PROBLEMS .= $ContentSpanEnd."
\n";
$TYPE_PROBLEMS .= $ContentDivStart."";
$TYPE_PROBLEMS .= " | Change | Effect | ";
$TYPE_PROBLEMS .= "
$TYPE_REPORT
".$Affected."
$ContentDivEnd\n";
}
}
$TYPE_PROBLEMS .= "
";
}
}
if($TYPE_PROBLEMS)
{
$TYPE_PROBLEMS = insertIDs($TYPE_PROBLEMS);
my $Title = "Problems with Data Types, $TargetSeverity Severity";
if($TargetSeverity eq "Safe")
{ # Safe Changes
$Title = "Other Changes in Data Types";
}
if($In::Opt{"OldStyle"}) {
$TYPE_PROBLEMS = "$Title ($ProblemsNum)
\n".$TYPE_PROBLEMS;
}
else {
$TYPE_PROBLEMS = "$Title $ProblemsNum
\n".$TYPE_PROBLEMS;
}
$TYPE_PROBLEMS = "\n".$TYPE_PROBLEMS;
$TYPE_PROBLEMS .= $TOP_REF."
\n";
}
return $TYPE_PROBLEMS;
}
sub cutNs($$)
{
my ($N, $Ns) = @_;
$N=~s/(\W|\A)\Q$Ns\E\.(\w)/$1$2/g;
return $N;
}
sub getAffectedMethods($$$)
{
my ($Level, $Target_TypeName, $Kinds_Locations) = @_;
my $LIMIT = 10;
if(defined $In::Opt{"AffectLimit"}) {
$LIMIT = $In::Opt{"AffectLimit"};
}
my %SymSel = ();
foreach my $Kind (sort keys(%{$Kinds_Locations}))
{
my @Locs = sort {(index($a, "retval")!=-1) cmp (index($b, "retval")!=-1)} sort {length($a)<=>length($b)} sort keys(%{$Kinds_Locations->{$Kind}});
foreach my $Loc (@Locs)
{
foreach my $Method (keys(%{$TypeProblemsIndex{$Level}{$Target_TypeName}{$Kind}{$Loc}}))
{
if($Method eq ".client_method") {
next;
}
if(not defined $SymSel{$Method})
{
$SymSel{$Method}{"Kind"} = $Kind;
$SymSel{$Method}{"Loc"} = $Loc;
}
}
}
}
my $Total = keys(%SymSel);
if(not $Total) {
return "";
}
my $Affected = "";
my $SNum = 0;
foreach my $Method (sort {lc($a) cmp lc($b)} keys(%SymSel))
{
my $Kind = $SymSel{$Method}{"Kind"};
my $Loc = $SymSel{$Method}{"Loc"};
my $Desc = getAffectDesc($Method, $Kind, $Loc, $Level);
my $PName = getParamName($Loc);
my $Pos = getParamPos($PName, $Method, 1);
$Affected .= "".getSignature($Method, 1, "HTML|Italic|Param|Class|Target=".$Pos)."
";
$Affected .= "".$Desc."
\n";
if(++$SNum>=$LIMIT) {
last;
}
}
if($Total>$LIMIT) {
$Affected .= " ...\n
\n"; # and others ...
}
$Affected = "".$Affected."
";
if($Affected)
{
my $Per = showNum($Total*100/keys(%CheckedMethods));
$Affected = $ContentDivStart.$Affected.$ContentDivEnd;
$Affected = $ContentSpanStart_Affected."[+] affected methods: $Total ($Per\%)".$ContentSpanEnd.$Affected;
}
return $Affected;
}
sub getAffectDesc($$$$)
{
my ($Method, $Kind, $Location, $Level) = @_;
my %Affect = %{$CompatProblems{$Method}{$Kind}{$Location}};
my $New_Value = $Affect{"New_Value"};
my $Type_Name = $Affect{"Type_Name"};
my @Sentence_Parts = ();
$Location=~s/\.[^.]+?\Z//;
my $TypeAttr = getType($MethodInfo{1}{$Method}{"Class"}, 1);
my $Type_Type = $TypeAttr->{"Type"};
my $ABSTRACT_M = $MethodInfo{1}{$Method}{"Abstract"}?" abstract":"";
my $ABSTRACT_C = $TypeAttr->{"Abstract"}?" abstract":"";
my $METHOD_TYPE = $MethodInfo{1}{$Method}{"Constructor"}?"constructor":"method";
if($Kind eq "Class_Overridden_Method" or $Kind eq "Class_Method_Moved_Up_Hierarchy") {
return "Method '".getSignature($New_Value, 2, "Class|HTML|Italic")."' will be called instead of this method in a client program.";
}
elsif($CompatRules{$Level}{$Kind}{"Kind"} eq "Types")
{
my %MInfo = %{$MethodInfo{1}{$Method}};
if($Location eq "this") {
return "This$ABSTRACT_M $METHOD_TYPE is from \'".specChars($Type_Name)."\'$ABSTRACT_C $Type_Type.";
}
my $TypeID = undef;
if($Location=~/retval/)
{ # return value
if($Location=~/\./) {
push(@Sentence_Parts, "Field \'".specChars($Location)."\' in the return value");
}
else {
push(@Sentence_Parts, "Return value");
}
$TypeID = $MInfo{"Return"};
}
elsif($Location=~/this/)
{ # "this" reference
push(@Sentence_Parts, "Field \'".specChars($Location)."\' in the object");
$TypeID = $MInfo{"Class"};
}
else
{ # parameters
my $PName = getParamName($Location);
my $PPos = getParamPos($PName, $Method, 1);
if($Location=~/\./) {
push(@Sentence_Parts, "Field \'".specChars($Location)."\' in ".showPos($PPos)." parameter");
}
else {
push(@Sentence_Parts, showPos($PPos)." parameter");
}
if($PName) {
push(@Sentence_Parts, "\'$PName\'");
}
if(defined $MInfo{"Param"}) {
$TypeID = $MInfo{"Param"}{$PPos}{"Type"};
}
}
push(@Sentence_Parts, " of this$ABSTRACT_M method");
my $Location_T = $Location;
$Location_T=~s/\A\w+(\.|\Z)//; # location in type
my $TypeID_Problem = $TypeID;
if($Location_T) {
$TypeID_Problem = getFieldType($Location_T, $TypeID, 1);
}
if($TypeInfo{1}{$TypeID_Problem}{"Name"} eq $Type_Name) {
push(@Sentence_Parts, "is of type \'".specChars($Type_Name)."\'.");
}
else {
push(@Sentence_Parts, "has base type \'".specChars($Type_Name)."\'.");
}
}
return join(" ", @Sentence_Parts);
}
sub getParamPos($$$)
{
my ($Name, $Method, $LVer) = @_;
if(defined $MethodInfo{$LVer}{$Method}
and defined $MethodInfo{$LVer}{$Method}{"Param"})
{
my $Info = $MethodInfo{$LVer}{$Method};
foreach (keys(%{$Info->{"Param"}}))
{
if($Info->{"Param"}{$_}{"Name"} eq $Name)
{
return $_;
}
}
}
return undef;
}
sub getParamName($)
{
my $Loc = $_[0];
$Loc=~s/\..*//g;
return $Loc;
}
sub getFieldType($$$)
{
my ($Location, $TypeId, $LVer) = @_;
my @Fields = split(/\./, $Location);
foreach my $Name (@Fields)
{
my $TInfo = getBaseType($TypeId, $LVer);
foreach my $N (keys(%{$TInfo->{"Fields"}}))
{
if($N eq $Name)
{
$TypeId = $TInfo->{"Fields"}{$N}{"Type"};
last;
}
}
}
return $TypeId;
}
sub writeReport($$)
{
my ($Level, $Report) = @_;
my $RPath = getReportPath($Level);
writeFile($RPath, $Report);
}
sub createReport()
{
if($In::Opt{"JoinReport"}) {
writeReport("Join", getReport("Join"));
}
elsif($In::Opt{"DoubleReport"})
{ # default
writeReport("Binary", getReport("Binary"));
writeReport("Source", getReport("Source"));
}
elsif($In::Opt{"BinaryOnly"})
{ # --binary
writeReport("Binary", getReport("Binary"));
}
elsif($In::Opt{"SourceOnly"})
{ # --source
writeReport("Source", getReport("Source"));
}
}
sub getCssStyles($)
{
my $Level = $_[0];
my $CssStyles = readModule("Css", "Report.css");
if($Level eq "Join" or $In::Opt{"ExternCss"}) {
$CssStyles .= readModule("Css", "Tabs.css");
}
return $CssStyles;
}
sub getJsScript($)
{
my $Level = $_[0];
my $JScripts = readModule("Js", "Sections.js");
if($Level eq "Join" or $In::Opt{"ExternJs"}) {
$JScripts .= readModule("Js", "Tabs.js");
}
return $JScripts;
}
sub getReport($)
{
my $Level = $_[0];
my $CssStyles = getCssStyles($Level);
my $JScripts = getJsScript($Level);
if(defined $In::Opt{"ExternCss"}) {
writeFile($In::Opt{"ExternCss"}, $CssStyles);
}
if(defined $In::Opt{"ExternJs"}) {
writeFile($In::Opt{"ExternJs"}, $JScripts);
}
if($Level eq "Join")
{
my $Title = $In::Opt{"TargetTitle"}.": ".$In::Desc{1}{"Version"}." to ".$In::Desc{2}{"Version"}." compatibility report";
my $Keywords = $In::Opt{"TargetTitle"}.", compatibility";
my $Description = "Compatibility report for the ".$In::Opt{"TargetTitle"}." library between ".$In::Desc{1}{"Version"}." and ".$In::Desc{2}{"Version"}." versions";
my ($BSummary, $BMetaData, $BAnyChanged) = getSummary("Binary");
my ($SSummary, $SMetaData, $SAnyChanged) = getSummary("Source");
my $Report = "\n\n".composeHTML_Head($Level, $Title, $Keywords, $Description, $CssStyles, $JScripts, ($BAnyChanged or $SAnyChanged))."";
$Report .= getReportHeader("Join");
$Report .= "
\n";
$Report .= "\n$BSummary\n".getReportAdded("Binary").getReportRemoved("Binary").getReportProblems("High", "Binary").getReportProblems("Medium", "Binary").getReportProblems("Low", "Binary").getReportProblems("Safe", "Binary").getSourceInfo()."
";
$Report .= "\n$SSummary\n".getReportAdded("Source").getReportRemoved("Source").getReportProblems("High", "Source").getReportProblems("Medium", "Source").getReportProblems("Low", "Source").getReportProblems("Safe", "Source").getSourceInfo()."
";
$Report .= getReportFooter();
$Report .= "\n