japitools-0.9.7/0000755000175000017500000000000010526243261013666 5ustar sballardsballardjapitools-0.9.7/bin/0000755000175000017500000000000010526243211014431 5ustar sballardsballardjapitools-0.9.7/bin/.run.bat0000644000175000017500000000344610412547363016023 0ustar sballardsballard@echo off rem ------------------------------------------------------------------------- rem Copied from JBoss Bootstrap Script for Win32 rem ------------------------------------------------------------------------- @if not "%ECHO%" == "" echo %ECHO% @if "%OS%" == "Windows_NT" setlocal set DIRNAME=.\ if "%OS%" == "Windows_NT" set DIRNAME=%~dp0% rem set PROGNAME=run.bat rem if "%OS%" == "Windows_NT" set PROGNAME=%~nx0% rem Read all command line arguments set ARGS= :loop if [%1] == [] goto endloop set ARGS=%ARGS% %1 shift goto loop :endloop set JAPI_HOME=%DIRNAME%\.. if not "%JAVA_HOME%" == "" goto ADD_TOOLS set JAVA=java echo JAVA_HOME is not set. Unexpected results may occur. echo Set JAVA_HOME to the directory of your local JVM to avoid this message. goto SKIP_TOOLS :ADD_TOOLS set JAVA=%JAVA_HOME%\bin\java if exist "%JAVA_HOME%\bin\java.exe" goto SKIP_TOOLS echo Could not locate %JAVA_HOME%\bin\java. Unexpected results may occur. echo Make sure that JAVA_HOME points to a compatible JVM installation. :SKIP_TOOLS rem set JAPI_CLASSPATH=%JAPI_CLASSPATH%;%RUNJAR%;%JAPI_HOME%\share\java\java-getopt-1.0.9.jar;%JAPI_HOME%\share\java\jode-1.1.1.jar;%JAPI_HOME%\share\java\JSX1.0.5.3.jar rem Setup program sepecific properties rem set JAVA_OPTS=%JAVA_OPTS% -Dprogram.name=%PROGNAME% rem echo ========================================================================= rem echo. rem echo JAPI_HOME: %JAPI_HOME% rem echo. rem echo JAVA: %JAVA% rem echo. rem echo JAVA_OPTS: %JAVA_OPTS% rem echo. rem echo CLASSPATH: %JAPI_CLASSPATH% rem echo. rem echo ========================================================================= rem echo. %JAVA% %JAVA_OPTS% -classpath "%CLASSPATH%;%JAPI_CLASSPATH%" "%PROGRAM_CLASS%" %ARGS% :END rem if "%NOPAUSE%" == "" pause :END_NO_PAUSE japitools-0.9.7/bin/.run.sh0000644000175000017500000000373507561562470015677 0ustar sballardsballard#!/bin/sh ### ====================================================================== ### ## ## ## Copied from JBoss Bootstrap Script ## ## ## ### ====================================================================== ### DIRNAME=`dirname $0` #PROGNAME=`basename $0` GREP="grep" # # Helper to complain. # warn() { echo "${PROGNAME}: $*" } # # Helper to puke. # die() { warn $* exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false; darwin=false; case "`uname`" in CYGWIN*) cygwin=true ;; Darwin*) darwin=true ;; esac # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$JAPI_HOME" ] && JAPI_HOME=`cygpath --unix "$JAPI_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` fi # Setup JAPI_HOME if [ "x$JAPI_HOME" = "x" ]; then # get the full path (without any relative bits) JAPI_HOME=`cd $DIRNAME/..; pwd` fi export JAPI_HOME # Setup the JVM if [ "x$JAVA_HOME" != "x" ]; then JAVA="$JAVA_HOME/bin/java" else JAVA="java" fi # For Cygwin, switch paths to Windows format before running java if $cygwin; then JAPI_HOME=`cygpath --path --windows "$JAPI_HOME"` JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` JAPI_CLASSPATH=`cygpath --path --windows "$JAPI_CLASSPATH"` fi # Display our environment #echo "=======================================================================" #echo "" #echo " JAPI_HOME: $JAPI_HOME" #echo "" #echo " JAVA: $JAVA" #echo "" #echo " JAVA_OPTS: $JAVA_OPTS" #echo "" #echo " CLASSPATH: $JAPI_CLASSPATH" #echo "" #echo "=======================================================================" #echo "" # Execute the JVM exec $JAVA $JAVA_OPTS -classpath "$CLASSPATH:$JAPI_CLASSPATH" "$PROGRAM_CLASS" "$@" japitools-0.9.7/bin/japicompat0000644000175000017500000011622610524167047016525 0ustar sballardsballard#!/usr/bin/perl -w ############################################################################### # japicompat - Test Java APIs for binary backwards compatibility. # Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ############################################################################### ## GLOBAL VARIABLES ## # Some global variables used for displaying stuff. $japiver = "0.9.7"; $buggyver = "0.9.5"; @static = ('instance', 'static'); @ofhs = (); @summarypkgs = (); @allerrors = (); $totalerrors = 0; $ignorenotes = ""; $javatypes = {Z=>'boolean', B=>'byte', C=>'char', D=>'double', F=>'float', I=>'int', J=>'long', S=>'short', V=>'void'}; # Requirements use IO::Handle; use IO::File; use IO::Pipe; use Getopt::Std; # Function prototypes sub open_japi($); sub read_japi_item($$); sub close_japi($); sub output_error($$$$$$$;$); sub compare_japis($$); sub compare_japi_item($$$$); sub merge_results($); sub print_summary(); sub sig2type($); sub sig2typelist($); sub getgenparams($); sub splitgenstr($); sub gentypestr($); sub reallychomp($); ## MAIN LOOP ## # Parse cmdline and give a usage message. getopts("svqhtjwpo:i:", \%opts); my $sun_only = 1 if $opts{"s"}; my $svuid_errors = 1 if $opts{"v"}; my $buggywarn = 1 unless $opts{"w"}; my $skip_missing_pkgs = 1 if $opts{"p"}; my $dot = $opts{"q"} ? "" : "."; my $outprog = $opts{"h"} ? "japiohtml" : $opts{"j"} ? undef : "japiotext"; my $ignorefile = $opts{"i"}; my ($origfile, $newfile) = @ARGV; if (!defined $newfile) { print STDERR "Usage: japicompat [-svqhtjwp] [-o outfile] [-i ignorefiles,commaseparated] \n"; exit 1; } # Read in the old and new APIs. $orig = open_japi($origfile); $new = open_japi($newfile); # Loop through the two files and compare them. my $counts = compare_japis($orig, $new); # Close out the files now we're done with them. close_japi($orig); close_japi($new); # Merge the results into a single stream of errors, and print them to stdout. merge_results($counts); # Print a summary of what was found. print_summary(); ## SUBROUTINES ## # Compare two japi entries pseudo-alphabetically. sub japicmp($$) { my ($ia, $ib) = @_; return -1 unless defined $ib; $ia->{rawitem} cmp $ib->{rawitem}; } sub open_japi($) { my ($filename) = @_; my $japi; $japi->{name} = $filename; my $fh; if ($filename =~ /\.gz$/) { $fh = new IO::Pipe()->reader("gzip", "-dc", $filename); die "Could not pipe from gzip: $!" unless $fh; } else { $fh = new IO::File("<$filename"); die "Could not open $filename: $!" unless $fh; } print STDERR "Loading $filename" if $dot; my $japiline = $fh->getline; die <{fh} = $fh; foreach my $infitem (split / /, $info) { my ($infname, $infvalue) = split /=/, $infitem, 2; $japi->{$infname} = $infvalue; } return $japi; } sub read_japi_item($$) { my ($japi, $is_lhs) = @_; my $fh = $japi->{fh}; my $line = $fh->getline; # Ignore "-" or "--" annotated methods on the LHS if ($is_lhs) { while ($line && $line =~ /^[^ ]+\)-{1,2}/) { $line = $fh->getline; } } return undef unless $line; reallychomp $line; # Parse and interpret the entry. my ($item, $flags, $type) = split / /, $line, 3; # If the item was "--" annotated, store that fact, it affects processing later my $duplicate = 0; $duplicate = 1 if $item =~ /\)--$/; my $bridge = 0; $bridge = 1 if $item =~ /\)-$/ || $duplicate; my $nobridge = 0; $nobridge = 1 if $item =~ /\)\+$/; # Strip off the "+" or "-" annotation. $item =~ s/\)[+-]{1,2}$/\)/; my $rawitem = $item; # Check and trim the leading plusses on java.lang.Object and java.lang. die "\nMissing required leading plusses on $item" if $item =~ /^java\.lang[.,]/ || $item =~ /^\+java\.lang,Object!/; $item =~ s/^\+\+java\.lang,Object!/java.lang,Object!/; $item =~ s/^\+java\.lang([.,])/java.lang$1/; die "\nIncorrect leading plusses on $item" if $item =~ /^\+/; my ($fqcn, $member) = split /!/, $item, 2; my ($pkg, $class) = split /,/, $fqcn, 2; my $isa; if ($member eq "") { $isa = $type =~ /^class/ ? "class" : $type =~ /^interface/ ? "interface" : $type =~ /^enum/ ? "enum" : $type =~ /^annotation/ ? "annotation" : die "\nUnknown kind of Type: $type"; } elsif ($member =~ /^\(.*\)$/) { $isa = "constructor"; } elsif ($member =~ /\(.*\)$/) { $isa = "method"; } elsif ($member =~ /^#(.*)$/) { $isa = "field"; $member = $1; } else { die <{$pkg}->{$class}->{$member}. my $mitem = {}; $mitem->{rawitem} = $rawitem; $mitem->{item} = $item; $mitem->{isa} = $isa; $mitem->{class} = $class; $mitem->{package} = $pkg; $mitem->{gmember} = $member; my ($public, $abstract, $static, $final, $deprecated, $stub) = split //, $flags; $mitem->{public} = ($public eq 'P' || 0); $mitem->{abstract} = ($abstract eq 'a' || 0); $mitem->{static} = ($static eq 's' || 0); $mitem->{final} = ($final eq 'f' || $final eq 'e' || 0); $mitem->{enumfield} = ($final eq 'e' || 0); $mitem->{deprecated} = ($deprecated eq 'd' ? 1 : $deprecated eq 'u' ? 0 : undef); $mitem->{stub} = ($stub eq 'S' || 0); $mitem->{duplicate} = $duplicate; $mitem->{bridge} = $bridge; $mitem->{nobridge} = $nobridge; # Store information about the containing class if ($member ne "") { $mitem->{clitem} = $japi->{clitem}; } else { my $cli = $japi->{clitem}; while ($cli) { if ($cli->{item} =~ /^\Q$item\E\$/) { $mitem->{clitem} = $cli; last; } $cli = $cli->{clitem}; } $japi->{clitem} = $mitem; } # Classes and interfaces have superclasses and implemented interfaces tacked # on to the "type" field. We store this information in the $japi hash also. if ($member eq "") { # Get the interfaces data, which is separated by '*'s from the classname. my @ifaces = split(/\*/, $type); $type = shift @ifaces; $mitem->{ifaces} = {}; foreach my $iface (@ifaces) { $mitem->{ifaces}->{$iface} = 1; my $rawiface = $iface; $rawiface = $1 if $iface =~ /^([^<>]+){rawifaces}->{$rawiface} = 1; } # Get the class's superclasses, which are separated by ':'s. my @supers = split(/:/, $type); $type = shift @supers; my $ct = 0; $mitem->{supers} = []; foreach my $super (@supers) { $mitem->{superset}->{$super} = 1; $mitem->{supers}->[$ct++] = $super; my $rawsuper = $super; $rawsuper = $1 if $super =~ /^([^<>]+){rawsupers}->{$rawsuper} = 1; } my $svuid; ($type, $svuid) = split(/#/, $type, 2); $mitem->{svuid} = $svuid if defined $svuid; my $genericparams; ($type, $genericparams) = ($1, $2) if $type =~ /^([^<>]+)<(.+)>$/; if (defined $genericparams) { $mitem->{gparamstr} = $genericparams; $mitem->{genericparams} = []; my $ct = 0; foreach my $gparam (splitgenstr($genericparams)) { $mitem->{genericparams}->[$ct++] = $gparam; } } # Methods and constructors have exceptions that can be thrown separated by # '*'s from the typename. Also in the case of annotations the method can # have a default value. These also need to get stored in the hash. } elsif ($member =~ /\(.*\)$/) { my ($val, $bits); ($type, $val) = split(/:/, $type, 2); my @excps = split(/\*/, $type); $type = shift @excps; foreach my $excp (@excps) { $mitem->{excps}->{$excp} = 1; } my $genericparams; ($genericparams, $type) = ($1, $2) if $type =~ /<(.*?)>([^>,;].*)?$/; if (defined $genericparams) { $mitem->{gparamstr} = $genericparams; $mitem->{genericparams} = []; my $ct = 0; foreach my $gparam (splitgenstr($genericparams)) { $mitem->{genericparams}->[$ct++] = $gparam; } } ($val, $bits) = ($1, $2) if ($type eq 'F'||$type eq 'D') && defined $val && $val =~ /^(.*)\/(.*)$/; $mitem->{defaultbits} = $bits if defined $bits; $mitem->{default} = $val if defined $val; # Fields can have their value separated by a : from the typename, if they # are constant. } else { my ($val, $bits); ($type, $val) = split(/:/, $type, 2); ($val, $bits) = ($1, $2) if ($type eq 'F'||$type eq 'D') && defined $val && $val =~ /^(.*)\/(.*)$/; $mitem->{constbits} = $bits if defined $bits; $mitem->{constant} = $val if defined $val; # Fields also get the declaring class separated by "=" in cases where it matters. # The rules for when it matters are defined in japi-spec-0.9.7.txt and in # Japize, but basically, it's when the field is nonfinal and either public or # static. my $decl; ($type, $decl) = split(/=/, $type, 2); $mitem->{decl} = $decl if defined $decl; } # Store what's left of the type after parsing off all of those parts. $mitem->{type} = $type; $mitem->{member} = sanitize_member($mitem, $member); # Ensure that this item is correctly ordered. $rawitem =~ s/\Q$mitem->{gmember}\E/$mitem->{member}/ if $mitem->{gmember}; $mitem->{rawitem} = $rawitem; die "\nIncorrect ordering of $japi->{name}:\n$japi->{last_item} >=\n$item" if defined($japi->{last_item}) && ($japi->{last_item} gt $rawitem || ($japi->{last_item} eq $rawitem && !$japi->{last_was_dup})); $japi->{last_item} = $rawitem; $japi->{last_was_dup} = $duplicate; return $mitem; } sub close_japi($) { my ($japi) = @_; my $fh = $japi->{fh}; close $fh; } sub inc($$$$;$) { my ($c, $pkg, $isa, $etype, $count) = (@_, 1); $c->{"$pkg/$etype"} += $count; $c->{"#$isa/$etype"} += $count; $c->{"/$etype"} += $count; } sub mootinc($$$$$;$) { my ($c, $prefix, $pkg, $isa, $etype, $count) = (@_, 1); $c->{"$prefix$pkg/$etype"} += $count; $c->{"$prefix#$isa/$etype"} += $count; $c->{"$prefix/$etype"} += $count; } sub output_error($$$$$$$;$) { my ($oitem, $nitem, $oclitem, $c, $etype, $was, $is, $count) = (@_, 1); my ($supct, $sups); if ($oitem->{isa} eq "package") { $supct = 0; $sups = ""; } else { $supct = scalar @{$oclitem->{supers}} + keys %{$oclitem->{ifaces}}; $supct++ unless $supct; $sups = join ";", @{$oclitem->{supers}}, keys %{$oclitem->{ifaces}}; } unless ($ofhs[$supct]) { $ofhs[$supct] = new_tmpfile IO::File(); } $was =~ s/~/~t/g; $was =~ s/\//~s/g; $is =~ s/~/~t/g; $is =~ s/\//~s/g; print {$ofhs[$supct]} "error $count $etype $oitem->{isa} $oitem->{item} $sups $was/$is\n"; # Note that since svuid checking happens last, svuid error count will only # be inc'd if the item isn't already BAD for some other reason. inc($c, $oitem->{package}, $oitem->{isa}, $etype, $count) unless $errhere; $errhere = 1; } sub pct($$$$) { my ($c, $pkg, $etype, $str) = @_; my $ct = $c->{"$pkg/$etype"}; return $str unless $ct; my $tot = $c->{"$pkg/total"}; $str .= ", " if $str; return $str . ((int (10000 * $ct / $tot)) / 100) . "% $etype"; } sub short_summary($$) { my ($c, $pkg) = @_; my $pkgn = $pkg || "Total"; if ($pkg =~ /^#(.)(.*)$/) { $pkgn = uc($1) . $2; $pkgn .= "e" if $pkgn =~ /[sx]$/; $pkgn .= "s"; } my $summ = pct($c, $pkg, "good", ""); $summ = pct($c, $pkg, "minor", $summ); $summ = pct($c, $pkg, "bad", $summ); $summ = pct($c, $pkg, "missing", $summ); $summ = pct($c, $pkg, "abs.add", $summ); print STDERR "\r$pkgn: $summ\n" if $dot; push @summarypkgs, $pkg; } sub dump_output($) { my ($c) = @_; # Open the appropriate thing for output. If we will be piping to japiohtml or # japiotext, we first redirect STDOUT to whatever was given as "-o". if ($outprog) { if ($opts{"o"}) { close STDOUT; open STDOUT, ">$opts{o}"; } my $ct = 0; my $prog = $0; $prog = readlink $prog while -l $prog && $ct++ < 5; $progdir = $1 if $prog =~ /^(.*)\/[^\/]+$/; if ($progdir) { $progdir .= "/"; } else { $progdir = $1 if $prog =~ /^(.*)\\[^\\]+$/; $progdir .= "\\" if $progdir; } open OUT, "|$^X \"$progdir$outprog\""; } else { if ($opts{"o"}) { open OUT, ">$opts{o}"; } else { open OUT, ">-"; } } my $origname = $origfile; my $newname = $newfile; my $origsname = $origname; $origsname =~ s/\.japi(?:\.gz)?$//; my $newsname = $newname; $newsname =~ s/\.japi(?:\.gz)?$//; $origname .= "\@$orig->{date}" if $orig->{date}; $newname .= "\@$new->{date}" if $new->{date}; print OUT "%\%japio 0.9.2 $origname $newname\n"; if ($ignorenotes) { print OUT $ignorenotes; print OUT "notify Since these differences are not counted as good OR bad, they may cause percentages not to add up to 100%.\n"; } if ($buggywarn) { if ($orig->{"origver"} && $orig->{"origver"} le $buggyver) { print OUT "notify Warning: $origsname API was read by a version of japitools that contained known bugs that cause inaccuracies in the output.\n"; } if ($new->{"origver"} && $new->{"origver"} le $buggyver) { print OUT "notify Warning: $newsname API was read by a version of japitools that contained known bugs that cause inaccuracies in the output.\n"; } } if ($orig->{"noserial"}) { my @serialexc = split /;/, $orig->{"noserial"}; foreach my $exc (@serialexc) { if ($exc =~ /,$/) { $exc =~ s/,//; print OUT "notify Serialization compatibility of classes in $exc and subpackages, and their subclasses, has not been checked.\n"; } else { $exc =~ s/,//; print OUT "notify Serialization compatibility of $exc and its subclasses has not been checked.\n"; } } if ($orig->{"serial"}) { my @serialinc = split /;/, $orig->{"serial"}; foreach my $inc (@serialinc) { if ($inc =~ /,$/) { $inc =~ s/,//; print OUT "notify Serialization compatibility of classes in $inc and subpackages HAS been checked unless they're subclasses of a class mentioned above.\n"; } else { $inc =~ s/,//; print OUT "notify Serialization compatibility of $inc HAS been checked unless it's a subclass of a class mentioned above.\n"; } } } } print OUT "categories =good "; print OUT "=mi_nor " if $svuid_errors; print OUT "bad missing"; print OUT " abs.add" unless $sun_only; print OUT "\n"; foreach my $pkg (@summarypkgs) { my $pkgc = $pkg || "#"; print OUT "summary $pkgc"; foreach my $item ("good", "minor", "bad", "missing", "abs.add") { my $itm = $item; $itm = "+abs.add" if $item eq "abs.add"; my $val = $c->{"$pkg/$item"}; my $smootval = $c->{"^$pkg/$item"} || 0; my $mootval = $c->{">$pkg/$item"} || 0; print OUT " $itm:$val^$smootval>$mootval" if $val; } print OUT "\n"; } foreach my $errline (@allerrors) { print OUT "$errline\n"; } print OUT "end japio\n"; close OUT; } # Loop through all the packages in the original API and process them. sub compare_japis($$) { my ($orig, $new) = @_; my $pkg = ""; my $class = ""; my $c = {}; # counts my $oitem = read_japi_item($orig, 1); my $nitem = read_japi_item($new, 0); my $class_has_ctors = 0; my ($oclitem, $nclitem); print STDERR "Comparing...\n" if $dot; while (defined $oitem) { my $isnewpkg = ($pkg ne $oitem->{package}); my $isnewclass = $isnewpkg || ($class ne $oitem->{class}); my $cmp; my $first = 1; while (($cmp = japicmp($oitem, $nitem)) > 0) { $errhere = 0; # Keep an eye out for members that are abstract that are also not in # the original. This check is not in the JLS, so only do it if sun_only # is false. # OPENQ: Maybe New annotation methods should be legal, esp. if there's a # default value? if (!$sun_only && !$first && $class_has_ctors && $nitem->{abstract} && $nitem->{package} eq $pkg && $nitem->{class} eq $class) { my $mtype = $nclitem->{isa} eq "interface" ? "interface" : "abstract"; output_error($nitem, $nitem, $oclitem, $c, "abs.add", "", "new $mtype method"); } $first = 0; $nitem = read_japi_item($new, 0); } if ($isnewpkg) { short_summary($c, $pkg) if $pkg; print STDERR $oitem->{package} if $dot; } print STDERR $dot if $isnewclass; $pkg = $oitem->{package}; $class = $oitem->{class}; $oclitem = $oitem if $isnewclass; $class_has_ctors = 0 if $isnewclass; $class_has_ctors = 1 if $oitem->{isa} eq "constructor" || $oitem->{isa} eq "interface"; unless ($skip_missing_pkgs && $isnewpkg && (!defined($nitem) || $nitem->{package} ne $oitem->{package})) { inc($c, $oitem->{package}, $oitem->{isa}, "total"); } $errhere = 0; if ($cmp) { my $ecount = 1; my $eitem = {%$oitem}; if ($isnewpkg && (!defined($nitem) || $nitem->{package} ne $oitem->{package})) { $eitem->{isa} = "package"; $eitem->{class} = ""; $eitem->{item} = "$pkg,!"; while (defined $oitem && $oitem->{package} eq $pkg) { $oitem = read_japi_item($orig, 1); if (!$skip_missing_pkgs && defined $oitem && $oitem->{package} eq $pkg) { inc($c, $pkg, $oitem->{isa}, "total"); $ecount++; } } } elsif ($isnewclass) { while (defined $oitem && $oitem->{package} eq $pkg && $oitem->{class} eq $class) { $oitem = read_japi_item($orig, 1); if (defined $oitem && $oitem->{package} eq $pkg && $oitem->{class} eq $class) { inc($c, $pkg, $oitem->{isa}, "total"); $ecount++; } } } else { $oitem = read_japi_item($orig, 1); } if ($skip_missing_pkgs && $eitem->{isa} eq "package") { $pkg = ""; } else { output_error($eitem, undef, $oclitem, $c, "missing", "", "missing", $ecount); } } else { $nclitem = $nitem if $isnewclass; # If the orig item is non-generic (sanitize of the method doesn't have # any effect) and the "new" item is marked as a # duplicate, and the erasure of the return types don't match, we # read on to the next item. while ($nitem->{duplicate} && $oitem->{gmember} eq $oitem->{member} && sanitize_typesig($nitem->{type}, $nitem) ne sanitize_typesig($oitem->{type}, $oitem)) { $nitem = read_japi_item($new, 0); } # Otherwise, we just do a straight one-to-one comparison of the items. inc($c, $pkg, $oitem->{isa}, "good") unless compare_japi_item($oitem, $nitem, $oclitem, $c); $oitem = read_japi_item($orig, 1); } } # New abstract members in a class might show up even at the end of the file, # after the last item in orig. my $first = 1; while (!$sun_only && defined $nitem && $class_has_ctors && $nitem->{package} eq $pkg && $nitem->{class} eq $class) { $errhere = 0; # Keep an eye out for members that are abstract that are also not in # the original. This check is not in the JLS, so only do it if sun_only # is false. if (!$first && $nitem->{abstract}) { my $mtype = $nclitem->{isa} eq "interface" ? "interface" : "abstract"; output_error($nitem, $nitem, $oclitem, $c, "abs.add", "", "new $mtype method"); } $first = 0; $nitem = read_japi_item($new, 0); } short_summary($c, $pkg) if $pkg; short_summary($c, ""); return $c; } sub compare_japi_item($$$$) { my ($oitem, $nitem, $oclitem, $c) = @_; my $isa = $oitem->{isa}; my $member = $oitem->{member}; if (!$oitem->{stub} && $nitem->{stub}) { output_error($oitem, $nitem, $oclitem, $c, "missing", "", "not implemented"); } # Check that the item hasn't gone from class to interface, annotation to # class etc. if ($oitem->{isa} ne $nitem->{isa}) { if ($oitem->{isa} eq "class" && $nitem->{isa} eq "enum") { # Do nothing, we're treating this as legal. } else { output_error($oitem, $nitem, $oclitem, $c, "bad", $oitem->{isa}, $nitem->{isa}); } } # Check that access to the item hasn't been reduced. if ($oitem->{public} && !$nitem->{public}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "public", "protected"); } # Check that the item hasn't changed from concrete to abstract. if (!$oitem->{abstract} && $nitem->{abstract}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "concrete", "abstract"); } # Check that the staticness of the item hasn't changed if ($oitem->{static} != $nitem->{static}) { output_error($oitem, $nitem, $oclitem, $c, "bad", $static[$oitem->{static}], $static[$nitem->{static}]); } # Check that the item hasn't gone from nonfinal to final, except # for static methods. if (!$oitem->{final} && $nitem->{final} && ($oitem->{isa} ne "method" || !$oitem->{static})) { output_error($oitem, $nitem, $oclitem, $c, "bad", "nonfinal", "final"); } # Check that the item hasn't changed from an enum field to a regular # field. if ($oitem->{enumfield} && !$nitem->{enumfield}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "enum field", "normal field"); } # Check that generic type parameters are the same. It's legal to add type # parameters to something that wasn't generic at all before, though. if ($oitem->{gparamstr}) { if (!$nitem->{gparamstr}) { output_error($oitem, $nitem, $oclitem, $c, "bad", gentypestr($oitem), "not generic"); } elsif ($nitem->{gparamstr} ne $oitem->{gparamstr}) { output_error($oitem, $nitem, $oclitem, $c, "bad", gentypestr($oitem), gentypestr($nitem)); } } # Check that the item's type has remained the same. if ($oitem->{type} ne $nitem->{type}) { # If the original type is entirely non-generic (sanitizing it makes no difference) and # the new type sanitizes to the same thing, that's legal and happens all over the place # when making a non-generic class generic. unless (sanitize_typesig($oitem->{type}, $oitem) eq $oitem->{type} && sanitize_typesig($nitem->{type}, $nitem) eq $oitem->{type}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "type @{[sig2type($oitem->{type})]}", "type @{[sig2type($nitem->{type})]}"); } # Do I win the award for "most consecutive close brackets"? } # Check that the generic parts of method signatures, etc, have remained the # same. If the original wasn't generic at all, though, it's legal for the # new one to become generic. if ($oitem->{gmember} ne $oitem->{member} && $oitem->{gmember} ne $nitem->{gmember}) { my $oparams = sig2typelist($1) if $oitem->{gmember} =~ /\(([^()]+)\)$/; my $nparams = sig2typelist($1) if $nitem->{gmember} =~ /\(([^()]+)\)$/; die "unexpectedly found gmember unequal to member" unless $oparams && $nparams; output_error($oitem, $nitem, $oclitem, $c, "bad", "parameters ($oparams)", "parameters ($nparams)"); } # For classes and interfaces, check that nothing has been removed from # the set of super-interfaces or superclasses. if ($member eq "") { foreach my $iface (keys %{$oitem->{ifaces}}) { my $rawiface = $iface; $rawiface = $1 if $iface =~ /^([^<>]+){ifaces}->{$iface} || ($iface eq $rawiface && $nitem->{rawifaces}->{$rawiface})) { if ($nitem->{ifaces}->{$rawiface}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "implements @{[sig2type($iface)]}", "implements raw $rawiface"); } else { output_error($oitem, $nitem, $oclitem, $c, "bad", "implements @{[sig2type($iface)]}", "doesn't implement @{[sig2type($iface)]}"); } } } my $super = $oitem->{supers}->[0]; if (defined($super)) { my $rawsuper = $super; $rawsuper = $1 if $super =~ /^([^<>]+){superset}->{$super} || ($super eq $rawsuper && $nitem->{rawsupers}->{$rawsuper})) { if ($nitem->{superset}->{$rawsuper}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "subclass of @{[sig2type($super)]}", "subclass of raw $rawsuper"); } else { output_error($oitem, $nitem, $oclitem, $c, "bad", "subclass of @{[sig2type($super)]}", "not a subclass of @{[sig2type($super)]}"); } } } # Also check the SerialVersionUID if that is turned on. Do this last, to # ensure that anything "bad" will be flagged before this "minor" error. if ($svuid_errors && defined $oitem->{svuid}) { if (!defined $nitem->{svuid}) { output_error($oitem, $nitem, $oclitem, $c, "minor", "SerialVersionUID=$oitem->{svuid}", "no SVUID"); } elsif ($nitem->{svuid} ne $oitem->{svuid}) { output_error($oitem, $nitem, $oclitem, $c, "minor", "SerialVersionUID=$oitem->{svuid}", "SerialVersionUID=$nitem->{svuid}"); } } # For methods and constructors, check that the set of thrown exceptions # is the same. The JLS does not specify this so only do it if not # sun_only. } elsif ($member =~ /\(.*\)/ && !$sun_only) { foreach my $excp (keys %{$oitem->{excps}}) { unless ($nitem->{excps}->{$excp}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "throws $excp", "doesn't throw $excp"); } } foreach my $excp (keys %{$nitem->{excps}}) { unless ($oitem->{excps}->{$excp}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "doesn't throw $excp", "throws $excp"); } } # Check that if the original was a "real" method, the new one is at least # a bridge. if ($nitem->{nobridge} && !$oitem->{nobridge}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "", "no bridge method"); } # Check the default value if applicable # OPENQ Assuming for now that it's legal to add a default where one wasn't # before. Not sure if this is true... if (defined $oitem->{default}) { my $odefault = $oitem->{default}; $odefault .= " (0x$oitem->{defaultbits})" if defined $oitem->{defaultbits}; if (!defined $nitem->{default}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "has default [$odefault]", "has no default"); } else { my $ndefault = $nitem->{default}; $ndefault .= " (0x$nitem->{defaultbits})" if defined $nitem->{defaultbits}; if (defined $nitem->{defaultbits} && defined $oitem->{defaultbits}) { if ($nitem->{defaultbits} ne $oitem->{defaultbits}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "has default [$odefault]", "has default [$ndefault]"); } } elsif ($nitem->{default} ne $oitem->{default}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "has default [$odefault]", "has default [$ndefault]"); } } } # For fields, check the constant value if there is one. } else { if (defined $oitem->{constant}) { my $oconstant = $oitem->{constant}; $oconstant .= " (0x$oitem->{constbits})" if defined $oitem->{constbits}; my $ocstr = (defined $oitem->{constbits}) ? "fp constant" : "constant"; if (!defined $nitem->{constant}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "$ocstr [$oconstant]", "not constant"); } else { my $nconstant = $nitem->{constant}; $nconstant .= " (0x$nitem->{constbits})" if defined $nitem->{constbits}; my $ncstr = (defined $nitem->{constbits}) ? "fp constant" : "constant"; if (defined $nitem->{constbits} && defined $oitem->{constbits}) { if ($nitem->{constbits} ne $oitem->{constbits}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "$ocstr [$oconstant]", "$ncstr [$nconstant]"); } } elsif ($nitem->{constant} ne $oitem->{constant}) { output_error($oitem, $nitem, $oclitem, $c, "bad", "$ocstr [$oconstant]", "$ncstr [$nconstant]"); } } } # When the declaring class is given, check that the fields are declared in the same # class. Note that we're relying on Japize to only tell us the declaring class in # the right situations. my $odecl = $oitem->{decl}; my $ndecl = $nitem->{decl}; if ($odecl && $ndecl && $odecl ne $ndecl) { output_error($oitem, $nitem, $oclitem, $c, "bad", "declared in $odecl", "declared in $ndecl"); } } # Check for deprecation problems - do this last, to ensure that anything "bad" # will be flagged before this "minor" error. if ($svuid_errors && defined $oitem->{deprecated} && defined $nitem->{deprecated}) { if ($oitem->{deprecated} && !$nitem->{deprecated}) { output_error($oitem, $nitem, $oclitem, $c, "minor", "deprecated", "not deprecated"); } } return $errhere; } sub load_ignore_file($$) { my ($ign, $ignorefiles) = @_; if ($ignorefiles) { foreach my $ignorefile (split /,/, $ignorefiles) { if ($ignorefile =~ /\.japi(?:\.gz)?$/) { my $ct = 0; my $prog = $0; $prog = readlink $prog while -l $prog && $ct++ < 5; $progdir = $1 if $prog =~ /^(.*)\/[^\/]+$/; if ($progdir) { $progdir .= "/"; } else { $progdir = $1 if $prog =~ /^(.*)\\[^\\]+$/; $progdir .= "\\" if $progdir; } my $opts = "-j"; $opts .= "v" if $svuid_errors; $opts .= "s" if $sun_only; open IGN, "$^X \"${progdir}japicompat\" $opts \"$origfile\" \"$ignorefile\"|"; } else { open IGN, "<$ignorefile" or die "Could not open ignore file $ignorefile"; } my $japioline = ; die "Ignore file $ignorefile does not look like a japio file" if $japioline !~ /^\%\%japio /; die "Ignore file $ignorefile is not japio version 0.9.2" if $japioline !~ /^\%\%japio 0.9.2 ([^ \@]+)(?:\@[^ ]+) ([^ \@]+)(?:\@[^ ]+)/; my ($ignore_orig, $ignore_new) = ($1, $2); $ignore_orig =~ s/\.japi(\.gz)?$//; $ignore_new =~ s/\.japi(\.gz)?$//; $ignorenotes .= "notify Differences due to incompatibility between $ignore_orig and $ignore_new have been ignored.\n"; my $origsname = $origfile; $origsname =~ s/\.japi(?:\.gz)?$//; $ignorenotes .= "notify Warning: this may not make sense, because normally differences between $origsname and something should be ignored instead.\n" unless $ignore_orig eq $origsname; while () { reallychomp $_; if (/^error /) { my ($error, $etype, $isa, $item, $sups, $rest) = split(/ /, $_, 6); my ($class, $member) = split(/!/, $item, 2); my ($pkg, $cls) = split(/,/, $class, 2); my $dotclass = "$pkg.$cls"; $rest = "fp constant [$1]/fp constant [$2]" if $rest =~ /^fp constant \[[^ ]* \((0x[0-9a-z]+)\)\]\/fp constant \[[^ ]* \((0x[0-9a-z]+)\)\]$/; $ign->{"$dotclass\!$member $rest"} = 1; } elsif (/^end japio$/) { last; } } close IGN; } } } sub merge_results($) { my ($c) = @_; my $errs = {}; my $ign_errs = {}; load_ignore_file($ign_errs, $ignorefile); $sc = {}; print STDERR "Merging results / eliminating duplicates...\n" if $dot; foreach my $fh (@ofhs) { if ($fh) { $fh->seek(0, 0); my $lastmember = ""; while (<$fh>) { reallychomp $_; my $line = $_; my ($error, $count, $etype, $isa, $item, $sups, $rest) = split(/ /, $_, 7); my ($class, $member) = split(/!/, $item, 2); my ($pkg, $cls) = split(/,/, $class, 2); my $dotclass = "$pkg.$cls"; my @sups = split(/;/, $sups); my $insup = 0; my $prest = $rest; $prest = "fp constant [$1]/fp constant [$2]" if $prest =~ /^fp constant \[[^ ]* \((0x[0-9a-z]+)\)\]\/fp constant \[[^ ]* \((0x[0-9a-z]+)\)\]$/; my $inign = ($ign_errs->{"$dotclass\!$member $prest"} ? 1 : 0); # Determine whether the same error appears in any superclass, and if so # do not report this error. That doesn't apply, though, to missing # classes, which should be reported regardless of whether their # superclass is missing too. It also doesn't apply to errors in # constructors, since they aren't inherited. unless ((($isa eq 'class' || $isa eq 'interface' || $isa eq 'enum' || $isa eq 'annotation') && $etype eq 'missing') || $isa eq 'constructor') { foreach my $sup (@sups) { $insup = 1 if $errs->{"$sup\!$member $rest"}; $inign = 1 if $ign_errs->{"$sup\!$member $rest"}; } } if ($inign) { mootinc($c, ">", $pkg, $isa, $etype, $count) unless $lastmember eq "$dotclass\!$member"; } elsif ($insup) { mootinc($c, "^", $pkg, $isa, $etype, $count) unless $lastmember eq "$dotclass\!$member"; } else { $errs->{"$dotclass\!$member $rest"} = 1; push @allerrors, "$error $etype $isa $item $sups $rest"; $totalerrors++; inc($sc, $pkg, $isa, $etype); } $lastmember = "$dotclass\!$member"; } $fh->close(); undef $fh; } } dump_output($c); print STDERR "Done.\n" if $dot; } sub ct($$$$) { my ($c, $pkg, $etype, $str) = @_; my $ct = $c->{"$pkg/$etype"}; return $str unless $ct; $str .= ", " if $str; return "$str$ct $etype"; } sub count_summary($$) { my ($c, $pkg) = @_; my $pkgn = $pkg || "Total"; if ($pkg =~ /^#(.)(.*)$/) { $pkgn = uc($1) . $2; $pkgn .= "e" if $pkgn =~ /[sx]$/; $pkgn .= "s"; } my $summ = ct($c, $pkg, "good", ""); $summ = ct($c, $pkg, "minor", $summ); $summ = ct($c, $pkg, "bad", $summ); $summ = ct($c, $pkg, "missing", $summ); $summ = ct($c, $pkg, "abs.add", $summ); $summ = "All good" unless $summ; print STDERR "\r$pkgn: $summ \n" if $dot; } # Print summary information. sub print_summary() { print STDERR "\n" if $dot; count_summary($sc, "#package"); count_summary($sc, "#class"); count_summary($sc, "#interface"); count_summary($sc, "#enum"); count_summary($sc, "#annotation"); count_summary($sc, "#field"); count_summary($sc, "#constructor"); count_summary($sc, "#method"); print STDERR "$totalerrors unique errors found.\n" if $dot; } sub getgenparams($) { my ($item) = @_; my $gps = []; unless ($item->{static}) { push @$gps, @{getgenparams($item->{clitem})} if $item->{clitem}; } push @$gps, @{$item->{genericparams}} if $item->{genericparams}; return $gps; } sub sanitize_typesig($$) { my ($str, $item) = @_; my $gparams = getgenparams($item); $str =~ s/^\./\[/; my $oldstr = ""; while ($oldstr ne $str) { while ($oldstr ne $str) { $oldstr = $str; $str =~ s/<[^<>]*>//g; } $str =~ s/\@([0-9]+)/$gparams->[$1]/g; } $str =~ s/&.*$//; #print "\n${lastsanitized}Sub: $str with gparams=@$gparams\n" if $str =~ /\@[0-9]/; return $str; } sub sanitize_member($) { my ($item) = @_; #$lastsanitized = "Sanitizing $item->{rawitem}\n"; if ($item->{gmember} =~ /\(([^\)]+)\)/) { my $params = join(',', map {sanitize_typesig($_, $item)} splitgenstr($1)); my $result = $item->{gmember}; $result =~ s/\([^\)]+\)/\($params\)/; return $result; } else { return $item->{gmember}; } } sub gentypestr($) { my ($item) = @_; my $result = ""; my $num = 0; unless ($item->{static}) { my $gps = getgenparams($item->{clitem}); $num = scalar(@$gps); } my $count = 0; foreach my $param (splitgenstr($item->{gparamstr})) { $result .= ", " if $result; my $bounds; if ($param eq "Ljava/lang/Object;") { $bounds = ""; } else { $bounds = " extends "; foreach my $bound (split /\&/, $param) { $bounds .= " & " unless $bounds eq " extends "; $bounds .= sig2type($bound); } } $num++; my $name = "T$num"; $name = "T" if $name eq "T1"; $result .= "$name$bounds"; $count++; } return $count > 1 ? "has generic type parameters <$result>" : "has generic type parameter <$result>"; } # Convert a type signature as used in a japi file to a displayable type. sub sig2type($) { my ($sig) = @_; return sig2type($1) . '[]' if $sig =~ /^\[(.*)$/; return sig2type($1) . "..." if $sig =~ /^\.(.*)$/; return "? super " . sig2type($1) if $sig =~ /^\}(.*)$/; return "?" if $sig eq "{Ljava/lang/Object;"; return "? extends " . sig2type($1) if $sig =~ /^\{(.*)$/; return "T" if $sig eq "\@0"; return "T" . ($1 + 1) if $sig =~ /^\@([0-9]+)$/; return $javatypes->{$sig} if $javatypes->{$sig}; my $gparams; $sig = $1 if $sig =~ /^L(.*);$/; ($sig, $gparams) = ($1, $2) if $sig =~ /^([^<>]+)<(.*)>$/; $sig =~ s-/-.-g; $sig =~ s/\$/./g; $sig = "$sig<" . sig2typelist($gparams) . ">" if defined($gparams); return $sig; } sub sig2typelist($) { my ($list) = @_; my @sigs = splitgenstr($list); # print "sig2typelist of $list gives @sigs\n"; # print "returning " .join(", ", map {sig2type($_)} @sigs) . "\n"; return join(", ", map {sig2type($_)} @sigs); } sub countchar($$) { my ($str, $char) = @_; $str =~ s/[^$char]//g; return length $str; } sub splitgenstr($) { my ($str) = @_; my @items = split(/,/, $str); my @result = (); my $class = ""; foreach my $item (@items) { $class .= "," if $class; $class .= $item; if (countchar($class, "<") == countchar($class, ">")) { push @result, $class; $class = ""; } } push @result, $class if $class; return @result; } # Be rather excessively forceful about chomping a line, to handle windows systems under # Cygwin perl where chomp still doesn't believe \r is a newline character. sub reallychomp($) { chomp $_[0]; chomp $_[0]; $_[0] = $1 if $_[0] =~ /^(.*)\r$/; }japitools-0.9.7/bin/japiextractpkgs0000755000175000017500000000017110517553175017576 0ustar sballardsballard#!/usr/bin/perl while (<>) { print "=$1,\n" if /package-frame.html" (?:TARGET|target)="packageFrame">([^<]+)<\/A>/; } japitools-0.9.7/bin/japilist0000644000175000017500000000535010316116735016205 0ustar sballardsballard#!/usr/bin/perl ############################################################################### # japilist - List the contents of japi files. # Copyright (C) 2000,2002,2003,2004 Stuart Ballard # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ############################################################################### use Getopt::Std; getopts("apcmM:n:", \%opts); $show_pkgs = 1 if $opts{a} || $opts{p}; $show_classes = 1 if $opts{a} || $opts{c}; $show_fields = 1 if $opts{a} || $opts{m} || $opts{M} =~ /f/; $show_methods = 1 if $opts{a} || $opts{m} || $opts{M} =~ /m/; $show_constr = 1 if $opts{a} || $opts{m} || $opts{M} =~ /c/; $show_pkgs = 1 unless $show_classes||$show_fields||$show_methods||$show_constr; $filter_name = $opts{n}; foreach (@ARGV) { if (/\.gz$/) { open(IN, "-|") || exec 'gzip', '-dc', $_; } else { open IN, $_; } my $hdr = ; chomp $hdr; my $ver = $1 if $hdr =~ /^\%\%japi ([^ ]*)(?: .*)?$/; $ver = "0.8 or earlier (or\nnot a japi file at all)" unless $ver; if ($ver ne "0.9.7") { print STDERR <) { if (/^\+{0,2}([^ ]+),([^ ]+)!([^ ]*) /) { my ($pkg, $class, $member) = ($1, $2, $3); if ($filter_name) { next unless $pkg eq $filter_name || "$pkg.$class" eq $filter_name; } if ($show_pkgs && $pkg ne $lastpkg) { $lastpkg = $pkg; print "P $pkg\n"; } if ($show_classes && "$pkg.$class" ne $lastclass) { $lastclass = "$pkg.$class"; print "C $pkg.$class\n"; } if ($show_fields && $member =~ /^#(.*)$/) { print "f $pkg.$class.$1\n"; } elsif ($show_constr && $member =~ /^(\(.*\))$/) { print "c $pkg.$class$1\n"; } elsif ($show_methods && $member =~ /^([^(]+\(.*\))$/) { print "m $pkg.$class.$1\n"; } } } close IN; undef $pkgs; } japitools-0.9.7/bin/japiohtml0000644000175000017500000003730510335652663016370 0ustar sballardsballard#!/usr/bin/perl -w ############################################################################### # japiohtml - Convert japicompat output to pretty html format. # Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ############################################################################### ## GLOBAL VARIABLES ## # Some global variables used for displaying stuff. $japiover = "0.9.2"; @categories = (); @packages = (); # Would be nice not to hardcode this - maybe later... @things = ("package", "class", "interface", "enum", "annotation", "field", "method", "constructor"); $javatypes = {Z=>'boolean', B=>'byte', C=>'char', D=>'double', F=>'float', I=>'int', J=>'long', S=>'short', V=>'void'}; # Requirements #use IO::Handle; sub sig2type($); sub readable_member($); sub readable_item($); sub htmlencode($); my $verline = <>; chomp $verline; ($filever, $origfile, $newfile) = ($1, $2, $3) if $verline =~ /^%\%japio ([^ ]+) ([^ ]+) ([^ ]+)(?: .*)?$/; unless (defined $filever) { print STDERR < Results of comparison between $orig and $new

Results of comparison between $orig and $new

Comparison run at $date

EOF print <$orig API scanned at $origdate

EOF print <$new API scanned at $newdate

EOF print <Summary EOF my $alt = 1; while (<>) { chomp; if (/^notify (.*)$/) { print "

$1

\n"; } elsif (/^categories (.*)$/) { print < Legend: All correct - EOF for (my $i = 100; $i >= 0; $i -= 10) { print <  EOF } print <  - None correct
EOF my @bits = split / /, $1; foreach my $bit (@bits) { if ($bit =~ /^(=?)(.)(.*)/) { my $cat = "$2$3"; my $capegory = uc($2).$3; my $letter = $2; my $ok = $1; $letter = $1 if $cat =~ /_(.)/; $cat =~ s/_//; $letters->{$cat} = $letter; $capegory =~ s/_//; push @categories, $cat; $okcats->{$cat} = 1 if $ok; $cat =~ s/\./-/g; print <$capegory EOF } } print < EOF } elsif (/^summary (.*)$/) { die "No categories line found before summary line" unless @categories; my @bits = split / /, $1; my ($extra, $total, $values, $etotal, $oktotal, $nonmoototal) = ({}, 0, {}, 0, 0, 0); my $pkg = shift @bits; foreach my $bit (@bits) { my ($plus, $key, $value, $smootval, $mootval) = ($1, $2, $3, $4, $5) if $bit =~ /^(\+?)([^:+]+):([0-9]+)\^([0-9]+)>([0-9]+)$/; $etotal += $value - $smootval - $mootval; $oktotal += $value - $smootval - $mootval if $okcats->{$key}; if ($plus) { $extra->{$key} = $value - $smootval - $mootval; } else { $total += $value - $smootval; $nonmoototal += $value - $smootval - $mootval; $values->{$key} = $value - $smootval - $mootval; $values->{MOOT} += $mootval; } } my $pkgn = $pkg; my $pkga; my $pkgl; if ($pkg eq "#") { print "\n"; $pkga = $pkgn = "Total"; } elsif ($pkg =~ /^#(.)(.*)$/) { $pkgn = uc($1) . $2; $pkgn .= "e" if $pkgn =~ /[sx]$/; $pkgn .= "s"; $pkga = $pkgn; } else { $pkgl = $pkg; $pkgl =~ s/\./_/g; $pkga = $pkgn; $pkga =~ s/\./. <\/span>/g; $pkga = "$pkga"; push @packages, $pkg; } my $pctclass = "none"; if ($etotal) { $pctclass = (int($oktotal * 10 / $etotal) * 10)."pct" if $oktotal; } else { $pctclass = "moot"; } print < EOF $alt = 3 - $alt; foreach my $key (@categories) { my $val; if ($values->{$key}) { $val = (int($values->{$key} * 10000 / $total)/100) . "%"; } elsif ($extra->{$key}) { $val = (int($extra->{$key} * 10000 / $total)/100) . "%"; } if ($val) { if ($pkg ne "#" && $key ne "good") { my $errl = $key; $errl =~ s/\./_/g; $val = "$val"; } } else { $val = " "; } my $class = $key; $class =~ s/\./-/g; print <$val EOF } use integer; # Now we need to integerize the percentages so we can emit a table whose # width (minus the "extra" bit) adds up to exactly 100 pixels. We also want # to ensure that *anything* nonzero gets at least a pixel. See the file # design/percent-rounding.txt for a justification of this algorithm. # Start off assuming nothing's less than 1%, and iterate until we find # that we're right. m is the number of adjustable (<1%) items, and t is # the total of the non-adjustable items. Lastm keeps track of what we # thought m was last time round. We loop until we do an entire pass without # m ending up different from lastm. Lastm starts off as -1 just so that the # m == lastm test fails first time around. my $t = $total; my $m = 0; my $lastm = -1; my $adjustable = {}; until ($m == $lastm) { $lastm = $m; # Loop over the items that haven't already been marked adjustable. For # each such item, determine whether it needs to be adjustable based on # the current values of m and t. If it does, mark it as adjustable and # update m and t accordingly. foreach my $item (keys %$values) { if ($values->{$item} > 0) { if (!$adjustable->{$item}) { if ($values->{$item} * (100-$m) < $t) { $t -= $values->{$item}; $m++; $adjustable->{$item} = 1; } } } } } # Having calculated the final values of m and t, and also knowing exactly # which items are adjustable, we can now calculate the adjusted totals. # Non-adjustable items are scaled up by a constant factor; adjustable # items are all set to exactly 1% of the scaled total. my $adjtotal = 100 * $t; my $adjvalue = {}; foreach my $item (keys %$values) { if ($values->{$item} > 0) { if ($adjustable->{$item}) { $adjvalue->{$item} = $t; } else { $adjvalue->{$item} = $values->{$item} * (100-$m); } } } # Calculate the percentage rounded *down* to the nearest integer, and also # calculate the magnitude of the difference between the integer percentage # and the actual percentage. This is still all done in integer math... # While we're at it, sum the percentages so we can see how close we got, # later. my $totalpct = 0; my $percent = {}; my $diff = {}; foreach my $item (keys %$values) { if ($values->{$item} > 0) { $percent->{$item} = ($adjvalue->{$item} * 100) / $adjtotal; $diff->{$item} = $adjvalue->{$item} * 100 - $percent->{$item} * $adjtotal; $totalpct += $percent->{$item}; } else { $diff->{$item} = 0; } } # Find the items with the largest differences, and adjust them upwards, # until 100% is reached. No need to reset the difference since we're looping # through the items and will never repeat: it's easy to show that the upper # bound on the number of upwards adjustments needed is smaller than the # number of items. foreach my $item (sort { $diff->{$b} <=> $diff->{$a} } keys %$values) { if ($values->{$item} > 0) { last if ($totalpct >= 100); $percent->{$item}++; $totalpct++; } } foreach my $item (keys %$extra) { if ($extra->{$item} > 0) { $percent->{$item} = ($extra->{$item} * 1000 + 5) / ($total * 10); $percent->{$item} = 1 unless $percent->{$item}; $totalpct += $percent->{$item}; } } print <
 $pkga:
EOF foreach my $item (@categories) { if (exists $percent->{$item}) { my $pct = $percent->{$item}; my $class = $item; $class =~ s/\./-/g; my $altc = uc($letters->{$item}); my $alt = $altc x ($percent->{$item} / 5); $alt = $altc unless $alt; print < $alt EOF } } print <
EOF } elsif (/^error (.*)$/) { my $line = $1; my ($etype, $isa, $item, $sups, $rest) = split / /, $line, 5; my ($pkg, $cmember) = split /,/, $item; push @{$errors->{"$pkg/$etype"}}, $line; $totals->{"$etype/$isa"}++; $totals->{"$etype/$isa/$pkg"}++; } elsif (/^end japio$/) { last; } else { die "Line not understood in japio file:\n$_"; } } print <

Errors

Total

EOF my $ct = 0; foreach my $cat (@categories) { next if $cat eq "good"; my $cap = uc($1).$2 if $cat =~ /^(.)(.*)$/; my $catn = $cat; $catn =~ s/\./-/g; my $vals = ""; foreach my $thing (@things) { my $tot = $totals->{"$cat/$thing"}; if ($tot) { my $th = $thing; if ($tot > 1) { $th .= "e" if $th =~ /[sx]$/; $th .= "s"; } $vals .= "," if $vals; $vals .= " $tot $th"; } } $vals = " None" unless $vals; print <$cap:$vals. EOF } print <

 

EOF foreach my $pkg (@packages) { my $pkgl = $pkg; $pkgl =~ s/\./_/g; my $catlinks = ""; my $anyerrors = 0; foreach my $cat (@categories) { my $catl = $cat; $catl =~ s/\./_/g; unless ($cat eq "good") { if ($errors->{"$pkg/$cat"}) { $anyerrors = 1; } else { $catlinks .= ""; } } } if ($anyerrors) { print <

$catlinks$pkg

EOF my $ct = 0; foreach my $cat (@categories) { next if $cat eq "good"; my $cap = uc($1).$2 if $cat =~ /^(.)(.*)$/; my $catn = $cat; $catn =~ s/\./-/g; my $catl = $cat; $catl =~ s/\./_/g; my $vals = ""; foreach my $thing (@things) { my $tot = $totals->{"$cat/$thing/$pkg"}; if ($tot) { my $th = $thing; if ($tot > 1) { $th .= "e" if $th =~ /[sx]$/; $th .= "s"; } $vals .= "," if $vals; $vals .= " $tot $th"; } } print <$cap:$vals. EOF } print < EOF } else { print <$catlinks EOF } foreach my $cat (@categories) { if ($errors->{"$pkg/$cat"}) { my $catl = $cat; $catl =~ s/\./_/g; my $catc = $cat; $catc =~ s/\./-/g; my $cap = uc($1).$2 if $cat =~ /^(.)(.*)$/; print <$cap
    EOF foreach my $line (sort @{$errors->{"$pkg/$cat"}}) { my ($etype, $isa, $item, $sups, $rest) = split / /, $line, 5; my ($was, $is) = split /\//, $rest; $was =~ s/~s/\//g; $was =~ s/~t/~/g; $is =~ s/~s/\//g; $is =~ s/~t/~/g; my $msg = $was ? "$was in $orig, but" : ""; my $ritem = readable_item($item); my $outline = "$isa " . htmlencode("$ritem: $msg $is in $new"); print <$outline EOF } print < EOF } } } print < EOF sub htmlencode($) { my ($val) = @_; $val =~ s/&/&/g; $val =~ s//>/g; return $val; } sub readable_item($) { my ($item) = @_; my ($fqclass, $member) = split /!/, $item, 2; my ($pkg, $class) = split /,/, $fqclass, 2; my $ritem = $pkg; if ($class) { $class =~ s/\$/./g; $ritem .= ".$class"; } if ($member) { $ritem .= "." unless $member =~ /^\(/; $ritem .= readable_member($member); } return $ritem; } # Convert all the type signatures in a method name... sub readable_member($) { my ($member) = @_; if ($member =~ /^(.*)\((.*)\)$/) { my ($name, $params) = ($1, $2); $params = sig2typelist($params); $member = "$name($params)"; } elsif ($member =~ /^#(.*)$/) { $member = $1; } $member; } # Convert a type signature as used in a japi file to a displayable type. sub sig2type($) { my ($sig) = @_; return sig2type($1) . '[]' if $sig =~ /^\[(.*)$/; return sig2type($1) . "..." if $sig =~ /^\.(.*)$/; return "? super " . sig2type($1) if $sig =~ /^\}(.*)$/; return "?" if $sig eq "{Ljava/lang/Object;"; return "? extends " . sig2type($1) if $sig =~ /^\{(.*)$/; return "T" if $sig eq "\@0"; return "T" . ($1 + 1) if $sig =~ /^\@([0-9]+)$/; return $javatypes->{$sig} if $javatypes->{$sig}; my $gparams; $sig = $1 if $sig =~ /^L(.*);$/; ($sig, $gparams) = ($1, $2) if $sig =~ /^([^<>]+)<(.*)>$/; $sig =~ s-/-.-g; $sig =~ s/\$/./g; $sig = "$sig<" . sig2typelist($gparams) . ">" if defined($gparams); return $sig; } sub sig2typelist($) { my ($list) = @_; my @sigs = splitgenstr($list); return join(", ", map {sig2type($_)} @sigs); } sub countchar($$) { my ($str, $char) = @_; $str =~ s/[^$char]//g; return length $str; } sub splitgenstr($) { my ($str) = @_; my @items = split(/,/, $str); my @result = (); my $class = ""; foreach my $item (@items) { $class .= "," if $class; $class .= $item; if (countchar($class, "<") == countchar($class, ">")) { push @result, $class; $class = ""; } } push @result, $class if $class; return @result; } japitools-0.9.7/bin/japiotext0000644000175000017500000001675510335652663016416 0ustar sballardsballard#!/usr/bin/perl -w ############################################################################### # japiotext - Convert japicompat output to readable plain text format. # Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ############################################################################### ## GLOBAL VARIABLES ## # Some global variables used for displaying stuff. $japiover = "0.9.2"; @categories = (); @packages = (); # Would be nice not to hardcode this - maybe later... @things = ("package", "class", "interface", "enum", "annotation", "field", "method", "constructor"); $javatypes = {Z=>'boolean', B=>'byte', C=>'char', D=>'double', F=>'float', I=>'int', J=>'long', S=>'short', V=>'void'}; sub readable_item($); sub sig2type($); sub readable_member($); my $verline = <>; chomp $verline; ($filever, $origfile, $newfile) = ($1, $2, $3) if $verline =~ /^%\%japio ([^ ]+) ([^ ]+) ([^ ]+)(?: .*)?$/; unless (defined $filever) { print STDERR <) { chomp; if (/^notify (.*)$/) { print "\n * $1\n"; } elsif (/^categories (.*)$/) { my @bits = split / /, $1; foreach my $bit (@bits) { if ($bit =~ /^=?(.*)$/) { my $cat = $1; $cat =~ s/_//g; push @categories, $cat; } } print "\nPackage Summary:\n"; } elsif (/^summary (.*)$/) { die "No categories line found before summary line" unless @categories; my @bits = split / /, $1; my ($extra, $total, $values, $etotal, $nonmoototal) = ({}, 0, {}, 0, 0); my $pkg = shift @bits; foreach my $bit (@bits) { my ($plus, $key, $value, $smootval, $mootval) = ($1, $2, $3, $4, $5) if $bit =~ /^(\+?)([^:+]+):([0-9]+)\^([0-9]+)>([0-9]+)$/; $etotal += $value - $smootval - $mootval; if ($plus) { $extra->{$key} = $value - $smootval - $mootval; } else { $total += $value - $smootval; $nonmoototal += $value - $smootval - $mootval; $values->{$key} = $value - $smootval - $mootval; $values->{MOOT} += $mootval; } } my $pkgn = $pkg; if ($pkg eq "#") { print "\n"; $pkgn = "Total"; } elsif ($pkg =~ /^#(.)(.*)$/) { $pkgn = uc($1) . $2; $pkgn .= "e" if $pkgn =~ /[sx]$/; $pkgn .= "s"; } else { push @packages, $pkg; } print "$pkgn: "; my $summ = ""; foreach my $key (@categories) { my $val; if ($values->{$key}) { $val = (int($values->{$key} * 10000 / $total)/100) . "%"; } elsif ($extra->{$key}) { $val = (int($extra->{$key} * 10000 / $total)/100) . "%"; } if ($val) { $summ .= ", " if $summ; $summ .= "$val $key"; } } $summ = "N/A" unless $summ; print "$summ\n"; } elsif (/^error (.*)$/) { my $line = $1; my ($etype, $isa, $item, $sups, $rest) = split / /, $line, 5; my ($pkg, $cmember) = split /,/, $item; push @{$errors->{"$pkg/$etype"}}, $line; $totals->{"$etype/$isa"}++; } elsif (/^end japio$/) { last; } else { die "Line not understood in japio file:\n$_"; } } print "\nError Summary:\n"; foreach my $thing (@things) { my $th = uc($1).$2 if $thing =~ /^(.)(.*)$/; $th .= "e" if $th =~ /[sx]$/; $th .= "s"; my $vals = ""; foreach my $cat (@categories) { my $tot = $totals->{"$cat/$thing"}; if ($tot) { $vals .= ", " if $vals; $vals .= "$tot $cat"; } } print "$th: $vals.\n" if $vals; } print "\nErrors:\n"; foreach my $pkg (@packages) { my $anyerrors = 0; foreach my $cat (@categories) { $anyerrors = 1 if $errors->{"$pkg/$cat"}; } print "\n$pkg:\n" if $anyerrors; foreach my $cat (@categories) { if ($errors->{"$pkg/$cat"}) { my $cap = uc($1).$2 if $cat =~ /^(.)(.*)$/; print "$cap\n"; foreach my $line (sort @{$errors->{"$pkg/$cat"}}) { my ($etype, $isa, $item, $sups, $rest) = split / /, $line, 5; my ($was, $is) = split /\//, $rest; $was =~ s/~s/\//g; $was =~ s/~t/~/g; $is =~ s/~s/\//g; $is =~ s/~t/~/g; my $msg = $was ? " $was in $orig, but" : ""; my $ritem = readable_item($item); print "$isa $ritem:$msg $is in $new\n"; } } } } sub readable_item($) { my ($item) = @_; my ($fqclass, $member) = split /!/, $item, 2; my ($pkg, $class) = split /,/, $fqclass, 2; my $ritem = $pkg; if ($class) { $class =~ s/\$/./g; $ritem .= ".$class"; } if ($member) { $ritem .= "." unless $member =~ /^\(/; $ritem .= readable_member($member); } return $ritem; } # Convert all the type signatures in a method name... sub readable_member($) { my ($member) = @_; if ($member =~ /^(.*)\((.*)\)$/) { my ($name, $params) = ($1, $2); $params = sig2typelist($params); $member = "$name($params)"; } elsif ($member =~ /^#(.*)$/) { $member = $1; } $member; } # Convert a type signature as used in a japi file to a displayable type. sub sig2type($) { my ($sig) = @_; return sig2type($1) . '[]' if $sig =~ /^\[(.*)$/; return sig2type($1) . "..." if $sig =~ /^\.(.*)$/; return "? super " . sig2type($1) if $sig =~ /^\}(.*)$/; return "?" if $sig eq "{Ljava/lang/Object;"; return "? extends " . sig2type($1) if $sig =~ /^\{(.*)$/; return "T" if $sig eq "\@0"; return "T" . ($1 + 1) if $sig =~ /^\@([0-9]+)$/; return $javatypes->{$sig} if $javatypes->{$sig}; my $gparams; $sig = $1 if $sig =~ /^L(.*);$/; ($sig, $gparams) = ($1, $2) if $sig =~ /^([^<>]+)<(.*)>$/; $sig =~ s-/-.-g; $sig =~ s/\$/./g; $sig = "$sig<" . sig2typelist($gparams) . ">" if defined($gparams); return $sig; } sub sig2typelist($) { my ($list) = @_; my @sigs = splitgenstr($list); return join(", ", map {sig2type($_)} @sigs); } sub countchar($$) { my ($str, $char) = @_; $str =~ s/[^$char]//g; return length $str; } sub splitgenstr($) { my ($str) = @_; my @items = split(/,/, $str); my @result = (); my $class = ""; foreach my $item (@items) { $class .= "," if $class; $class .= $item; if (countchar($class, "<") == countchar($class, ">")) { push @result, $class; $class = ""; } } push @result, $class if $class; return @result; } japitools-0.9.7/bin/japize0000644000175000017500000000640110526243036015644 0ustar sballardsballard#!/usr/bin/perl ############################################################################### # japize - Wrapper script to launch japize. # Copyright (C) 2000,2002,2003,2004 Stuart Ballard # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ############################################################################### # Wrapper script for Japize. Checks argument syntax to avoid wasting time # starting a JVM just to print an error message, then launches the Japize class. $progname = $1 if $0 =~ /^.*\/([^\/]+)$/; my $ct = 0; my $prog = $0; $prog = readlink $prog while -l $prog && $ct++ < 5; $progdir = $1 if $prog =~ /^(.*)\/[^\/]+$/; sub printusage() { print STDERR <] [lint ] apis | ... +|-|= ... At least one +pkg is required. 'name' will have .japi and/or .gz appended if appropriate. The word 'apis' can be replaced by 'explicitly', 'byname', 'packages' or 'classes'. These values indicate whether something of the form a.b.C should be treated as a class or a package. Use 'a.b,C' or 'a.b.c,' to be explicit. EOF exit(1); } sub illegalpkgpath() { $errarg =~ s/^[+=-]//; print STDERR <1,apis=>1,byname=>1,packages=>1,classes=>1}->{@args[0]}; shift @args; my $plusses = 0; my $paths = 0; foreach my $arg (@args) { if ($arg =~ /^[+=-]/) { $errarg = $arg; illegalpkgpath unless $arg =~ /^[+=-](?:(?:[a-zA-Z0-9_]+\.)*[a-zA-Z0-9_\$]+)?(?:,[a-zA-Z0-9_\$]*)?(?::serial)?$/; $plusses++ unless $arg =~ /^-/; } else { $paths++; } } printusage unless $plusses && $paths; $errarg = $fn; zipfnerror if !$zip && $fn =~ /\.gz$/; } $ENV{JAVA} = 'java' unless defined $ENV{JAVA}; $ENV{CLASSPATH} = "$progdir/../share/java/japitools.jar:$ENV{CLASSPATH}"; print STDERR "...\n"; exec($ENV{JAVA}, 'net.wuffies.japi.Japize', @ARGV); japitools-0.9.7/bin/japize.bat0000644000175000017500000000272607556010756016431 0ustar sballardsballard@echo off rem ------------------------------------------------------------------------- rem Copied from JBoss Bootstrap Script for Win32 rem ------------------------------------------------------------------------- @if not "%ECHO%" == "" echo %ECHO% @if "%OS%" == "Windows_NT" setlocal set DIRNAME=.\ if "%OS%" == "Windows_NT" set DIRNAME=%~dp0% set PROGNAME=japiserialize.bat if "%OS%" == "Windows_NT" set PROGNAME=%~nx0% rem Read all command line arguments set ARGS= :loop if [%1] == [] goto endloop set ARGS=%ARGS% %1 shift goto loop :endloop set JAPI_HOME=%DIRNAME%\.. rem Find run.bat, or we can't continue set RUNBAT=%JAPI_HOME%\bin\.run.bat if exist "%RUNBAT%" goto FOUND_RUN_BAT echo Could not locate %RUNBAT%. Please check that you are in the echo bin directory when running this script. goto END :FOUND_RUN_BAT rem Find japitools.jar, or we can't continue set RUNJAR=%JAPI_HOME%\share\java\japitools.jar if exist "%RUNJAR%" goto FOUND_RUN_JAR echo Could not locate %RUNJAR%. Please check that you are in the echo bin directory when running this script. goto END :FOUND_RUN_JAR set JAPI_CLASSPATH=%JAPI_CLASSPATH%;%RUNJAR%;%JAPI_HOME%\share\java\java-getopt-1.0.9.jar;%JAPI_HOME%\share\java\jode-1.1.1.jar;%JAPI_HOME%\share\java\JSX1.0.5.3.jar rem Setup program sepecific properties set JAVA_OPTS=%JAVA_OPTS% -Dprogram.name=%PROGNAME% set PROGRAM_CLASS=net.wuffies.japi.Japize %RUNBAT% %ARGS% :END rem if "%NOPAUSE%" == "" pause :END_NO_PAUSE japitools-0.9.7/bin/japizejdks0000755000175000017500000000355410311130500016510 0ustar sballardsballard#!/bin/bash # This is very obviously tuned to run on my system, but I'm including it in # the japitools tarball because it's useful for showing exactly how to generate # japi files for each JDK version. Feel free to modify it to take the paths etc # as arguments so people other than me can run it directly... cd /home/sballard/public_html/japi/htmlout JAPIDIR=/home/sballard/japi JDK15DIR=/usr/lib/j2sdk1.5-sun JDK14DIR=/usr/local/j2sdk1.4.1 JDK13DIR=/usr/lib/j2se/1.3 JDK12DIR=/usr/local/jdk1.2.2 JDK11DIR=/usr/lib/jdk/1.1 JDK10DIR=/usr/local/jdk1.0 JDK15ARGS="+java +javax +org -java.awt.dnd.peer -java.awt.peer -org.apache -org.w3c.dom.css -org.w3c.dom.events -org.w3c.dom.html -org.w3c.dom.stylesheets -org.w3c.dom.traversal -org.w3c.dom.views -java.text.resources -org.omg.stub.javax -org.omg.dom.ranges" JDK14ARGS="+java +javax +org -java.awt.dnd.peer -java.awt.peer -org.apache -org.w3c.dom.css -org.w3c.dom.events -org.w3c.dom.html -org.w3c.dom.stylesheets -org.w3c.dom.traversal -org.w3c.dom.views -java.text.resources" JDK13ARGS="$JDK14ARGS" JDK12ARGS="$JDK13ARGS" JDK11ARGS="+java -java.awt.peer -java.text.resources" JDK10ARGS="+java -java.lang,UNIXProcess" japize=$JAPIDIR/bin/japize for n in jdk10 jdk11 jdk12 jdk13 jdk14 jdk15; do mv $n.japi.gz $n.bak.japi.gz; done echo -n "Japizing jdk15" $japize as jdk15 packages $JDK15DIR/jre/lib/{rt,jce,jsse}.jar $JDK15ARGS echo -n "Japizing jdk14" $japize as jdk14 packages $JDK14DIR/jre/lib/{rt,jce,jsse}.jar $JDK14ARGS echo -n "Japizing jdk13" $japize as jdk13 packages $JDK13DIR/jre/lib/rt.jar $JDK13ARGS echo -n "Japizing jdk12" $japize as jdk12 packages $JDK12DIR/jre/lib/rt.jar $JDK12ARGS echo -n "Japizing jdk11" $japize as jdk11 packages $JDK11DIR/lib/classes.zip $JDK11ARGS echo -n "Japizing jdk10" $japize as jdk10 packages $JDK10DIR/lib/classes.zip $JDK10ARGS echo "Finished japizing." tar cvf ../jdkjapis.tar jdk1?.japi.gz japitools-0.9.7/src/0000755000175000017500000000000010526243211014450 5ustar sballardsballardjapitools-0.9.7/src/net/0000755000175000017500000000000010526243211015236 5ustar sballardsballardjapitools-0.9.7/src/net/wuffies/0000755000175000017500000000000010526243211016706 5ustar sballardsballardjapitools-0.9.7/src/net/wuffies/japi/0000755000175000017500000000000010526243211017631 5ustar sballardsballardjapitools-0.9.7/src/net/wuffies/japi/ArrayType.java0000644000175000017500000000413110315337652022424 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public class ArrayType extends RefType { private Type elementType; public ArrayType(Type elementType) { if (elementType == null) { throw new NullPointerException("Cannot have an array with an unbound element type!"); } this.elementType = elementType; } public Type getElementType() { return elementType; } public String getTypeSig(GenericWrapper wrapper) { return "[" + getElementType().getTypeSig(wrapper); } public String getNonGenericTypeSig() { return "[" + getElementType().getNonGenericTypeSig(); } public Type getNonGenericType() { return new ArrayType(elementType.getNonGenericType()); } public String toStringImpl() { return "Array:" + getElementType(); } public Type bind(ClassType t) { debugStart("Bind", "to " + t); try { return new ArrayType(getElementType().bindWithFallback(t)); } finally { debugEnd(); } } public void resolveTypeParameters() { if (elementType instanceof RefType) { elementType = resolveTypeParameter((RefType)elementType); } } } japitools-0.9.7/src/net/wuffies/japi/BoundCall.java0000644000175000017500000001375110404333242022345 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005,2006 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; class BoundCall implements CallWrapper, Comparable { private CallWrapper base; private Type returnType; private Type[] parameterTypes; private NonArrayRefType[] exceptionTypes; private TypeParam[] typeParams; private GenericWrapper containingWrapper; private boolean visible14 = true; private boolean visible15 = true; private BoundCall(CallWrapper base, GenericWrapper containingWrapper, Type returnType, Type[] parameterTypes, NonArrayRefType[] exceptionTypes, TypeParam[] typeParams, boolean visible14, boolean visible15) { this.base = base; this.containingWrapper = containingWrapper; this.returnType = returnType; this.parameterTypes = parameterTypes; this.exceptionTypes = exceptionTypes; this.typeParams = typeParams; if (!(visible14 || visible15)) throw new RuntimeException("Can't be excluded from *everywhere*!"); this.visible14 = visible14; this.visible15 = visible15; } public BoundCall(CallWrapper base, GenericWrapper containingWrapper) { this(base, containingWrapper, base.getReturnType(), base.getParameterTypes(), base.getExceptionTypes(), base.getTypeParams(), true, true); } public Type getReturnType() { return returnType; } public Type[] getParameterTypes() { return parameterTypes; } public NonArrayRefType[] getExceptionTypes() { return exceptionTypes; } public TypeParam[] getTypeParams() { return typeParams; } public GenericWrapper getContainingWrapper() { return containingWrapper; } public boolean isVisible14() { return visible14; } public boolean isVisible15() { return visible15; } public BoundCall bind(ClassType t) { if (!visible15 || t == null) return this; Type[] newParameterTypes = new Type[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { newParameterTypes[i] = parameterTypes[i].bindWithFallback(t); } NonArrayRefType[] newExceptionTypes = new NonArrayRefType[exceptionTypes.length]; for (int i = 0; i < exceptionTypes.length; i++) { newExceptionTypes[i] = (NonArrayRefType) exceptionTypes[i].bindWithFallback(t); } TypeParam[] newTypeParams = null; if (typeParams != null) { newTypeParams = new TypeParam[typeParams.length]; for (int i = 0; i < typeParams.length; i++) { newTypeParams[i] = (TypeParam) typeParams[i].bindWithFallback(t); } } BoundCall result = new BoundCall(base, containingWrapper, returnType.bindWithFallback(t), newParameterTypes, newExceptionTypes, newTypeParams, visible14, visible15); if (!getNonGenericSig().equals(result.getNonGenericSig())) { // shouldn't happen - !visible15 was handled above... if (!result.visible15) throw new RuntimeException("Can't be excluded from *everywhere*!"); result.visible14 = false; } return result; } public BoundCall bind14() { if (!visible14) return null; Type[] newParameterTypes = new Type[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { newParameterTypes[i] = parameterTypes[i].getNonGenericType(); } NonArrayRefType[] newExceptionTypes = new NonArrayRefType[exceptionTypes.length]; for (int i = 0; i < exceptionTypes.length; i++) { newExceptionTypes[i] = (NonArrayRefType) exceptionTypes[i].getNonGenericType(); } return new BoundCall(base, containingWrapper, returnType.getNonGenericType(), newParameterTypes, newExceptionTypes, null, visible14, false); } public int getModifiers() {return base.getModifiers();} public boolean isDeprecated() {return base.isDeprecated();} public String getName() {return base.getName();} public Object getDefaultValue() {return base.getDefaultValue();} public ClassWrapper getDeclaringClass() {return base.getDeclaringClass();} public boolean isInheritable() {return base.isInheritable();} private static String getNonGenericSig(CallWrapper call) { String sig = call.getName() + "("; Type[] parameterTypes = call.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { if (i > 0) sig += ","; sig += parameterTypes[i].getNonGenericTypeSig(); } sig += ")"; return sig; } public String getNonGenericSig() { return getNonGenericSig(this); } public int compareTo(CallWrapper call) { int result = getNonGenericSig().compareTo(getNonGenericSig(call)); if (result != 0) return result; if (call instanceof BoundCall) { if (visible15 && !((BoundCall) call).visible15) return 1; if (!visible15 && ((BoundCall) call).visible15) return -1; } return getReturnType().getNonGenericTypeSig().compareTo(call.getReturnType().getNonGenericTypeSig()); } public int compareTo(Object o) { return compareTo((CallWrapper) o); } public String toString() { return containingWrapper + "." + getNonGenericSig(); } }japitools-0.9.7/src/net/wuffies/japi/BoundCallSet.java0000644000175000017500000000457110404333242023021 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005,2006 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; import java.util.HashMap; import java.util.Iterator; // This is sort of a map from erased-return-type to BoundCall, but it maintains // some constraints on its members, specifically: // - Only one call can be visible15. // - Adding a new member with the same return type overwrites the existing one. // - Adding a new member that is visible15 causes the existing item that is // visible15 to be replaced with a bind14'd version, unless that existing // item was !visible14 in which case it's removed instead. // - If the erased signature of the method (*excluding* the return type) is // different than for the members already present, an exception is thrown. class BoundCallSet { private HashMap calls = new HashMap(); private BoundCall visible15call; public void add(BoundCall call) { if (call.isVisible15()) { if (visible15call != null) { calls.remove(visible15call.getReturnType().getNonGenericTypeSig()); if (visible15call.isVisible14()) { add(visible15call.bind14()); } } visible15call = call; } if (call.getReturnType() != null) { calls.put(call.getReturnType().getNonGenericTypeSig(), call); } else { calls.put("", call); } } public Iterator iterator() { return calls.values().iterator(); } public int size() { return calls.size(); } }japitools-0.9.7/src/net/wuffies/japi/BoundField.java0000644000175000017500000000415510404333242022513 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; class BoundField implements FieldWrapper, Comparable { private FieldWrapper base; private Type type; private BoundField(FieldWrapper base, Type type) { this.base = base; this.type = type; } public BoundField(FieldWrapper base) { this(base, base.getType()); } public Type getType() { return type; } public BoundField bind(ClassType t) { if (t == null) return this; return new BoundField(base, type.bindWithFallback(t)); } public int getModifiers() {return base.getModifiers();} public boolean isDeprecated() {return base.isDeprecated();} public String getName() {return base.getName();} public boolean isPrimitiveConstant() {return base.isPrimitiveConstant();} public Object getPrimitiveValue() {return base.getPrimitiveValue();} public ClassWrapper getDeclaringClass() {return base.getDeclaringClass();} public boolean isEnumField() {return base.isEnumField();} public int compareTo(FieldWrapper f) { return getName().compareTo(f.getName()); } public int compareTo(Object o) { return compareTo((FieldWrapper) o); } } japitools-0.9.7/src/net/wuffies/japi/BoundMemberSet.java0000644000175000017500000000567310404333242023361 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005,2006 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; import java.util.HashMap; import java.util.Iterator; import java.util.Arrays; class BoundMemberSet { private HashMap fields = new HashMap(); private HashMap calls = new HashMap(); public BoundField[] getFields() { BoundField[] result = new BoundField[fields.size()]; fields.values().toArray(result); Arrays.sort(result); return result; } public BoundCall[] getCalls() { BoundCall[] result = new BoundCall[callsSize()]; int idx = 0; for (Iterator i = calls.values().iterator(); i.hasNext(); ) { BoundCallSet bcs = (BoundCallSet) i.next(); for (Iterator j = bcs.iterator(); j.hasNext(); ) { BoundCall call = (BoundCall) j.next(); result[idx++] = call; } } Arrays.sort(result); return result; } public void bindAndAdd(BoundField field, ClassType ctype) { fields.put(field.getName(), field.bind(ctype)); } public void bindAndAdd(BoundCall call, ClassType ctype) { String sig = call.getNonGenericSig(); BoundCall bcall = call.bind(ctype); if (!sig.equals(bcall.getNonGenericSig())) { add(call.bind14()); } add(bcall); } public void bindAndAddAll(BoundMemberSet set, ClassType ctype) { BoundField[] fields = set.getFields(); BoundCall[] calls = set.getCalls(); for (int i = 0; i < fields.length; i++) { bindAndAdd(fields[i], ctype); } for (int i = 0; i < calls.length; i++) { bindAndAdd(calls[i], ctype); } } private void add(BoundCall call) { String sig = call.getNonGenericSig(); BoundCallSet bcs = (BoundCallSet) calls.get(sig); if (bcs == null) { bcs = new BoundCallSet(); calls.put(sig, bcs); } bcs.add(call); } private int callsSize() { int result = 0; for (Iterator i = calls.values().iterator(); i.hasNext(); ) { BoundCallSet bcs = (BoundCallSet) i.next(); result += bcs.size(); } return result; } }japitools-0.9.7/src/net/wuffies/japi/CallWrapper.java0000644000175000017500000000307410314136577022727 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public interface CallWrapper extends GenericWrapper { Type[] getParameterTypes(); NonArrayRefType[] getExceptionTypes(); String getName(); Type getReturnType(); ClassWrapper getDeclaringClass(); boolean isInheritable(); /** * For annotation methods, the default value, if there is one. If the method returns an annotation type * this should return null for now until we can figure out a good way to get the info without having to * load the annotation class into this JVM! */ Object getDefaultValue(); } japitools-0.9.7/src/net/wuffies/japi/ClassFile.java0000644000175000017500000012727710526120766022372 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2004 Jeroen Frijters // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; import java.lang.reflect.Modifier; import java.io.*; import java.util.*; import java.util.zip.*; import java.io.DataOutputStream; import java.io.OutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class ClassFile implements ClassWrapper { private static final boolean DEBUG_SVUID = false; private static final int CONSTANT_Class = 7; private static final int CONSTANT_Fieldref = 9; private static final int CONSTANT_Methodref = 10; private static final int CONSTANT_InterfaceMethodref = 11; private static final int CONSTANT_String = 8; private static final int CONSTANT_Integer = 3; private static final int CONSTANT_Float = 4; private static final int CONSTANT_Long = 5; private static final int CONSTANT_Double = 6; private static final int CONSTANT_NameAndType = 12; private static final int CONSTANT_Utf8 = 1; private DataInputStream todo; private ConstantPoolItem[] constant_pool; private int access_flags; private int raw_access_flags; private String name; private String superClass; private String[] interfaces; private FieldInfoItem[] fields; private FieldInfoItem[] allFields; private MethodInfoItem[] methods; private MethodInfoItem[] allMethods; private boolean deprecated; private TypeParam[] typeParameters; private ClassType superclassType; private ClassType[] interfaceTypes; private GenericWrapper containingWrapper; private class ConstantPoolItem { Object getConstantValue() { throw new RuntimeException(); } } private class ClassConstantPoolItem extends ConstantPoolItem { int name_index; ClassConstantPoolItem(int name_index) { this.name_index = name_index; } } private class FMIConstantPoolItem extends ConstantPoolItem { int class_index; int name_and_type_index; FMIConstantPoolItem(int class_index, int name_and_type_index) { this.class_index = class_index; this.name_and_type_index = name_and_type_index; } } private class StringConstantPoolItem extends ConstantPoolItem { int string_index; StringConstantPoolItem(int string_index) { this.string_index = string_index; } Object getConstantValue() { return getUtf8String(string_index); } } private class IntegerConstantPoolItem extends ConstantPoolItem { int _int; IntegerConstantPoolItem(int _int) { this._int = _int; } Object getConstantValue() { return new Integer(_int); } } private class FloatConstantPoolItem extends ConstantPoolItem { float _float; FloatConstantPoolItem(float _float) { this._float = _float; } Object getConstantValue() { return new Float(_float); } } private class LongConstantPoolItem extends ConstantPoolItem { long _long; LongConstantPoolItem(long _long) { this._long = _long; } Object getConstantValue() { return new Long(_long); } } private class DoubleConstantPoolItem extends ConstantPoolItem { double _double; DoubleConstantPoolItem(double _double) { this._double = _double; } Object getConstantValue() { return new Double(_double); } } private class NameAndTypeConstantPoolItem extends ConstantPoolItem { int name_index; int descriptor_index; NameAndTypeConstantPoolItem(int name_index, int descriptor_index) { this.name_index = name_index; this.descriptor_index = descriptor_index; } } private class Utf8ConstantPoolItem extends ConstantPoolItem { String string; Utf8ConstantPoolItem(String string) { this.string = string; } } private abstract class FMInfoItem implements Wrapper { int access_flags; int name_index; int descriptor_index; boolean deprecated; FMInfoItem(DataInputStream in) throws IOException { access_flags = in.readUnsignedShort(); name_index = in.readUnsignedShort(); descriptor_index = in.readUnsignedShort(); } public int getModifiers() { return access_flags; } public boolean isDeprecated() { return deprecated; } } private class FieldInfoItem extends FMInfoItem implements FieldWrapper { private Object constantValue; private Type type; FieldInfoItem(DataInputStream in) throws IOException { super(in); int attributes_count = in.readUnsignedShort(); for(int i = 0; i < attributes_count; i++) { int attribute_name_index = in.readUnsignedShort(); int attribute_length = in.readInt(); String attributeName = getUtf8String(attribute_name_index); if(attributeName.equals("ConstantValue")) { constantValue = constant_pool[in.readUnsignedShort()].getConstantValue(); } else if(attributeName.equals("Deprecated")) { this.deprecated = true; } else if(attributeName.equals("Signature")) { int signature_index = in.readUnsignedShort(); String signature = getUtf8String(signature_index); FieldSignatureParser p = new FieldSignatureParser(ClassFile.this, signature); type = p.getFieldType(); } else { skip(in, attribute_length); } } if(type == null) { type = Type.fromNonGenericSig(getUtf8String(descriptor_index)); } } public String getName() { return getUtf8String(name_index); } public Type getType() { return type; } public boolean isEnumField() { return (this.access_flags & 0x4000) != 0; } public boolean isPrimitiveConstant() { return constantValue != null; } public Object getPrimitiveValue() { return constantValue; } public ClassWrapper getDeclaringClass() { return ClassFile.this; } public int compareTo(Object obj) { return getName().compareTo(((FieldInfoItem)obj).getName()); } void resolve() { type = Type.resolveTypeParameter(type); } } private class MethodInfoItem extends FMInfoItem implements CallWrapper { private TypeParam[] typeParameters; private Type[] parameterTypes; private Type returnType; private RefType[] exceptionTypes; MethodInfoItem(DataInputStream in) throws IOException { super(in); int attributes_count = in.readUnsignedShort(); String[] exceptions = null; for(int i = 0; i < attributes_count; i++) { int attribute_name_index = in.readUnsignedShort(); int attribute_length = in.readInt(); String attributeName = getUtf8String(attribute_name_index); if(attributeName.equals("Exceptions")) { int count = in.readUnsignedShort(); exceptions = new String[count]; for(int j = 0; j < count; j++) { exceptions[j] = getClassConstantName(in.readUnsignedShort()); } } else if(attributeName.equals("Deprecated")) { this.deprecated = true; } else if(attributeName.equals("Signature")) { int signature_index = in.readUnsignedShort(); String signature = getUtf8String(signature_index); //System.out.println(signature); MethodSignatureParser p = new MethodSignatureParser(this, signature); typeParameters = p.getTypeParameters(); parameterTypes = p.getParameterTypes(); returnType = p.getReturnType(); exceptionTypes = p.getExceptionTypes(); } else { skip(in, attribute_length); } } if(exceptionTypes == null || exceptionTypes.length == 0) { if(exceptions == null) { exceptionTypes = new RefType[0]; } else { exceptionTypes = new RefType[exceptions.length]; for(int i = 0; i < exceptions.length; i++) { exceptionTypes[i] = new ClassType(exceptions[i]); } } } if(parameterTypes == null) { String sig = getUtf8String(descriptor_index); ArrayList l = new ArrayList(); for(int i = 1; sig.charAt(i) != ')'; i++) { int start = i; while(sig.charAt(i) == '[') i++; if(sig.charAt(i) == 'L') i = sig.indexOf(';', i); l.add(Type.fromNonGenericSig(sig.substring(start, i + 1))); } parameterTypes = new Type[l.size()]; l.toArray(parameterTypes); } if(returnType == null) { String sig = getUtf8String(descriptor_index); returnType = Type.fromNonGenericSig(sig.substring(sig.lastIndexOf(')') + 1)); } if(exceptionTypes == null) { exceptionTypes = new ClassType[exceptions.length]; for (int i = 0; i < exceptions.length; i++) { exceptionTypes[i] = new ClassType(exceptions[i]); } } } public String getName() { String name = getUtf8String(name_index); if(name.equals("")) return ""; else return name; } public String toString() { return "MethodInfoItem:" + ClassFile.this.getName() + "." + this.getName(); } public String getRawName() { return getUtf8String(name_index); } public String getDescriptor() { return getUtf8String(descriptor_index); } public Type[] getParameterTypes() { // The first parameter of a nonstatic inner class constructor is the enclosing // instance, rather than a real parameter. Identify this case and skip it. // Note: this doesn't handle anonymous classes which are scoped to a method since // getContainingWrapper() isn't a ClassWrapper in that case; these are handled // wrong but they are never public, so they never show up in japi files, so // having an incorrect view of their constructor parameters is irrelevant. if ("".equals(getName()) && !Modifier.isStatic(ClassFile.this.getModifiers()) && ClassFile.this.getContainingWrapper() != null && ClassFile.this.getContainingWrapper() instanceof ClassWrapper) { // As far as I can tell all the things I check here are required by the JLS so // I just throw if they're violated. if (parameterTypes.length == 0) throw new RuntimeException("Nonstatic inner class " + ClassFile.this.getName() + " constructor has no parameters!"); if (!(parameterTypes[0] instanceof ClassType)) throw new RuntimeException("First parameter of nonstatic inner class " + ClassFile.this.getName() + " constructor is not a class type"); ClassType t = (ClassType) parameterTypes[0]; ClassWrapper cw = (ClassWrapper) ClassFile.this.getContainingWrapper(); if (!t.getName().equals(cw.getName())) throw new RuntimeException("First parameter of nonstatic inner class " + ClassFile.this.getName() + " constructor is " + t.getName() + ", not " + cw.getName()); List paramList = Arrays.asList(parameterTypes); List realParamList = paramList.subList(1, parameterTypes.length); return (Type[]) realParamList.toArray(new Type[parameterTypes.length - 1]); } return parameterTypes; } public NonArrayRefType[] getExceptionTypes() { ArrayList l = new ArrayList(); for(int i = 0; i < exceptionTypes.length; i++) { if(exceptionTypes[i] instanceof ClassType) { l.add(exceptionTypes[i]); } } // FIXME ClassType[] hack = new ClassType[l.size()]; l.toArray(hack); return hack; } public TypeParam[] getTypeParams() { return typeParameters; } public Type getReturnType() { if(getUtf8String(name_index).equals("")) return null; return returnType; } public ClassWrapper getDeclaringClass() { return ClassFile.this; } public Object getDefaultValue() { // FIXME15 - implement this. Don't try to return anything if the value is an annotation type though // because we don't want to have to load the type into this JVM! return null; } public int compareTo(Object obj) { return getSig().compareTo(((MethodInfoItem)obj).getSig()); } private String sig; // FIXME (SAB) - this may need to be removed entirely and the comparison of members be offloaded outside this class String getSig() { if (sig == null) { sig = getName() + "("; String comma = ""; Type[] parameterTypes = getParameterTypes(); for (int j = 0; j < parameterTypes.length; j++) { sig += comma + parameterTypes[j].getNonGenericTypeSig(); comma = ","; } sig += ")"; } return sig; } public boolean isInheritable() { return !Modifier.isPrivate(this.access_flags) && !getUtf8String(name_index).equals(""); } public GenericWrapper getContainingWrapper() { return ClassFile.this; } void resolve() { if(typeParameters != null) { for(int i = 0; i < typeParameters.length; i++) { typeParameters[i].resolveTypeParameters(); } } for(int i = 0; i < parameterTypes.length; i++) { parameterTypes[i] = Type.resolveTypeParameter(parameterTypes[i]); } returnType = Type.resolveTypeParameter(returnType); for(int i = 0; i < exceptionTypes.length; i++) { exceptionTypes[i] = (RefType)Type.resolveTypeParameter(exceptionTypes[i]); } } } private class AttributeInfoItem { int attribute_name_index; int attribute_length; byte[] info; void read(DataInputStream in) throws IOException { attribute_name_index = in.readUnsignedShort(); attribute_length = in.readInt(); info = new byte[attribute_length]; in.readFully(info); } } private ClassFile(byte[] buf) throws IOException { DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf)); todo = in; if(in.readInt() != 0xCAFEBABE) { throw new IOException("Illegal magic"); } int minor_version = in.readUnsignedShort(); int major_version = in.readUnsignedShort(); int constant_pool_count = in.readUnsignedShort(); constant_pool = new ConstantPoolItem[constant_pool_count]; for(int i = 1; i < constant_pool_count; i++) { switch(in.readUnsignedByte()) { case CONSTANT_Class: constant_pool[i] = new ClassConstantPoolItem(in.readUnsignedShort()); break; case CONSTANT_Fieldref: case CONSTANT_Methodref: case CONSTANT_InterfaceMethodref: constant_pool[i] = new FMIConstantPoolItem(in.readUnsignedShort(), in.readUnsignedShort()); break; case CONSTANT_String: constant_pool[i] = new StringConstantPoolItem(in.readUnsignedShort()); break; case CONSTANT_Integer: constant_pool[i] = new IntegerConstantPoolItem(in.readInt()); break; case CONSTANT_Float: constant_pool[i] = new FloatConstantPoolItem(in.readFloat()); break; case CONSTANT_Long: constant_pool[i] = new LongConstantPoolItem(in.readLong()); i++; break; case CONSTANT_Double: constant_pool[i] = new DoubleConstantPoolItem(in.readDouble()); i++; break; case CONSTANT_NameAndType: constant_pool[i] = new NameAndTypeConstantPoolItem(in.readUnsignedShort(), in.readUnsignedShort()); break; case CONSTANT_Utf8: constant_pool[i] = new Utf8ConstantPoolItem(in.readUTF()); break; default: throw new IOException("unrecognized constant pool item"); } } raw_access_flags = in.readUnsignedShort(); access_flags = raw_access_flags | Modifier.STATIC; int this_class = in.readUnsignedShort(); name = this_class == 0 ? null : getClassConstantName(this_class); int super_class = in.readUnsignedShort(); superClass = (super_class == 0 || Modifier.isInterface(access_flags)) ? null : getClassConstantName(super_class); } private void ensureParsed() { if (todo != null) { try { DataInputStream in = todo; todo = null; readSecondPart(in); } catch (IOException x) { throw new RuntimeException(x); } } } private void readSecondPart(DataInputStream in) throws IOException { int interfaces_count = in.readUnsignedShort(); interfaces = new String[interfaces_count]; for(int i = 0; i < interfaces_count; i++) { interfaces[i] = getClassConstantName(in.readUnsignedShort()); } int fields_count = in.readUnsignedShort(); fields = new FieldInfoItem[fields_count]; for(int i = 0; i < fields_count; i++) { fields[i] = new FieldInfoItem(in); } int methods_count = in.readUnsignedShort(); methods = new MethodInfoItem[methods_count]; for(int i = 0; i < methods_count; i++) { methods[i] = new MethodInfoItem(in); } int attributes_count = in.readUnsignedShort(); for(int i = 0; i < attributes_count; i++) { int attribute_name_index = in.readUnsignedShort(); int attribute_length = in.readInt(); String attributeName = getUtf8String(attribute_name_index); if(attributeName.equals("InnerClasses")) { int count = in.readUnsignedShort(); for(int j = 0; j < count; j++) { int inner_class = in.readUnsignedShort(); int outer_class = in.readUnsignedShort(); int inner_name = in.readUnsignedShort();; int access_flags = in.readUnsignedShort(); if(getClassConstantName(inner_class).equals(name)) { int mask = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE | Modifier.STATIC; this.access_flags &= ~mask; this.access_flags |= access_flags & mask; if(outer_class != 0 && !Modifier.isStatic(access_flags)) { containingWrapper = ClassFile.forName(getClassConstantName(outer_class)); } } } } else if(attributeName.equals("Deprecated")) { deprecated = true; } else if(attributeName.equals("Signature")) { int signature_index = in.readUnsignedShort(); String signature = getUtf8String(signature_index); //System.out.println(signature); ClassSignatureParser p = new ClassSignatureParser(this, signature); typeParameters = p.getTypeParameters(); superclassType = p.getSuperclassType(); interfaceTypes = p.getInterfaceTypes(); } else if(attributeName.equals("EnclosingMethod")) { int class_index = in.readUnsignedShort(); int method_index = in.readUnsignedShort(); ClassFile containingClass = ClassFile.forName(getClassConstantName(class_index)); if(method_index == 0) { containingWrapper = containingClass; } else { NameAndTypeConstantPoolItem cpi = (NameAndTypeConstantPoolItem)constant_pool[method_index]; String name = getUtf8String(cpi.name_index); String descriptor = getUtf8String(cpi.descriptor_index); containingWrapper = containingClass.findMethod(name, descriptor); } } else { skip(in, attribute_length); } } if (superclassType == null) { superclassType = new ClassType(superClass); } if (interfaceTypes == null) { interfaceTypes = new ClassType[interfaces.length]; for(int i = 0; i < interfaces.length; i++) { interfaceTypes[i] = new ClassType(interfaces[i]); } } if(typeParameters != null) { for(int i = 0; i < typeParameters.length; i++) { typeParameters[i].resolveTypeParameters(); } } superclassType.resolveTypeParameters(); for(int i = 0; i < interfaceTypes.length; i++) { interfaceTypes[i].resolveTypeParameters(); } for(int i = 0; i < fields.length; i++) { fields[i].resolve(); } for(int i = 0; i < methods.length; i++) { methods[i].resolve(); } } static class UnresolvedTypeParam extends NonArrayRefType { private GenericWrapper associatedWrapper; private String name; UnresolvedTypeParam(GenericWrapper associatedWrapper, String name) { this.associatedWrapper = associatedWrapper; this.name = name; } public String getNonGenericTypeSig() { throw new RuntimeException(); } public void resolveTypeParameters() { throw new RuntimeException(); } public Type bind(ClassType t) { throw new RuntimeException(); } public String getJavaRepr(GenericWrapper wrapper) { throw new RuntimeException(); } public Type getNonGenericType() { throw new RuntimeException(); } public TypeParam resolve() { GenericWrapper container = associatedWrapper; do { //System.out.println("Searching for " + name + " in " + container); TypeParam[] typeParams = container.getTypeParams(); if(typeParams != null) { for(int i = 0; i < typeParams.length; i++) { if(typeParams[i].getName().equals(name)) { return typeParams[i]; } } } container = container.getContainingWrapper(); } while (container != null); throw new RuntimeException("ClassFormatError: TypeParameter '" + name + "' does not exist in " + associatedWrapper); } } private static class SignatureParser { private GenericWrapper container; private String signature; private int pos; SignatureParser(GenericWrapper container, String signature) { this.container = container; this.signature = signature; } TypeParam[] readFormalTypeParameters() { consume('<'); ArrayList params = new ArrayList(); do { params.add(readFormalTypeParameter()); } while(peekChar() != '>'); consume('>'); TypeParam[] list = new TypeParam[params.size()]; params.toArray(list); return list; } private TypeParam readFormalTypeParameter() { String identifier = readIdentifier(); consume(':'); ArrayList bounds = new ArrayList(); if(peekChar() != ':') { bounds.add(readFieldTypeSignature()); } while(peekChar() == ':') { consume(':'); bounds.add(readFieldTypeSignature()); } NonArrayRefType[] boundsArray = new NonArrayRefType[bounds.size()]; bounds.toArray(boundsArray); return new TypeParam(container, identifier, boundsArray); } RefType readFieldTypeSignature() { switch(peekChar()) { case 'L': return readClassTypeSignature(); case '[': return readArrayTypeSignature(); case 'T': return readTypeVariableSignature(); default: throw new RuntimeException("ClassFormatError: at pos = " + pos); } } RefType readClassTypeSignature() { consume('L'); String className = ""; for(;;) { String part = readIdentifier(); if(peekChar() != '/') { className += part; break; } consume('/'); className += part + "."; } ArrayList typeArguments = new ArrayList(); if(peekChar() == '<') { readTypeArguments(typeArguments); } while(peekChar() == '.') { consume('.'); className += "$" + readIdentifier(); if(peekChar() == '<') { readTypeArguments(typeArguments); } } consume(';'); RefType[] array = new RefType[typeArguments.size()]; typeArguments.toArray(array); return new ClassType(className, array); } private void readTypeArguments(ArrayList list) { consume('<'); do { list.add(readTypeArgument()); } while((peekChar() != '>')); consume('>'); } private RefType readTypeArgument() { char c = peekChar(); if(c == '+') { consume('+'); return new WildcardType((RefType) readFieldTypeSignature()); } else if(c == '-') { consume('-'); return new WildcardType(null, (RefType) readFieldTypeSignature()); } else if(c == '*') { consume('*'); return new WildcardType(); } else { return readFieldTypeSignature(); } } RefType readArrayTypeSignature() { consume('['); switch(peekChar()) { case 'L': case '[': case 'T': return new ArrayType(readFieldTypeSignature()); default: return new ArrayType(PrimitiveType.fromSig(readChar())); } } RefType readTypeVariableSignature() { consume('T'); String identifier = readIdentifier(); consume(';'); return new UnresolvedTypeParam(container, identifier); } private String readIdentifier() { int start = pos; char c; do { readChar(); c = peekChar(); } while(";:./<>-+*".indexOf(c) == -1); return signature.substring(start, pos); } char peekChar() { if(pos == signature.length()) return '\u0000'; else return signature.charAt(pos); } char readChar() { return signature.charAt(pos++); } void consume(char c) { if(readChar() != c) throw new RuntimeException("ClassFormatError: at pos = " + pos); } } private static class ClassSignatureParser extends SignatureParser { private TypeParam[] typeParameters; private ClassType superclassType; private ClassType[] interfaceTypes; ClassSignatureParser(ClassWrapper container, String signature) { super(container, signature); if(peekChar() == '<') { typeParameters = readFormalTypeParameters(); } // SuperclassSignature superclassType = (ClassType)readClassTypeSignature(); ArrayList interfaces = new ArrayList(); while(peekChar() == 'L') { // SuperinterfaceSignature interfaces.add(readClassTypeSignature()); } interfaceTypes = new ClassType[interfaces.size()]; interfaces.toArray(interfaceTypes); } TypeParam[] getTypeParameters() { return typeParameters; } ClassType getSuperclassType() { return superclassType; } ClassType[] getInterfaceTypes() { return interfaceTypes; } } private static class FieldSignatureParser extends SignatureParser { private Type type; FieldSignatureParser(ClassWrapper container, String signature) { super(container, signature); switch(peekChar()) { case 'L': case '[': case 'T': type = readFieldTypeSignature(); break; default: type = PrimitiveType.fromSig(readChar()); break; } } Type getFieldType() { return type; } } private static class MethodSignatureParser extends SignatureParser { private TypeParam[] typeParameters; private Type[] argTypes; private Type retType; private RefType[] throwsSigs; MethodSignatureParser(CallWrapper wrapper, String signature) { super(wrapper, signature); if(peekChar() == '<') { typeParameters = readFormalTypeParameters(); } consume('('); ArrayList args = new ArrayList(); while(peekChar() != ')') { args.add(readTypeSignature()); } argTypes = new Type[args.size()]; args.toArray(argTypes); consume(')'); retType = readTypeSignature(); ArrayList throwsSigs = new ArrayList(); while(peekChar() == '^') { consume('^'); if(peekChar() == 'T') { throwsSigs.add(readTypeVariableSignature()); } else { throwsSigs.add(readClassTypeSignature()); } } this.throwsSigs = new RefType[throwsSigs.size()]; throwsSigs.toArray(this.throwsSigs); } TypeParam[] getTypeParameters() { return typeParameters; } Type[] getParameterTypes() { return argTypes; } Type getReturnType() { return retType; } RefType[] getExceptionTypes() { return throwsSigs; } private Type readTypeSignature() { switch(peekChar()) { case 'T': return readTypeVariableSignature(); case 'L': return readClassTypeSignature(); case '[': return readArrayTypeSignature(); default: return PrimitiveType.fromSig(readChar()); } } } private String getClassConstantName(int idx) { return getUtf8String(((ClassConstantPoolItem)constant_pool[idx]).name_index).replace('/', '.'); } private String getUtf8String(int idx) { return ((Utf8ConstantPoolItem)constant_pool[idx]).string; } public int getModifiers() { return access_flags; } public boolean isDeprecated() { ensureParsed(); return deprecated; } public String getName() { return name; } public String toString() { return "ClassFile:" + getName(); } public ClassType getSuperclass() { if(superClass == null) return null; else return superclassType; } private static boolean gotSerializable = false; private static ClassFile serializableClass = null; public boolean isSerializable() { ensureParsed(); if (!gotSerializable) { gotSerializable = true; try { serializableClass = forName("java.io.Serializable"); } catch (RuntimeException e) {} } return serializableClass != null && !isInterface() && isSubTypeOf(serializableClass); } public boolean isSubTypeOf(ClassFile c) { ensureParsed(); if(this == c) return true; for(int i = 0; i < interfaces.length; i++) { if(forName(interfaces[i]).isSubTypeOf(c)) return true; } if(superClass != null) return forName(superClass).isSubTypeOf(c); else return false; } // This code is partially based on GNU Classpath's implementation which is // (c) FSF and licensed under the GNU Library General Public License. public Long getSerialVersionUID() { ensureParsed(); for(int i = 0; i < fields.length; i++) { if(fields[i].getName().equals("serialVersionUID") && fields[i].getType() == PrimitiveType.LONG && Modifier.isStatic(fields[i].getModifiers()) && Modifier.isFinal(fields[i].getModifiers())) { return (Long) fields[i].getPrimitiveValue(); } } // The class didn't define serialVersionUID, so we have to compute it try { final MessageDigest md = MessageDigest.getInstance("SHA"); OutputStream digest = new OutputStream() { private boolean text = false; private void p(byte b) { char ch = (char) b; if (ch > ' ' && ch <= '~' && ch != '\"') { if (!text) System.out.print("\""); text = true; System.out.print(ch); } else { if (text) System.out.print("\", "); text = false; System.out.print(b + ", "); } } public void write(int b) { if (DEBUG_SVUID) p((byte) b); md.update((byte) b); } public void write(byte[] data, int offset, int length) { if (DEBUG_SVUID) { for (int i = 0; i < length; i++) p(data[offset + i]); } md.update(data, offset, length); } }; DataOutputStream data_out = new DataOutputStream(digest); data_out.writeUTF(getName()); int modifiers = access_flags; // just look at interesting bits modifiers = modifiers & (Modifier.ABSTRACT | Modifier.FINAL | Modifier.INTERFACE | Modifier.PUBLIC); data_out.writeInt(modifiers); String[] interfaces = (String[])this.interfaces.clone(); Arrays.sort(interfaces); for (int i = 0; i < interfaces.length; i++) { data_out.writeUTF(interfaces[i]); } Arrays.sort(fields, new Comparator() { public int compare(Object o1, Object o2) { return compare((FieldWrapper)o1, (FieldWrapper)o2); } public int compare(FieldWrapper f1, FieldWrapper f2) { if (f1.getName().equals(f2.getName())) { return f1.getType().getNonGenericTypeSig().compareTo(f2.getType().getNonGenericTypeSig()); } else { return f1.getName().compareTo(f2.getName()); } } }); for (int i = 0; i < fields.length; i++) { FieldInfoItem field = fields[i]; modifiers = field.getModifiers(); if (Modifier.isPrivate (modifiers) && (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers))) { continue; } data_out.writeUTF(field.getName()); data_out.writeInt(modifiers); data_out.writeUTF(field.getType().getNonGenericTypeSig()); } Arrays.sort(methods, new Comparator() { public int compare(Object o1, Object o2) { return compare((MethodInfoItem)o1, (MethodInfoItem)o2); } public int compare(MethodInfoItem m1, MethodInfoItem m2) { if (m1.getRawName().equals(m2.getRawName())) { return m1.getDescriptor().compareTo(m2.getDescriptor()); } else { return m1.getRawName().compareTo(m2.getRawName()); } } }); for (int i = 0; i < methods.length; i++) { MethodInfoItem method = methods[i]; modifiers = method.getModifiers(); if (Modifier.isPrivate(modifiers)) { continue; } data_out.writeUTF(method.getRawName()); data_out.writeInt(modifiers); // the replacement of '/' with '.' was needed to make computed // SUID's agree with those computed by JDK data_out.writeUTF(method.getDescriptor().replace('/', '.')); } data_out.close (); byte[] sha = md.digest (); long result = 0; int len = sha.length < 8 ? sha.length : 8; for (int i=0; i < len; i++) result += (long)(sha[i] & 0xFF) << (8 * i); return new Long(result); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("The SHA algorithm was not found to use in " + "computing the Serial Version UID for class " + getName()); } catch (IOException ioe) { throw new RuntimeException(ioe.getMessage()); } } private CallWrapper findMethod(String name, String descriptor) { for(int i = 0; i < methods.length; i++) { if(methods[i].getRawName().equals(name) && methods[i].getDescriptor().equals(descriptor)) { return methods[i]; } } if(superClass != null) return ClassFile.forName(superClass).findMethod(name, descriptor); else throw new RuntimeException("NoSuchMethodError: " + name + descriptor); } public CallWrapper[] getCalls() { ensureParsed(); // FIXME - should we take a copy here to make sure the caller can't mess // with this class's internal state? return methods; // Old code for comparison... // if(allMethods == null) // { // HashMap map = new HashMap(); // ClassType[] ifaces = getInterfaces(); // for(int i = 0; i < ifaces.length; i++) // { // MethodInfoItem[] m = (MethodInfoItem[])ifaces[i].getWrapper().getCalls(); // for(int j = 0; j < m.length; j++) // { // if(!map.containsKey(m[j].getSig())) // map.put(m[j].getSig(), m[j]); // } // } // if(superClass != null) // { // MethodInfoItem[] m = (MethodInfoItem[])getSuperclass().getWrapper().getCalls(); // for(int i = 0; i < m.length; i++) // { // if(m[i].isInheritable()) // map.put(m[i].getSig(), m[i]); // } // } // for(int i = 0; i < methods.length; i++) // { // // JDK15: skip bridge methods (the ACC_VOLATILE bit corresponds to the ACC_BRIDGE bit) // if(!Modifier.isVolatile(methods[i].getModifiers())) // map.put(methods[i].getSig(), methods[i]); // } // allMethods = new MethodInfoItem[map.size()]; // map.values().toArray(allMethods); // Arrays.sort(allMethods); // } // return allMethods; } public boolean isInterface() { return Modifier.isInterface(access_flags); } public boolean isAnnotation() { return (access_flags & 0x2000) != 0; } public boolean isEnum() { return (access_flags & 0x4000) != 0; } public GenericWrapper getContainingWrapper() { ensureParsed(); return containingWrapper; } public ClassType[] getInterfaces() { ensureParsed(); return interfaceTypes; } public FieldWrapper[] getFields() { ensureParsed(); // FIXME - should we take a copy here to make sure the caller can't mess // with this class's internal state? return fields; // Old code, for comparison // if(allFields == null) // { // HashMap map = new HashMap(); // if(superClass != null) // { // FieldInfoItem[] f = (FieldInfoItem[])getSuperclass().getWrapper().getFields(); // for(int i = 0; i < f.length; i++) // { // map.put(f[i].getName(), f[i]); // } // } // ClassType[] ifaces = getInterfaces(); // for(int i = 0; i < ifaces.length; i++) // { // FieldInfoItem[] f = (FieldInfoItem[])ifaces[i].getWrapper().getFields(); // for(int j = 0; j < f.length; j++) // { // map.put(f[j].getName(), f[j]); // } // } // for(int i = 0; i < fields.length; i++) // { // map.put(fields[i].getName(), fields[i]); // } // allFields = new FieldInfoItem[map.size()]; // map.values().toArray(allFields); // Arrays.sort(allFields); // } // return allFields; } public TypeParam[] getTypeParams() { ensureParsed(); return typeParameters; } private static ClassPathEntry[] classpath; private static WeakHashMap cache = new WeakHashMap(); private static abstract class ClassPathEntry { abstract ClassFile load(String name); } private static class JarClassPathEntry extends ClassPathEntry { private ZipFile zf; JarClassPathEntry(File f) throws IOException { this.zf = new ZipFile(f); } ClassFile load(String name) { try { ZipEntry entry = zf.getEntry(name.replace('.', '/') + ".class"); if(entry != null) { DataInputStream dis = new DataInputStream(zf.getInputStream(entry)); try { byte[] buf = new byte[(int)entry.getSize()]; dis.readFully(buf); return new ClassFile(buf); } finally { dis.close(); } } } catch(IOException x) { } return null; } } private static class DirClassPathEntry extends ClassPathEntry { private File dir; DirClassPathEntry(File dir) { this.dir = dir; } ClassFile load(String name) { try { File f = new File(dir, name.replace('.', File.separatorChar) + ".class"); DataInputStream dis = new DataInputStream(new FileInputStream(f)); try { byte[] buf = new byte[(int)f.length()]; dis.readFully(buf); return new ClassFile(buf); } finally { dis.close(); } } catch(IOException x) { } return null; } } public static ClassFile forName(String name) { ClassFile cf = (ClassFile)cache.get(name); if(cf != null) return cf; for(int i = 0; i < classpath.length; i++) { cf = classpath[i].load(name); if(cf != null) { cache.put(name, cf); return cf; } } throw new RuntimeException("NoClassDefFoundError: " + name); } public static void setClasspath(String classpath) throws IOException { ArrayList list = new ArrayList(); StringTokenizer st = new StringTokenizer(classpath, File.pathSeparator); while(st.hasMoreTokens()) { File f = new File(st.nextToken()); if(f.isFile()) list.add(new JarClassPathEntry(f)); else if(f.isDirectory()) list.add(new DirClassPathEntry(f)); } ClassFile.classpath = new ClassPathEntry[list.size()]; list.toArray(ClassFile.classpath); } static void skip(DataInputStream dis, int count) throws IOException { while(count > 0) count -= (int)dis.skip(count); } } japitools-0.9.7/src/net/wuffies/japi/ClassType.java0000644000175000017500000001230110315337652022411 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; /** * Represents a class, interface, enum or annotation. */ public class ClassType extends NonArrayRefType { private String name; private ClassWrapper classWrapper; private RefType[] typeArguments; public ClassType(String name) { this(name, null); } public ClassType(ClassWrapper wrapper) { this(wrapper, null); } public ClassType(String name, RefType[] typeArguments) { this.name = name; if (typeArguments != null && typeArguments.length == 0) { this.typeArguments = null; } else { this.typeArguments = typeArguments; } } public ClassType(ClassWrapper wrapper, RefType[] typeArguments) { this(wrapper.getName(), typeArguments); classWrapper = wrapper; checkArgs(); } private ClassType(String name, ClassWrapper wrapper, RefType[] typeArguments) { this.name = name; this.classWrapper = wrapper; this.typeArguments = typeArguments; } private void checkArgs() { TypeParam[] params = TypeParam.getAllTypeParams(classWrapper); if (params == null && typeArguments != null) { throw new RuntimeException("Cannot supply type arguments to a non-generic class (" + name + ")!"); } // This check may be wrong because it appears to be possible to construct a case where a type is half-bound, // using an unbound inner class of a generic containing class. Since I hope this isn't supposed to be legal, // I'm leaving this check as is until I can verify it. if (params != null && typeArguments != null && params.length != typeArguments.length) { throw new RuntimeException("Cannot supply " + typeArguments.length + " type arguments to " + name + " that expects " + params.length + " parameters!"); } } public String getName() { return name; } public String getJavaRepr(GenericWrapper wrapper) { String repr = name; if (typeArguments != null) { repr += "<"; for (int i = 0; i < typeArguments.length; i++) { if (i > 0) repr += ","; repr += typeArguments[i].getTypeSig(wrapper); } repr += ">"; } return repr; } public ClassWrapper getWrapper() { if (classWrapper == null) { classWrapper = ClassFile.forName(name); checkArgs(); } return classWrapper; } public RefType[] getTypeArguments() { return typeArguments; } public String getTypeSig(GenericWrapper wrapper) { StringBuffer sb = new StringBuffer("L" + name.replace('.', '/')); RefType[] args = getTypeArguments(); if (args != null && args.length > 0) { sb.append('<'); for (int i = 0; i < args.length; i++) { if (i > 0) sb.append(','); if (args[i] != null) sb.append(args[i].getTypeSig(wrapper)); } sb.append('>'); } sb.append(';'); return sb.toString(); } public String getNonGenericTypeSig() { return "L" + name.replace('.', '/') + ";"; } public Type getNonGenericType() { if (typeArguments == null) return this; return new ClassType(name, classWrapper, null); } public String toStringImpl() { String result = "Class:" + getName(); if (typeArguments != null) { result += "<"; for (int i = 0; i < typeArguments.length; i++) { if (i > 0) result += ","; result += typeArguments[i]; } result += ">"; } return result; } public Type bind(ClassType t) { debugStart("Bind", "to " + t); try { RefType[] typeArgs = getTypeArguments(); if (typeArgs == null) return this; RefType[] boundArgs = new RefType[typeArgs.length]; boolean isRaw = true; for (int i = 0; i < typeArgs.length; i++) { boundArgs[i] = (RefType) typeArgs[i].bind(t); if (boundArgs[i] != null) isRaw = false; } // If none of the args ended up bound to anything, it's effectively a raw type. if (isRaw) boundArgs = null; return new ClassType(name, classWrapper, boundArgs); } finally { debugEnd(); } } public void resolveTypeParameters() { if (typeArguments != null) { for (int i = 0; i < typeArguments.length; i++) { typeArguments[i] = (RefType)resolveTypeParameter(typeArguments[i]); } } } } japitools-0.9.7/src/net/wuffies/japi/ClassWrapper.java0000644000175000017500000000325310334251201023100 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public interface ClassWrapper extends GenericWrapper { String getName(); ClassType getSuperclass(); ClassType[] getInterfaces(); boolean isSerializable(); /** * Return the class's serialVersionUID, or null if a blank final * serialVersionUID field exists. */ Long getSerialVersionUID(); FieldWrapper[] getFields(); CallWrapper[] getCalls(); /** * True if this is an interface; also true for annotations. */ boolean isInterface(); /** * True if this is an interface that is actually an annotation. */ boolean isAnnotation(); /** * True if this class is actually an enum. */ boolean isEnum(); } japitools-0.9.7/src/net/wuffies/japi/FieldWrapper.java0000644000175000017500000000271610310622422023062 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public interface FieldWrapper extends Wrapper, Comparable { String getName(); Type getType(); boolean isPrimitiveConstant(); Object getPrimitiveValue(); ClassWrapper getDeclaringClass(); /** * True if this field is one of the special fields of an enum type that defines the * values of the enumeration. FIXME: Is this actually something that can be got from * the modifiers? */ boolean isEnumField(); } japitools-0.9.7/src/net/wuffies/japi/GenericWrapper.java0000644000175000017500000000366010311016023023405 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; /** * ClassWrapper and CallWrapper implement this interface, because they can both have type parameters. Constructors and fields cannot. */ public interface GenericWrapper extends Wrapper { /** * For generic classes and generic static methods, returns an array of TypeParam objects whose associatedWrappers are * set to this object. For constructors, non-generic methods and non-generic classes, returns null. * Note that for nonstatic inner classes, the type parameters of the containing class should *not* be included. * When these are needed they can be accessed by getContainingWrapper().getTypeParams(). */ TypeParam[] getTypeParams(); /** * This returns the containing wrapper (to be able to get at the type parameters). * This can be both a ClassWrapper and CallWrapper. Anonymous classes are contained in a CallWrapper. */ GenericWrapper getContainingWrapper(); } japitools-0.9.7/src/net/wuffies/japi/JapiantTask.java0000644000175000017500000001227510517000764022720 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2006 Jaroslav Tulach // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.StringTokenizer; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.FileScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.DirSet; import org.apache.tools.ant.types.FileSet; /** An Ant task to * wrap the embed the Japize into Ant scripts. * * @author Jaroslav Tulach */ public class JapiantTask extends Task { /** JARs with classes */ private FileSet jars; /** dirs with classes */ private DirSet dirs; /** output file */ private File out; /** gzip the output or not, by default taken from the out file extension */ private Boolean gzip; /** packages to scan */ private String packages; public JapiantTask() { } public FileSet createJars() { return jars = new FileSet(); } public DirSet createDirs() { return dirs = new DirSet(); } public void setFile(File f) { out = f; } public void setGzip(boolean g) { gzip = Boolean.valueOf(g); } public void setPackages(String p) { packages = p; } private boolean isGzip() { if (gzip != null) { return gzip.booleanValue(); } else { return out.getName().endsWith(".gz"); } } public void execute() throws BuildException { if (jars == null && dirs == null) { throw new BuildException("Either or element must be used!"); } if (out == null) { throw new BuildException("file attribute must be set!"); } if (packages == null) { throw new BuildException("packages attribute must be set!"); } ArrayList/**/ args = new ArrayList/**/(); if (!isGzip()) { args.add("unzip"); } args.add("as"); args.add(out.getPath()); args.add("packages"); int srcs = 0; if (jars != null) { FileScanner scanner = jars.getDirectoryScanner(getProject()); File dir = scanner.getBasedir(); String[] files = scanner.getIncludedFiles(); srcs += files.length; for (int i = 0; i < files.length; i++) { File f = new File(dir, files[i]); if (!f.isFile()) { throw new BuildException("Not a file: " + f); } args.add(f.getPath()); } } if (dirs != null) { DirectoryScanner scanner = dirs.getDirectoryScanner(getProject()); File dir = scanner.getBasedir(); log("dirs: " + dir, Project.MSG_VERBOSE); String[] files = scanner.getIncludedDirectories(); srcs += files.length; for (int i = 0; i < files.length; i++) { File f = new File(dir, files[i]); if (!f.isDirectory()) { throw new BuildException("Not a file: " + f); } args.add(f.getPath()); } } if (srcs == 0) { throw new BuildException("No or found"); } { args.add(System.getProperty("java.home") + File.separator + "lib" + File.separator + "rt.jar"); } { StringTokenizer t = new StringTokenizer(packages, " ,"); while (t.hasMoreElements()) { String pkg = t.nextToken(); int len = pkg.length(); if (pkg.endsWith(".**")) { args.add("+" + pkg.substring(0, len - 3)); } else if (pkg.endsWith(".*")) { args.add("=" + pkg.substring(0, len - 2)); } else { throw new BuildException("Unknown package: " + pkg); } } } log("Running japize with: " + args); try { Japize.main((String[])args.toArray(new String[0])); } catch (Exception ex) { throw new BuildException(ex); } } } japitools-0.9.7/src/net/wuffies/japi/Japize.java0000644000175000017500000015425310526230672021737 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005,2006 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; import java.lang.reflect.Modifier; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.io.File; import java.io.IOException; import java.util.Enumeration; import java.util.List; import java.util.ArrayList; import java.util.SortedSet; import java.util.Set; import java.util.Arrays; import java.util.TreeSet; import java.util.HashSet; import java.util.Map; import java.util.HashMap; import java.util.Arrays; import java.io.Writer; import java.util.Iterator; import java.io.PrintWriter; import java.io.BufferedWriter; import java.io.BufferedOutputStream; import java.io.OutputStreamWriter; import java.util.zip.GZIPOutputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.FileReader; import java.io.LineNumberReader; import java.text.SimpleDateFormat; import java.util.Date; /** * Process a Java API and emit a machine-readable description of the API, * suitable for comparison to other APIs. Specifically, the perl script * japicompat can test APIs for source/binary compatibility with each other. * * @author Stuart Ballard <stuart.a.ballard@gmail.com> */ public class Japize { /** * The path to scan for classes in. */ private static List path; /** * The package roots to scan in. */ private static SortedSet roots; /** * The packages to exclude. */ private static SortedSet exclusions; /** * Packages that are included exactly and whose subpackages shouldn't be * descended into. */ private static SortedSet exact; /** * Classes and packages to exclude from serialization */ private static SortedSet serialExclusions; /** * Classes and packages that would otherwise be excluded from serialization * that shouldn't be. Also includes "," representing the root package to * ensure that serialization defaults to included. */ private static SortedSet serialRoots; /** * The output writer to write results to. */ private static PrintWriter out; /** * The output writer to write "lint" output to, if any. */ private static PrintWriter lintOut; /* Disambiguation rules */ private static final int UNSPECIFIED = 0; private static final int EXPLICITLY = 1; private static final int APIS = 2; private static final int BYNAME = 3; private static final int PACKAGES = 4; private static final int CLASSES = 5; /** * Parse the program's arguments and perform the main processing. */ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, IOException, ClassNotFoundException { // init for case of multiple invocations path = new ArrayList(); roots = new TreeSet(); exclusions = new TreeSet(); exact = new TreeSet(); serialExclusions = new TreeSet(); serialRoots = new TreeSet(Arrays.asList(new String[] {","})); // Interpret any arguments that start with @ as filenames, and replace the argument with the // contents of those files. args = scanForFileArgs(args); // Scan the arguments until the end of keywords is reached, interpreting // all the intermediate arguments and dealing with them as appropriate. int i = 0; boolean zipIt = true; String fileName = null; String lintFileName = null; if (i < args.length && "unzip".equals(args[i])) { zipIt = false; i++; } if (i < args.length && "as".equals(args[i])) { fileName = args[++i]; i++; } if (i < args.length && "lint".equals(args[i])) { lintFileName = args[++i]; i++; } // The next word indicates the method used to decide whether an ambiguous // argument of the form a.b.c is a class or a package. Any other word is an // error, but checks further down will catch that. int disambig = UNSPECIFIED; if (i < args.length) { if ("explicitly".equals(args[i])) { disambig = EXPLICITLY; } else if ("apis".equals(args[i])) { disambig = APIS; } else if ("byname".equals(args[i])) { disambig = BYNAME; } else if ("packages".equals(args[i])) { disambig = PACKAGES; } else if ("classes".equals(args[i])) { disambig = CLASSES; } i++; } // Correct syntax requires that one of the previous cases must have matched, // and also that there be at least one more word. Both these cases, however, // can be errored below because they will result in both the path and the // set of roots being empty. if (i < args.length && disambig != UNSPECIFIED) { // Identify each argument that's prefixed with + or - and put it in // either the "roots" or the "exclusions" TreeSet as appropriate. Use // the disambiguation method specified above for arguments that do not // explicitly indicate if they are classes or packages. for (; i < args.length; i++) { char first = args[i].charAt(0); String pkgpath = args[i].substring(1); if (first == '+' || first == '-' || first == '=') { SortedSet setToAddTo; boolean isExact = false; if (pkgpath.endsWith(":serial")) { if (first == '=') { System.err.println("Cannot use '=' with ':serial' qualifier"); printUsage(); } setToAddTo = first == '+' ? serialRoots : serialExclusions; pkgpath = pkgpath.substring(0, pkgpath.lastIndexOf(':')); } else { setToAddTo = first == '-' ? exclusions : roots; isExact = (first == '='); } String pathToAdd = null; // First identify *whether* it's ambiguous - and whether it's legal. int commapos = pkgpath.indexOf(','); // If it contains a comma, and doesn't have a dot or a comma after // that, then it's unambiguous. if (commapos >= 0) { if (pkgpath.indexOf(',', commapos + 1) >= 0 || pkgpath.indexOf('.', commapos + 1) >= 0) { System.err.println("Illegal package/class name " + pkgpath + " - skipping"); } else { pathToAdd = pkgpath; } // Otherwise it's ambiguous. Figure out what to do based on the // disambiguation rule set above. } else { switch (disambig) { case EXPLICITLY: System.err.println("Ambiguous package/class name " + pkgpath + " not allowed with 'explicitly' - skipping"); break; case APIS: // Since "apis" results in two separate roots being added, we add the // class root directly here, and don't worry about "exact" or not // because that only applies to packages. setToAddTo.add(toClassRoot(pkgpath)); pathToAdd = pkgpath + ","; break; case BYNAME: int dotpos = pkgpath.lastIndexOf('.'); if (Character.isUpperCase(pkgpath.charAt(dotpos + 1))) { pathToAdd = toClassRoot(pkgpath); } else { pathToAdd = pkgpath + ","; } break; case PACKAGES: pathToAdd = pkgpath + ","; break; case CLASSES: pathToAdd = toClassRoot(pkgpath); break; } } if (pathToAdd != null) { setToAddTo.add(pathToAdd); if (isExact) exact.add(pathToAdd); } // If it doesn't start with + or -, it's a path component. } else { path.add(args[i]); } } } if (path.isEmpty() || roots.isEmpty()) printUsage(); // We need to initialize the classpath to find classes in the correct // location. StringBuffer cp = new StringBuffer(); for (Iterator j = path.iterator(); j.hasNext(); ) { if (cp.length() > 0) cp.append(File.pathSeparatorChar); cp.append(j.next()); } setClasspath(cp.toString()); // Figure out what output writer to use. if (fileName == null) { if (zipIt) { System.err.println("Note: for correct operation of tools that read japi files, it is strongly"); System.err.println("recommended to use a filename ending in japi.gz for a compressed japi file."); out = new PrintWriter(new GZIPOutputStream(System.out)); } else { System.err.println("Note: for correct operation of tools that read japi files, it is strongly"); System.err.println("recommended to use a filename ending in japi for an uncompressed japi file."); out = new PrintWriter(System.out); } } else { // Japize will only create output to files ending in .japi (uncompressed) // or .japi.gz (compressed). It enforces this rule by adding .japi and .gz // to the specified filename if it doesn't already have them. If the user // specifies a .gz extension for uncompressed output, this is flagged as // an error - if it's really what they meant, they can specify x.gz.japi. if (fileName.endsWith(".gz")) { if (!zipIt) { System.err.println("Filename ending in .gz specified without zip output enabled."); System.err.println("Please either omit 'unzip' or specify a different filename (did you"); System.err.println("mean '" + fileName + ".japi'?)"); System.exit(1); } // Trim ".gz" off the end. It'll be re-added later, but ".japi" might // be inserted first. fileName = fileName.substring(0, fileName.length() - 3); } // Add ".japi" if it's not already there. if (!fileName.endsWith(".japi")) fileName += ".japi"; // Produce an output writer - compressed or not, as appropriate. if (zipIt) { out = new PrintWriter(new GZIPOutputStream(new BufferedOutputStream( new FileOutputStream(fileName + ".gz")))); } else { out = new PrintWriter(new BufferedWriter(new FileWriter(fileName))); } } if (lintFileName != null) { lintOut = new PrintWriter(new BufferedWriter(new FileWriter(lintFileName))); } // Now actually go and japize the classes. try { doJapize(); } finally { out.close(); if (lintOut != null) lintOut.close(); } } private static String[] scanForFileArgs(String[] args) throws IOException { ArrayList result = new ArrayList(); for (int i = 0; i < args.length; i++) { if (args[i].startsWith("@")) { FileReader file = new FileReader(args[i].substring(1)); try { LineNumberReader lines = new LineNumberReader(file); try { String line; while ((line = lines.readLine()) != null) { result.add(line); } } finally { lines.close(); } } finally { file.close(); } } else { result.add(args[i]); } } return (String[]) result.toArray(args); } private static String toClassRoot(String pkgpath) { StringBuffer sb = new StringBuffer(pkgpath); int dotpos = pkgpath.lastIndexOf('.'); if (dotpos >= 0) { sb.setCharAt(dotpos, ','); } else { sb.insert(0, ','); } return sb.toString(); } private static void progress(char ch) { System.err.print(ch); System.err.flush(); } private static void progress(String str) { System.err.println(); System.err.print(str); System.err.flush(); } // See design/japi-spec.txt for why this ends in a comma rather than the usual period. private static final String J_LANG = "java.lang,"; private static final String J_L_OBJECT = J_LANG + "Object"; private static ClassWrapper jlObjectWrapper; private static HashSet objCalls = new HashSet(); private static void doJapize() throws NoSuchMethodException, IllegalAccessException, IOException, ClassNotFoundException { // Print the header identifier. The syntax is "%%japi ver anything". // The "anything" is currently used for name/value pairs indicating the // creation date and creation tool. out.print("%%japi 0.9.7 creator=japize date=" + new SimpleDateFormat("yyyy/MM/dd_hh:mm:ss_z").format(new Date())); if (!serialExclusions.isEmpty()) { out.print(" noserial="); boolean first = true; for (Iterator i = serialExclusions.iterator(); i.hasNext(); ) { if (!first) out.print(";"); first = false; out.print(i.next()); } if (serialRoots.size() > 1) { out.print(" serial="); first = true; for (Iterator i = serialRoots.iterator(); i.hasNext(); ) { String root = (String) i.next(); if (!",".equals(root)) { if (!first) out.print(";"); first = false; out.print(root); } } } } out.println(); // Identify whether java.lang,Object fits into our list of things to // process. If it does, process it first, then add it to the list of // things to avoid (and remove it from the list of roots if it appears // there). if (checkIncluded(J_L_OBJECT)) { processClass(J_L_OBJECT); if (roots.contains(J_L_OBJECT)) roots.remove(J_L_OBJECT); exclusions.add(J_L_OBJECT); } // Then do the same thing with java.lang as a whole. SortedSet langRoots = roots.subSet(J_LANG, endPackageRoot(J_LANG)); if (checkIncluded(J_LANG)) { processPackage(J_LANG); exclusions.add(J_LANG); // Even if java.lang isn't included, java.lang.something might be... } else { processRootSet(langRoots); } // Remove all roots that are subpackages of java.lang. for (Iterator i = langRoots.iterator(); i.hasNext(); ) { i.next(); i.remove(); } jlObjectWrapper = getClassWrapper(J_L_OBJECT.replace(',', '.')); CallWrapper[] calls = jlObjectWrapper.getCalls(); for (int i = 0; i < calls.length; i++) { if (!"".equals(calls[i].getName())) objCalls.add(getObjComparableString(calls[i])); } // Now process all the roots that are left. processRootSet(roots); progress(""); } private static String packageRootTweak(String packageRoot, String tweak) { if (!packageRoot.endsWith(",")) throw new RuntimeException("packageRootTweak must be passed a package root ending in comma, not " + packageRoot); return packageRoot.substring(0, packageRoot.length() - 1) + tweak; } private static String endPackageRoot(String packageRoot) { return packageRootTweak(packageRoot, "/"); } private static void processRootSet(SortedSet rootSet) throws NoSuchMethodException, IllegalAccessException, ClassNotFoundException, IOException { // Process all roots in alphabetical order (note that the ordering is // implied by the use of a SortedSet). String skipping = null; for (Iterator i = rootSet.iterator(); i.hasNext(); ) { String root = (String) i.next(); if (skipping != null) { if (root.compareTo(skipping) < 0) continue; skipping = null; } if (root.indexOf(',') < root.length() - 1) { processClass(root); } else { processPackage(root); skipping = endPackageRoot(root); } } } private static void lintPrint(String s) { if (lintOut != null) lintOut.println(s); } //- Sort all "plus" items - these will be our "roots". //- Identify whether java.lang.Object falls within the scope of things to process. //If it does, process it as a class root and then add "-java.lang,Object" to //the list of exclusions. //- Iteratively process each root in order. After processing each root, skip any //following roots that lie between "root" and "root/". Since slash sorts after //comma and period but before alphanumerics, this will exclude any subpackages //and classes but not anything like a.b.CD. //- "Process" for a package root is a recursive function defined as follows: //- Scan all zips and directories for (a) classes in this package directly, and //(b) immediate subpackages of this package. Store everything that is found. //- Sort and then iterate over the items found in (a): //- Skip the class if there is an exclusion ("-" form) for this class //specified on the commandline. //- Otherwise Japize the class. //- Sort and then iterate over the items found in (b): //- If there is an exclusion for this subpackage found on the commandline, //skip it, but also do the following: //- Using SortedSet.subSet(), identify if there are any global roots that //lie between "excludedpkg" and "excludedpkg/". If there are, process //those in order using the appropriate process method for the type. //- If the package is not excluded, process it recursively using this //process method. // Process an individual class, by Japizing it. // FIXME: We should scan all zips and directories for this class, and only // Japize it if it's found. Optimization: on the first scan through, compare // all classes to all class roots and remove class roots that aren't found. // Also set a flag to indicate that this has been done - then you can skip // the scan for all subsequent class roots. static void processClass(String cls) throws NoSuchMethodException, IllegalAccessException, ClassNotFoundException { progress("Processing class " + cls + ":"); japizeClass(cls); } static void processPackage(String pkg) throws NoSuchMethodException, IllegalAccessException, ClassNotFoundException, IOException { progress("Processing package " + pkg + ":"); SortedSet classes = new TreeSet(); SortedSet subpkgs = new TreeSet(); // Scan the paths for classes and subpackages. Store everything in // classes and subpkgs. for (Iterator i = path.iterator(); i.hasNext(); ) { String pathElem = (String) i.next(); scanForPackage(pathElem, pkg, classes, subpkgs); } // Iterate over the classes found, and Japize each in turn, unless they are // explicitly excluded. for (Iterator i = classes.iterator(); i.hasNext(); ) { String cls = (String) i.next(); if (!exclusions.contains(cls)) japizeClass(cls); } // For packages included with '=' instead of '+', we only include subpackages // if they're themselves roots. if (exact.contains(pkg)) { // The '/' character sorts after '.' and ',', but before any // alphanumerics, so it covers a.b.c.d and a.b.c,D but not a.b.cd. The // character ' ' comes before all of these. processRootSet(roots.subSet(packageRootTweak(pkg, ", "), endPackageRoot(pkg))); // Otherwise descend recursively into subpackages. } else { // Iterate over the packages found, and process each in turn, unless they // are explicitly excluded. If they *are* explicitly excluded, check for and // process any roots that lie within the excluded package. for (Iterator i = subpkgs.iterator(); i.hasNext(); ) { String subpkg = (String) i.next(); if (!exclusions.contains(subpkg)) { processPackage(subpkg); } else { // Identify any roots that lie within the excluded package and process // them. The '/' character sorts after '.' and ',', but before any // alphanumerics, so it covers a.b.c.d and a.b.c,D but not a.b.cd. processRootSet(roots.subSet(subpkg, endPackageRoot(subpkg))); } } } } static void scanForPackage(String pathElem, String pkg, SortedSet classes, SortedSet subpkgs) throws IOException { if (new File(pathElem).isDirectory()) { scanDirForPackage(pathElem, pkg, classes, subpkgs); } else { scanZipForPackage(pathElem, pkg, classes, subpkgs); } progress('='); } /** * Process a directory as entered on the command line (ie, a root of the * class hierarchy - the same thing that would appear in a Classpath). * * @param pathElem The name of the directory to process. * @param pkg The package to scan for. * @param classes A set to add classes found to. * @param subpkgs A set to add subpackages found to. */ static void scanDirForPackage(String pathElem, String pkg, SortedSet classes, SortedSet subpkgs) throws IOException { // Replace dot by slash and remove the trailing comma. It's the caller's // responsibility to ensure that the last character is a comma. pkg = pkg.substring(0, pkg.length() - 1); String pkgf = pkg.replace('.', '/'); // If there is a directory of the appropriate name, recurse over it. File dir = new File(pathElem, pkgf); // Iterate over the files and directories within this directory. String[] entries = dir.list(); if (entries != null) { for (int i = 0; i < entries.length; i++) { File f2 = new File(dir, entries[i]); // If the entry is another directory, add the package associated with // it to the set of subpackages. // "-" entry for it. if (f2.isDirectory()) { subpkgs.add(pkg + '.' + entries[i] + ','); // If the entry is a file ending with ".class", add the class name to // the set of classes. } else if (entries[i].endsWith(".class")) { classes.add(pkg + ',' + entries[i].substring(0, entries[i].length() - 6)); } } } } /** * Process a zipfile as entered on the command line (ie, a root of the * class hierarchy - the same thing that would appear in a Classpath). * * @param pathElem The name of the zipfile to process. * @param pkg The package to scan for. * @param classes A set to add classes found to. * @param subpkgs A set to add subpackages found to. */ static void scanZipForPackage(String pathElem, String pkg, SortedSet classes, SortedSet subpkgs) throws IOException { // Replace dot by slash and remove the trailing comma. It's the caller's // responsibility to ensure that the last character is a comma. pkg = pkg.substring(0, pkg.length() - 1); String pkgf = pkg.replace('.', '/') + '/'; // Iterate over all the entries in the zipfile. ZipFile z = new ZipFile(pathElem); Enumeration ents = z.entries(); while (ents.hasMoreElements()) { String ze = ((ZipEntry)ents.nextElement()).getName(); // If the entry is a class file and located in the package we are looking // for, process it. if (ze.startsWith(pkgf) && ze.endsWith(".class")) { // Trim off the package bit that we already know and the .class suffix. ze = ze.substring(pkgf.length(), ze.length() - 6); // If it's directly in the package we're processing, add it to classes. // If it's in a subpackage, add the top-level subpackage to subpkgs. if (ze.indexOf('/') >= 0) { subpkgs.add(pkg + '.' + ze.substring(0, ze.indexOf('/')) + ','); } else { classes.add(pkg + ',' + ze); } } } } /** * Print a usage message. */ private static void printUsage() { System.err.println("Usage: japize [unzip] [as ] [lint ] apis | ... +|- ..."); System.err.println("At least one +pkg is required. 'name' will have .japi and/or .gz"); System.err.println("appended if appropriate."); System.err.println("The word 'apis' can be replaced by 'explicitly', 'byname', 'packages' or"); System.err.println("'classes'. These values indicate whether something of the form a.b.C should"); System.err.println("be treated as a class or a package. Use 'a.b,C' or 'a.b.c,' to be explicit."); System.exit(1); } /** * Construct a String consisting of every super-interface of a class * separated by "*". * * @param c The class to process. * @param s Initially "" should be passed; during recursion the string * produced so far is passed. This is used to ensure the same interface * does not appear twice in the string. * @return The name of every super-interface of c, separated by "*" and * with a leading "*". */ public static String mkIfaceString(ClassWrapper c, String s) { return mkIfaceString(c, s, null, c); } /** * Construct a String consisting of every super-interface of a class * separated by "*". * * @param c The class to process. * @param s Initially "" should be passed; during recursion the string * produced so far is passed. This is used to ensure the same interface * does not appear twice in the string. * @param ctype If non-null, all interfaces will first be bound against * this type before being displayed. * @param wrapper The wrapper to verify type parameters against. * @return The name of every super-interface of c, separated by "*" and * with a leading "*". */ public static String mkIfaceString(ClassWrapper c, String s, ClassType ctype, GenericWrapper wrapper) { // First iterate over the class's direct superinterfaces. ClassType[] ifaces = c.getInterfaces(); for (int i = 0; i < ifaces.length; i++) { // Bind the interface against ctype, if supplied. ClassType iface = ifaces[i]; if (ctype != null) iface = (ClassType) iface.bind(ctype); // If the string does not already contain the interface, and the // interface is public/protected, then add it to the string and // also process *its* superinterfaces, recursively. String repr = iface.getJavaRepr(wrapper); if ((s + "*").indexOf("*" + repr + "*") < 0) { int mods = iface.getWrapper().getModifiers(); if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) { s += "*" + repr; } s = mkIfaceString(iface.getWrapper(), s, iface, wrapper); } } // Finally, recursively process the class's superclass, if it has one. ClassType sup = c.getSuperclass(); if (sup != null) { if (ctype != null) sup = (ClassType) sup.bind(ctype); s = mkIfaceString(sup.getWrapper(), s, sup, wrapper); } return s; } /** * Write out API information for a given class. Nothing will be written if * the class is not public/protected. * * @param n The name of the class to process. * @return true if the class was public/protected, false if not. */ public static boolean japizeClass(String n) throws NoSuchMethodException, IllegalAccessException { try { // De-mangle the class name. if (n.charAt(0) == ',') n = n.substring(1); n = n.replace(',', '.'); // Get a ClassWrapper to work on. ClassWrapper c = getClassWrapper(n); // Load the class and check its accessibility. int mods = c.getModifiers(); if (!Modifier.isPublic(mods) && !Modifier.isProtected(mods)) { progress('-'); return false; } // Construct the basic strings that will be used in the output. String entry = toClassRoot(c.getName()) + "!"; String classEntry = entry; String type; if (c.isEnum()) { type = "enum"; } else if (c.isAnnotation()) { type = "annotation"; } else if (c.isInterface()) { type = "interface"; } else { type = "class"; } type += getTypeParamStr(c); if (c.isInterface()) { mods |= Modifier.ABSTRACT; // Interfaces are abstract by definition, } else { // Classes that happen to be Serializable get their SerialVersionUID // output as well. The separation by the '#' character from the rest // of the type string has mnemonic value for Brits, as the SVUID is a // special sort of 'hash' of the class. if (c.isSerializable() && !c.isEnum() && serialIncluded(c)) { Long svuid = c.getSerialVersionUID(); if (svuid == null) lintPrint(c.getName() + " has a blank final serialVersionUID"); type += "#" + svuid; } } // Iterate over the class's superclasses adding them to its "type" name, // skipping any superclasses that are not public/protected. int smods = mods; ClassType supt = c.getSuperclass(); while (supt != null) { ClassWrapper sup = supt.getWrapper(); smods = sup.getModifiers(); if (!Modifier.isPublic(smods) && !Modifier.isProtected(smods)) { lintPrint(c.getName() + " has non-public class " + sup.getName() + " among its superclasses"); progress('^'); } else { type += ":" + supt.getJavaRepr(c); } if (sup.getSuperclass() == null) { supt = null; } else { supt = (ClassType) sup.getSuperclass().bind(supt); } } type += mkIfaceString(c, ""); // Skip things that aren't entirely visible as defined below. if (!isEntirelyVisible(c)) return false; // Print out the japi entry for the class itself. printEntry(entry, type, mods, c.isDeprecated(), false, false); // Get the class's members. BoundMemberSet members = getFieldsAndCalls(c, null); BoundField[] fields = members.getFields(); BoundCall[] calls = members.getCalls(); // Iterate over the fields in the class. for (int i = 0; i < fields.length; i++) { // Get the modifiers and type of the field. mods = fields[i].getModifiers(); // Fields of interfaces are *always* public, static and final, although // wrapper implementations are inconsistent about telling us this. if (fields[i].getDeclaringClass().isInterface()) { mods |= Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC; } type = fields[i].getType().getTypeSig(c); if (fields[i].getName().equals("serialVersionUID") && fields[i].getDeclaringClass() == c) { if (c.isInterface()) { lintPrint("Useless serialVersionUID field in interface " + c.getName()); } else if (!Modifier.isStatic(fields[i].getModifiers()) || !Modifier.isFinal(fields[i].getModifiers())) { lintPrint("serialVersionUID field in " + c.getName() + " not 'static final'"); } else if (!c.isSerializable()) { lintPrint("serialVersionUID field in non-serializable class " + c.getName()); } } // If the field is nonfinal and either public or static, the class it's declared // in can affect the behavior of code that uses it. However, we don't want to // expose nonpublic classes so we find the nearest public/protected class instead. // FIXME: difficult outstanding bug here: A and B both extend (nonpublic) C, // C has a static field f. A.f and B.f are the same field but with this // algorithm you can't tell that. What to do, what to do? if (!Modifier.isFinal(fields[i].getModifiers()) && (Modifier.isPublic(fields[i].getModifiers()) || Modifier.isStatic(fields[i].getModifiers()))) { ClassWrapper wrapper = c; String declaringName = wrapper.getName(); while (!wrapper.equals(fields[i].getDeclaringClass())) { wrapper = wrapper.getSuperclass().getWrapper(); if (Modifier.isPublic(wrapper.getModifiers()) || Modifier.isProtected(wrapper.getModifiers())) { declaringName = wrapper.getName(); } } type += '=' + declaringName; } // A static, final field is a primitive constant if it is initialized to // a compile-time constant. if (fields[i].isPrimitiveConstant()) { Object o = fields[i].getPrimitiveValue(); // Character values get int-ized to keep the output nice and 7bit. if (o instanceof Character) { type += ":" + (int)((Character)o).charValue(); // String values get newlines and backslashes escaped to stop them from // going onto a second line. } else if (o instanceof String) { String val = (String)o; StringBuffer sb = new StringBuffer('\"'); int p = 0, q = 0; while (q >= 0) { q = val.indexOf("\n", p); int r = val.indexOf("\\", p); if (r >= 0 && (r < q || q < 0)) q = r; if (q >= 0) { sb.append(val.substring(p, q)); sb.append('\\'); if (val.charAt(q) == '\\') sb.append('\\'); else sb.append('n'); p = ++q; } } sb.append(val.substring(p)); type += ":" + sb; // Floats and doubles get their toRaw*Bits() value printed as well as // their actual value. } else if (o instanceof Float) { type += ':' + o.toString() + '/' + Integer.toHexString(Float.floatToRawIntBits( ((Float) o).floatValue())); } else if (o instanceof Double) { type += ':' + o.toString() + '/' + Long.toHexString(Double.doubleToRawLongBits( ((Double) o).doubleValue())); // Other types just get output. } else { type += ":" + o; } } // Skip things that aren't entirely visible as defined below. if (!isEntirelyVisible(fields[i])) continue; // Output the japi entry for the field. printEntry(classEntry + "#" + fields[i].getName(), type, mods, fields[i].isDeprecated() || c.isDeprecated(), fields[i].isEnumField(), false); } // Iterate over the methods and constructors in the class. for (int i = 0; i < calls.length; i++) { // Skip calls called and . Constructors are handled // with an empty method name, and class initializers are never part of // the public API. // This code is probably dead since I'm pretty sure ClassFile knows to // give us these in the right form. if ("".equals(calls[i].getName()) || "".equals(calls[i].getName())) { continue; } // Skip methods in interfaces that are also defined identically in // Object. Specifically, it needs to be defined in Object with // *exactly* the same parameter types, return types *and* thrown // exceptions (because it *is* legal for an interface to specify, // say, "Object clone();" and thereby specify that implementors must // not throw CloneNotSupportedException from their clone method. // Surprisingly, Cloneable doesn't do this...) if (c.isInterface() && objCalls.contains(getObjComparableString(calls[i]))) { progress(';'); continue; } // Skip things that aren't entirely visible as defined below. if (!isEntirelyVisible(calls[i])) continue; // Construct the name of the method, of the form Class!method(params). entry = classEntry + calls[i].getName() + "("; Type[] params = calls[i].getParameterTypes(); String comma = ""; for (int j = 0; j < params.length; j++) { entry += comma + params[j].getTypeSig(calls[i]); comma = ","; } entry += ")"; if (!calls[i].isVisible14()) { entry += "+"; } else if (!calls[i].isVisible15()) { entry += "-"; // Find the next call that would be included. If it has the exact same // nonGenericSig, we need a double-minus instead of a // single one. if (i < calls.length) { for (int j = i + 1; j < calls.length; j++) { if (!calls[j].getNonGenericSig().equals(calls[i].getNonGenericSig())) break; // All the reasons we might skip an item above, we skip it here as well. if ("".equals(calls[j].getName()) || "".equals(calls[j].getName())) { continue; } if (c.isInterface() && objCalls.contains(getObjComparableString(calls[j]))) { continue; } if (!isEntirelyVisible(calls[j])) continue; entry += "-"; break; } } } // Construct the "type" field, of the form returnType*exception*except2... type = ""; // ... but if it's a generic method it gets the type parameters first type += getTypeParamStr(calls[i]); Type rtnType = calls[i].getReturnType(); type += (rtnType == null) ? "constructor" : rtnType.getTypeSig(calls[i]); boolean isStub = false; NonArrayRefType[] excps = calls[i].getExceptionTypes(); for (int j = 0; j < excps.length; j++) { if (includeException(excps, j)) type += "*" + excps[j].getJavaRepr(calls[i]); if (excps[j] instanceof ClassType && ((ClassType) excps[j]).getName().endsWith(".NotImplementedException")) { isStub = true; } } // Get the modifiers for this method. Methods of interfaces are // by definition public and abstract, although wrapper implementations // are inconsistent about telling us this. int mmods = calls[i].getModifiers(); if (c.isInterface()) { mmods |= Modifier.ABSTRACT | Modifier.PUBLIC; } // Methods of final classes are by definition final if (Modifier.isFinal(c.getModifiers())) { mmods |= Modifier.FINAL; } // Constructors are never final. The verifier should enforce this // so this should always be a no-op, except for when the line above // set it. if ("".equals(calls[i].getName())) { mmods &= ~Modifier.FINAL; } // Print the japi entry for the method. printEntry(entry, type, mmods, calls[i].isDeprecated() || c.isDeprecated(), false, isStub); } // Return true because we did parse this class. progress('+'); return true; } catch (ClassNotFoundException e) { System.err.println("\nFailed to Japize " + n + ": " + e); e.printStackTrace(); } catch (RuntimeException e) { System.err.println("\nFailed to Japize " + n + ": " + e); e.printStackTrace(); } return false; } private static String getTypeParamStr(GenericWrapper wrapper) { TypeParam[] tparams = wrapper.getTypeParams(); String type = ""; if (tparams != null) { type += "<"; for (int i = 0; i < tparams.length; i++) { if (i > 0) type += ","; for (int j = 0; j < tparams[i].getBounds().length; j++) { if (j > 0) type += "&"; type += tparams[i].getBounds()[j].getTypeSig(wrapper); } } type += ">"; } return type; } /** * Load all the fields and calls for a particular class, taking inheritance into account. */ private static BoundMemberSet getFieldsAndCalls(ClassWrapper outer, ClassType ctype) { ClassWrapper c = (ctype != null ? ctype.getWrapper() : outer); BoundMemberSet members = new BoundMemberSet(); ClassType[] ifaces = c.getInterfaces(); for (int i = 0; i < ifaces.length; i++) { ClassType iface = ifaces[i]; if (ctype != null) iface = (ClassType) iface.bind(ctype); members.bindAndAddAll(getFieldsAndCalls(outer, iface), ctype); } ClassType sup = c.getSuperclass(); if (sup != null) { if (ctype != null) sup = (ClassType) sup.bind(ctype); members.bindAndAddAll(getFieldsAndCalls(outer, sup), ctype); } FieldWrapper[] fields = c.getFields(); for (int i = 0; i < fields.length; i++) { members.bindAndAdd(new BoundField(fields[i]), ctype); } CallWrapper[] calls = c.getCalls(); for (int i = 0; i < calls.length; i++) { BoundCall call = new BoundCall(calls[i], outer); if (ctype == null || call.isInheritable()) { // JDK15: handle bridge methods (the ACC_VOLATILE bit corresponds to the ACC_BRIDGE bit) // These get immediately bind14()d because they are only visible in the 1.4 view of the universe if (Modifier.isVolatile(calls[i].getModifiers())) call = call.bind14(); members.bindAndAdd(call, ctype); } } return members; } /** * Get a string containing the name, parameter types and thrown exceptions * for a particular method. Returns null on a constructor. Designed to * allow comparing interface methods against Object methods. NOTE that this will * potentially not work correctly if generic methods are ever added to Object * itself (because of "@0" etc meaning different things). Oh, how I hope that * never happens... */ private static String getObjComparableString(CallWrapper call) throws ClassNotFoundException { if (call.getName().equals("")) return null; String s = call.getName() + "("; Type[] params = call.getParameterTypes(); for (int i = 0; i < params.length; i++) { if (i > 0) s += ","; s += params[i].getTypeSig(call); } s += ")" + call.getReturnType().getTypeSig(call); NonArrayRefType[] excps = call.getExceptionTypes(); TreeSet exstrs = new TreeSet(); for (int i = 0; i < excps.length; i++) { if (includeException(excps, i)) exstrs.add(excps[i].getJavaRepr(call)); } for (Iterator i = exstrs.iterator(); i.hasNext(); ) { s += "*" + i.next(); } return s; } /** * Print a japi file entry. The format of a japi file entry is space-separated * with 3 fields - the name of the "thing", the modifiers, and the type * (which generally includes more information than *just* the type; see the * implementation of japizeClass for what actually gets passed in here). * The modifiers are represented as a six-letter string consisting of 1 * character each for the accessibility ([P]ublic or [p]rotected), the * abstractness ([a]bstract or [c]oncrete), the staticness ([s]tatic or * [i]nstance), the finalness ([f]inal or [n]onfinal, or [e]num field), * the deprecatedness ([d]eprecated or [u]ndeprecated) and whether the * method is a [S]tub or [r]eal. * * @param thing The name of the "thing" (eg class, field, etc) to print. * @param type The contents of the "type" field. * @param mods The modifiers of the thing, as returned by {Class, Field, * Method, Constructor}.getModifiers(). * @param deprecated Whether the thing is deprecated. * @param enumField Whether the field is an enum field. * @param stub Whether the method is a stub. */ public static void printEntry(String thing, String type, int mods, boolean deprecated, boolean enumField, boolean stub) { if (!Modifier.isPublic(mods) && !Modifier.isProtected(mods)) return; if (thing.startsWith("java.lang,Object!")) out.print('+'); if (thing.startsWith("java.lang,") || thing.startsWith("java.lang.")) out.print('+'); out.print(thing); out.print(' '); out.print(Modifier.isPublic(mods) ? 'P' : 'p'); out.print(Modifier.isAbstract(mods) ? 'a' : 'c'); out.print(Modifier.isStatic(mods) ? 's' : 'i'); out.print(enumField ? 'e' : Modifier.isFinal(mods) ? 'f' : 'n'); out.print(deprecated ? 'd' : 'u'); out.print(stub ? 'S' : 'r'); out.print(' '); out.println(type); } /** * Trivial utility method to get the wrapper for a superclass or null if there * isn't one. */ static ClassWrapper getWrapper(ClassType t) { return t == null ? null : t.getWrapper(); } /** * Determine whether the type parameter bounds of an item are entirely visible. */ static boolean paramsEntirelyVisible(GenericWrapper wrapper) { // Return true for now because otherwise you tend to get into infinite loops with, what else, // Enum>... return true; // TypeParam[] params = TypeParam.getAllTypeParams(wrapper); // if (params != null) { // for (int i = 0; i < params.length; i++) { // if (!isEntirelyVisible(params[i])) return false; // } // } // return true; } /** * Determine whether a class is entirely visible. If it's not then it should be skipped. * A class is entirely visible if it's public or protected, its containing * class, if any, is entirely visible, and all the bounds of its * type parameters are entirely visible. */ static boolean isEntirelyVisible(ClassWrapper cls) { if (!Modifier.isPublic(cls.getModifiers()) && !Modifier.isProtected(cls.getModifiers())) { return false; } ClassWrapper containing = (ClassWrapper) cls.getContainingWrapper(); if (containing != null && !isEntirelyVisible(containing)) return false; return paramsEntirelyVisible(cls); } /** * Determine whether a type is entirely visible. If it's not then it should be skipped. * A type is entirely visible if it's a class that's entirely visible and all of its * type arguments are entirely visible, or it's a primitive type, or it's an * array type whose element type is entirely visible, or it's a wildcard type or type * parameter whose bounds are entirely visible. */ static boolean isEntirelyVisible(Type t) { if (t == null) { return true; } else if (t instanceof PrimitiveType) { return true; } else if (t instanceof ArrayType) { return isEntirelyVisible(((ArrayType) t).getElementType()); } else if (t instanceof ClassType) { if (!isEntirelyVisible(((ClassType) t).getWrapper())) return false; RefType[] args = ((ClassType) t).getTypeArguments(); if (args != null) { for (int i = 0; i < args.length; i++) { if (!isEntirelyVisible(args[i])) return false; } } return true; } else if (t instanceof TypeParam) { // return true for now because otherwise we tend to get into an infinite loop on, what else, // Enum> // NonArrayRefType[] bounds = ((TypeParam) t).getBounds(); // for (int i = 0; i < bounds.length; i++) { // if (!isEntirelyVisible(bounds[i])) return false; // } return true; } else if (t instanceof WildcardType) { RefType upper = ((WildcardType) t).getUpperBound(); RefType lower = ((WildcardType) t).getLowerBound(); if (lower != null && !isEntirelyVisible(lower)) return false; return isEntirelyVisible(upper); } else { throw new RuntimeException("Unknown kind of Type " + t.getClass()); } } /** * Determine whether a field is entirely visible. If it's not then it should be skipped. * A field is entirely visible if it is itself public or protected and its type is * entirely visible. */ static boolean isEntirelyVisible(FieldWrapper field) { if (!Modifier.isPublic(field.getModifiers()) && !Modifier.isProtected(field.getModifiers())) { return false; } if (!isEntirelyVisible(field.getType())) { lintPrint("field " + field.getDeclaringClass().getName() + "." + field.getName() + " has non-public type " + field.getType().getTypeSig(field.getDeclaringClass())); return false; } return true; } /** * Determine whether a method or constructor is entirely visible. If it's not then it * should be skipped. * A call is entirely visible if its return type is entirely visible, all of its * parameter types are entirely visible, all of its thrown exception types are * entirely visible, and all the bounds of its type parameters are entirely visible. */ static boolean isEntirelyVisible(CallWrapper call) { if (!Modifier.isPublic(call.getModifiers()) && !Modifier.isProtected(call.getModifiers())) { return false; } if (!paramsEntirelyVisible(call)) return false; boolean result = true; if (!isEntirelyVisible(call.getReturnType())) { result = false; lintPrint("method " + call.getDeclaringClass().getName() + "." + call.getName() + "() has non-public return type " + call.getReturnType().getTypeSig(call.getDeclaringClass())); } for (int i = 0; i < call.getParameterTypes().length; i++) { if (!isEntirelyVisible(call.getParameterTypes()[i])) { result = false; lintPrint("method " + call.getDeclaringClass().getName() + "." + call.getName() + "() has non-public type " + call.getParameterTypes()[i].getTypeSig(call.getDeclaringClass()) + " among its parameters"); } } // For now don't worry about exception types. Later we may handle them a different way // (eg by rendering each one as its closest accessible superclass). But we'll still // print the error. for (int i = 0; i < call.getExceptionTypes().length; i++) { if (!isEntirelyVisible(call.getExceptionTypes()[i])) { lintPrint("method " + call.getDeclaringClass().getName() + "." + call.getName() + "() throws non-public exception " + call.getExceptionTypes()[i].getTypeSig(call.getDeclaringClass())); } } return result; } /** * Check to see if an exception should be included in the list of exceptions. * Subclasses of RuntimeException and Error should be omitted, as should * subclasses of other exceptions also thrown. */ static boolean includeException(NonArrayRefType[] excps, int index) throws ClassNotFoundException { boolean isSuper = false; ClassType excp; if (excps[index] instanceof ClassType) { excp = (ClassType) excps[index]; } else { TypeParam tp = (TypeParam) excps[index]; while (tp.getPrimaryConstraint() instanceof TypeParam) { tp = (TypeParam) tp.getPrimaryConstraint(); } excp = (ClassType) tp.getPrimaryConstraint(); } for (ClassWrapper supclass = excp.getWrapper(); supclass != null; supclass = getWrapper(supclass.getSuperclass())) { String supname = supclass.getName(); if ("java.lang.RuntimeException".equals(supname) || "java.lang.Error".equals(supname)) { return false; } if (isSuper) { for (int i = 0; i < excps.length; i++) { if (i != index && excps[i] instanceof ClassType && supname.equals(((ClassType) excps[i]).getName())) return false; } } isSuper = true; } return true; } /** * Check a class name against the global 'roots' and 'exclusions' sets * to see if it should be included. A class should be included if * it is inside a package that has a roots entry, and not inside a deeper * package that has an exclusions entry. * * @param cname the name of the class to check. * @return true if the class should be included, false if not. */ public static boolean checkIncluded(String cname) { return checkIncluded(cname, roots, exclusions); } public static boolean serialIncluded(String cname) { return checkIncluded(cname, serialRoots, serialExclusions); } public static boolean serialIncluded(ClassWrapper c) { if (!serialIncluded(toClassRoot(c.getName()))) return false; ClassType supt = c.getSuperclass(); if (supt != null && !serialIncluded(supt.getWrapper())) return false; return true; } public static boolean checkIncluded(String cname, Set roots, Set exclusions) { if (roots.contains(cname)) return true; if (exclusions.contains(cname)) return false; // Loop backwards over the "."s in the class's name. int i = cname.indexOf(','); while (i >= 0) { cname = cname.substring(0, i); String mangled = cname + ','; // Check whether there is an entry for the package name up to the ".". // If so we know what to do so we return the result; otherwise we // continue at the next ".". if (roots.contains(mangled)) return true; if (exclusions.contains(mangled)) return false; i = cname.lastIndexOf('.'); } // If we ran out of dots before finding a match, we need to check the root // package. return roots.contains(","); } /** * Set the classpath for the appropriate implementation we are using. * * @param cp The classpath to set. */ public static void setClasspath(String cp) throws IOException { ClassFile.setClasspath(cp); } /** * Construct the appropriate type of ClassWrapper object for the processing we * are doing. * * @param className The fully-qualified name of the class to get a wrapper * for. * @return A ClassWrapper object for the specified class. */ public static ClassWrapper getClassWrapper(String className) throws ClassNotFoundException { return ClassFile.forName(className); } /** * Encode a string. The encoding is: * "\" translates to "\\" * newline translates to "\n" * all other characters except for 0-9a-zA-Z translate to \ uNNNN where N is the unicode value. */ private static String jencode(String str) { StringBuffer sb = new StringBuffer(str.length()); for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); if (ch == '\\') { sb.append("\\\\"); } else if (ch == '\n') { sb.append("\\n"); } else if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { sb.append(ch); } else { sb.append("\\u" + to4charHexString(ch)); } } return sb.toString(); } private static String to4charHexString(char ch) { String result = Integer.toHexString((int) ch); if (result.length() > 4) throw new RuntimeException("toHexString gave a longer than 4-char output"); while (result.length() < 4) result = "0" + result; return result; } } japitools-0.9.7/src/net/wuffies/japi/NonArrayRefType.java0000644000175000017500000000223510314136577023541 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public abstract class NonArrayRefType extends RefType { public abstract String getJavaRepr(GenericWrapper wrapper); } japitools-0.9.7/src/net/wuffies/japi/PrimitiveType.java0000644000175000017500000000517310315337652023325 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public class PrimitiveType extends Type { char code; private PrimitiveType(char code) { this.code = code; } public String getNonGenericTypeSig() { return "" + code; } public Type getNonGenericType() { return this; } public void resolveTypeParameters() { } public Type bind(ClassType t) { debugStart("Bind", "to " + t); debugEnd(); return this; } public static PrimitiveType fromSig(char c) { switch (c) { case 'V': return PrimitiveType.VOID; case 'Z': return PrimitiveType.BOOLEAN; case 'C': return PrimitiveType.CHAR; case 'B': return PrimitiveType.BYTE; case 'S': return PrimitiveType.SHORT; case 'I': return PrimitiveType.INT; case 'J': return PrimitiveType.LONG; case 'F': return PrimitiveType.FLOAT; case 'D': return PrimitiveType.DOUBLE; default: throw new RuntimeException("Illegal type: " + c); } } public String toStringImpl() { return "Primitive:" + code; } static final PrimitiveType VOID = new PrimitiveType('V'); static final PrimitiveType BOOLEAN = new PrimitiveType('Z'); static final PrimitiveType CHAR = new PrimitiveType('C'); static final PrimitiveType BYTE = new PrimitiveType('B'); static final PrimitiveType SHORT = new PrimitiveType('S'); static final PrimitiveType INT = new PrimitiveType('I'); static final PrimitiveType LONG = new PrimitiveType('J'); static final PrimitiveType FLOAT = new PrimitiveType('F'); static final PrimitiveType DOUBLE = new PrimitiveType('D'); } japitools-0.9.7/src/net/wuffies/japi/RefType.java0000644000175000017500000000212410313077663022063 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public abstract class RefType extends Type { } japitools-0.9.7/src/net/wuffies/japi/Type.java0000644000175000017500000001166110316617261021431 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public abstract class Type { private static final boolean DEBUG = false; /** * Return the type signature (as described in the japi file spec). An exception * is thrown if any type parameters appear that are not on g or one of its * containers. */ public String getTypeSig(GenericWrapper wrapper) { return getNonGenericTypeSig(); } /** * Return the 1.4-compatible non-generic type signature */ public abstract String getNonGenericTypeSig(); /** * Strip all generic information and return a plain old type. */ public abstract Type getNonGenericType(); /** * Used by ClassFile to resolve TypeParams */ public abstract void resolveTypeParameters(); public static Type resolveTypeParameter(Type t) { if (t == null) return null; if (t instanceof ClassFile.UnresolvedTypeParam) { return ((ClassFile.UnresolvedTypeParam)t).resolve(); } else { t.resolveTypeParameters(); return t; } } /** * Return a copy of this type with all type parameters on t bound to the value * of t's corresponding type argument. Type parameters on t that are unbound, * because t was specified as a raw type, will result in null. That opens up * the possibility that bind() will itself return null. If that's unacceptable, * consider using bindWithFallback() instead. */ public abstract Type bind(ClassType t); /** * Return the result of bind(t), except that in the case of an unbound type * parameter (where bind() would return null), return the result of binding the * primary constraint instead (which is a ClassType and cannot itself result in * null when bound). (The implementation in Type throws if bind() returns null; * this method is overridden in TypeParam which is the only subclass that * actually returns null from bind()). */ public Type bindWithFallback(ClassType t) { Type result = bind(t); if (result == null) throw new NullPointerException("bind() resulted in null, unhandled"); return result; } /** * Construct a type based on a non-generic signature string, eg Z, [[I, Ljava/lang/String;. */ public static Type fromNonGenericSig(String sig) { if (sig.length() == 1) return PrimitiveType.fromSig(sig.charAt(0)); switch (sig.charAt(0)) { case '[': return new ArrayType(fromNonGenericSig(sig.substring(1))); case 'L': return new ClassType(sig.substring(1, sig.length() - 1).replace('/', '.')); default: throw new RuntimeException("Illegal type: " + sig); } } // From here on down is debugging stuff. The toString method checks to see whether it's calling // itself more than 15 times nested, in which case there's probably a circular data structure. // That might be okay - class Enum> is an example - but you don't want a // StackOverflowException trying to toString() it. toString() also returns "" if debugging is // disabled. Subclasses must override toStringImpl instead of toString(). private static int nest; public final String toString() { // toString on these things is only used for debugging; shorting out the code otherwise // avoids running redundant code calculating the parameter to the debug method. if (!DEBUG) return ""; nest++; try { if (nest > 15) return "LOOOOP"; else return toStringImpl(); } finally { nest--; } } public String toStringImpl() { return super.toString(); } // You can wrap a method in "debugStart(...); try { ... } finally {debugEnd();}" to get // some debugging output if debugging is enabled. private static String indent = ""; protected void debugStart(String label, String msg) { if (DEBUG) { System.err.println(indent + label + ": " + this + " - " + msg); indent += "+ "; } } protected void debugEnd() { if (DEBUG) { indent = indent.substring(2); } } } japitools-0.9.7/src/net/wuffies/japi/TypeParam.java0000644000175000017500000001317410316617261022413 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; import java.lang.reflect.Modifier; // Note that a TypeParam *must* correspond to one of the entries in its // getAssociatedWrapper().getTypeParams() array. public class TypeParam extends NonArrayRefType { private GenericWrapper associatedWrapper; private String name; private NonArrayRefType[] bounds; public TypeParam(GenericWrapper associatedWrapper, String name, NonArrayRefType[] bounds) { if (bounds == null || bounds.length == 0) bounds = new NonArrayRefType[] {new ClassType("java.lang.Object")}; this.associatedWrapper = associatedWrapper; this.name = name; this.bounds = bounds; } public GenericWrapper getAssociatedWrapper() { return associatedWrapper; } public String getName() { return name; } public NonArrayRefType getPrimaryConstraint() { return bounds[0]; } public NonArrayRefType[] getBounds() { return bounds; } public int getIndex(GenericWrapper wrapper) { TypeParam[] params = getAllTypeParams(wrapper); if (params != null) { for (int i = 0; i < params.length; i++) { if (params[i] == this) return i; } } return -1; } public String getTypeSig(GenericWrapper wrapper) { int index = getIndex(wrapper); if (index < 0) throw new RuntimeException("Unbound type parameter " + this + " not associated with current wrapper " + wrapper); return "@" + index; } public String getJavaRepr(GenericWrapper wrapper) { return getTypeSig(wrapper); } public String getNonGenericTypeSig() { return getPrimaryConstraint().getNonGenericTypeSig(); } public Type getNonGenericType() { return getPrimaryConstraint().getNonGenericType(); } public void resolveTypeParameters() { for (int i = 0; i < bounds.length; i++) { if (bounds[i] instanceof ClassFile.UnresolvedTypeParam) { bounds[i] = ((ClassFile.UnresolvedTypeParam) bounds[i]).resolve(); } bounds[i].resolveTypeParameters(); } } private boolean binding = false; public Type bind(ClassType t) { debugStart("Bind", "to " + t); try { int index = getIndex(t.getWrapper()); if (index == -1) { return this; // SABFIXME: No idea why the following doesn't work - it should be better, but apparently it isn't. // Probably because in the case of Enum> primaryConstraint == Enum and you hit an infinite // loop. getNonGenericType() instead of bindWithFallback() might be better, or some loopbreaking code. // return new TypeParam(getAssociatedWrapper(), getName(), (NonArrayRefType) primaryConstraint.bindWithFallback(t)); // Obviously the line above also would need changing now that bounds is an array instead of a single primaryConstraint. } else if (t.getTypeArguments() == null) { return null; } else { return t.getTypeArguments()[index]; } } finally { debugEnd(); } } public Type bindWithFallback(ClassType t) { Type result = bind(t); return result != null ? result : getPrimaryConstraint().bindWithFallback(t); } public String toStringImpl() { String s = "TypeParam:" + associatedWrapper.toString() + "-" + name; for (int i = 0; i < bounds.length; i++) { s += "/" + bounds[i].toString(); } return s; } /** * Find all the type params of a particular GenericWrapper, including those inherited * from its containers. */ public static TypeParam[] getAllTypeParams(GenericWrapper wrapper) { int count = 0; GenericWrapper container = wrapper; while (container != null) { if (container.getTypeParams() != null) { count += container.getTypeParams().length; } container = getContainingWrapper(container); } if (count == 0) return null; TypeParam[] params = new TypeParam[count]; container = wrapper; int start = params.length; while (container != null) { TypeParam[] cparams = container.getTypeParams(); if (cparams != null) { start -= cparams.length; for (int i = 0; i < cparams.length; i++) { params[start + i] = cparams[i]; } } container = getContainingWrapper(container); } if (start != 0) throw new RuntimeException("Oops, internal error, didn't completely fill typeparams array"); return params; } // Static methods and static inner classes do not inherit their container's type params, but // nonstatic things do. private static GenericWrapper getContainingWrapper(GenericWrapper wrapper) { if (Modifier.isStatic(wrapper.getModifiers())) { return null; } else { return wrapper.getContainingWrapper(); } } } japitools-0.9.7/src/net/wuffies/japi/VarArrayType.java0000644000175000017500000000270410312316547023076 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public class VarArrayType extends ArrayType { public VarArrayType(Type elementType) { super(elementType); } public String getTypeSig(GenericWrapper wrapper) { return "." + getElementType().getTypeSig(wrapper); } public String toStringImpl() { return "VarArray:" + getElementType(); } public Type bind(ClassType t) { return new VarArrayType(getElementType().bindWithFallback(t)); } } japitools-0.9.7/src/net/wuffies/japi/WildcardType.java0000644000175000017500000000532610520727635023110 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004,2005 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public class WildcardType extends RefType { private RefType upperBound; private RefType lowerBound; public WildcardType() { this(null); } public WildcardType(RefType upperBound) { this(upperBound, null); } public WildcardType(RefType upperBound, RefType lowerBound) { if (upperBound == null) upperBound = new ClassType("java.lang.Object"); if (!(upperBound instanceof ClassType && "java.lang.Object".equals(((ClassType)upperBound).getName()))) { if (lowerBound != null) throw new RuntimeException("Upper and lower bounds cannot both be set"); } this.upperBound = upperBound; this.lowerBound = lowerBound; } public RefType getUpperBound() { return upperBound; } public RefType getLowerBound() { return lowerBound; } public String getTypeSig(GenericWrapper wrapper) { if (lowerBound == null) { return "{" + upperBound.getTypeSig(wrapper); } else { return "}" + lowerBound.getTypeSig(wrapper); } } public String getNonGenericTypeSig() { return upperBound.getNonGenericTypeSig(); } public Type getNonGenericType() { return upperBound.getNonGenericType(); } public void resolveTypeParameters() { upperBound = (RefType) resolveTypeParameter(upperBound); lowerBound = (RefType) resolveTypeParameter(lowerBound); } public Type bind(ClassType t) { debugStart("Bind", "to " + t); try { return new WildcardType((RefType) upperBound.bindWithFallback(t), lowerBound == null ? null : (RefType) lowerBound.bind(t)); } finally { debugEnd(); } } public String toStringImpl() { return "Wildcard:" + upperBound + "/" + lowerBound; } } japitools-0.9.7/src/net/wuffies/japi/Wrapper.java0000644000175000017500000000215610310622422022114 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2000,2002,2003,2004 Stuart Ballard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; public interface Wrapper { int getModifiers(); boolean isDeprecated(); } japitools-0.9.7/.cvsignore0000644000175000017500000000003610413255766015675 0ustar sballardsballard*.jar *.class net build share japitools-0.9.7/COPYING0000644000175000017500000004311007561562145014732 0ustar sballardsballard GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. japitools-0.9.7/Makefile0000644000175000017500000000042110310622422015312 0ustar sballardsballardall: jar classes: src/net/wuffies/japi/*.java cd src && jikes -d .. -classpath $(BOOT_CLASSPATH):$(CLASSPATH) net/wuffies/japi/*.java jar: classes jar -0cvf share/java/japitools.jar net/wuffies/japi/*.class tarball: jar cd .. && tar zcvf japitools.tar.gz japitools/ japitools-0.9.7/README0000644000175000017500000000265310142441626014553 0ustar sballardsballardThis is a temporary README file for the sake of getting *something* together fast. Full instructions can be found in the web/index.html file, or at http://www.kaffe.org/~stuart/japi/ You can unpack the tarball and use the files in the bin/ directory directly or via symlinks: they can find the java libraries, when necessary, by relative pathnames. The tarball comes with the java code precompiled so you don't need to build before use, but if you do any hacking, "make" in the root japi/ directory will rebuild the java code. There's no "make install" but if you copy everything from bin/ to $prefix/bin and everything in share/java to $prefix/share/java, everything will continue to work. Sorry, the build system is spartan! As of japitools 0.8.7 there is an alternative build system using ant, kindly provided by Brian Jones. Since I (Stuart) know nothing about ant, I'll assume that people who do know ant will know exactly what to do with the build.xml file he provided :) Japitools assumes that perl is /usr/bin/perl, "gzip" is in your path, and your preferred java runtime is "java" in your path. gzip is not required if you work with uncompressed japi files (see the "unzip" option in the japize documentation). Japize requires a Java runtime with some JDK1.2 features: mostly the Collections classes. Success has been reported with Free runtimes including Kaffe and gcj/gij. Fixes are welcomed, and I'll be improving the docs over time. japitools-0.9.7/build.xml0000644000175000017500000001542510517000764015515 0ustar sballardsballard japitools-0.9.7/design/0000755000175000017500000000000010526243211015132 5ustar sballardsballardjapitools-0.9.7/design/ANNOUNCEMENT0000644000175000017500000000512407561760756017000 0ustar sballardsballardJAPITOOLS 0.9 RELEASED 2002/11/05 ---------------------- I'm proud to announce the first japitools release intended for wide public consumption. japitools is a set of tools for testing compatibility between different versions of a Java API. It can be used both for verifying whether an independent implementation of an API is correct and complete, and for ensuring that binary compatibility is maintained between successive versions of the same API. In particular, japitools can be used to test the conformance of independent implementations of the Java platform itself. Features of this release: * japize tool reads Java class files and dumps a machine-readable representation of the API to a "japi" file. * japicompat tool compares two japi files for binary backwards compatibility. * japilist tool provides human-readable summaries of the contents of a japi file. * serialize and serialcompat tools test serialization compatibility. * japifix tool updates japi files made by previous releases, and in some cases can correct malformed files. * Comparisions cover every requirement for binary compatibility as defined in the JLS, and more, but exclude many differences that are known to be insignificant, to keep the signal-to-noise ratio high. * All algorithms are tuned for memory usage and performance, to allow running on machines with limited resources or as unsupervised (eg nightly) jobs. * japi file format is fully specified, allowing interoperable tools to be created. For more information on japitools, including a change history, visit the project website at http://rainbow.netreach.net/~sballard/japi . japitools 0.9 can be downloaded from http://rainbow.netreach.net/~sballard/japi/japitools-0.9.tar.gz . Any questions or comments should be directed to sballard@netreach.net. Particular thanks for their help in making this release as good as it can be go to Brian Jones for providing the ant build system, the serialize and serialcompat tools, and lots of wrapper scripts, along with some invaluable testing and setting up nightly japicompat tests for GNU Classpath CVS; and to Dalibor Topic for working towards a similar arrangement for Kaffe, and being a patient and committed tester despite several brown-paper-bag development releases. Thanks also to everyone else who's provided feedback. Major features planned for the near future include HTML and possibly XML output, and more advanced filtering of errors to allow, for example, ignoring errors against an early JDK version if the same problem appears in (more recent versions of) the JDK itself. japitools-0.9.7/design/TODO0000644000175000017500000002747410233004675015644 0ustar sballardsballard* For 0.8.3: * Verify results of new japicompat against old * Print at least *some* kind of overall summary information at end * Update docs * For 0.8.4: * Oops! Fix hang-at-end bug! * For 0.8.5: * Better summary information * Update file version format to 0.9 * Put the whole of java.lang first along with Object * Make japi2new support new version * Make japize generate the new version * Support "unzip" option in japize and require that either it or zip be specified. This is a precursor to making zipped output the default; in the next release, "zip" will be a no-op. Requiring the option for one release at least makes sure that people are aware that something changed... * Update webpage to describe zip/unzip option and rationale, with note that "zip" is going away. * Make japi2new work something like gzip/gunzip, replacing the original unless otherwise specified. Make it work with gzipped input and produce gzipped output (by default, but turn-offable). * Rename japi2new to japifix. * Make japifix abort with an error on mis-ordered files. Support a -s option to force a sort in such situations. * Test japize at least a little bit! * Figure out why japize is screwing up the ordering and fix it. Perhaps put jdk1.1/classes.zip onto laptop so I can work with it. * Get japize to reliably get constants, by ensuring that nothing will get unloaded until there are no references to the JodeClass. "Anything can be solved by another layer of indirection". * Teach japicompat that constructors don't inherit, so don't filter out problems "inherited" from a superclass constructor. * Test japize on jdk1.1 classes.zip (again) and make sure output is equivalent or better than the best I've gotten so far. * Test japicompat again against japicompat-old for new jdk1.1.japi vs kaffe.japi from Dalibor. * Distribute as a tarball rather than a jarball, so permissions flags on the things in 'bin' are preserved. * For 0.8.6: * Make tarball include 'japi' directory rather than just its contents. * Make zipped output the silent default in japize, with "zip" a no-op. "unzip" explicitly will turn off the zipping. * Remove reflection support. * Remove japicompat-old. * Put japitools.jar in japi/share/java, and put jode.jar there too. * Make japize figure out location of *.jar from $0 (../share/java rel path) * Use "!" instead of "%" so that it sorts before "$", making the order correct for inner classes. * Change japize to output "!" and japiver 0.9.1 * Change japicompat to recognize "!" and japiver 0.9.1 * Change japipkgs to recognize 0.9.1 * Change japifix to convert to "!" and 0.9.1 * Test japize on jdk1.1 * Test japifix on old jdk1.1 japis * Test japifix on old kaffe japis (to ensure that the ordering of inner classes is right) * Test japicompat between jdk1.1 and kaffe, as usual. * For 0.8.7: * Bump file format version * Exclude subclasses of RuntimeException and Error, and subclasses of other exceptions thrown by the same method. * Flush stdout at the end of output... * Filter out Throwable subclasses in japifix by the same algorithm as used in japize. * Remove japifix's ability to work on stdin/stdout. * Remove japifix's -s option. * Allow japifix to restart with needtosort set if a file is found to be mis-sorted. * Turn japipkgs into japilist with enhanced capabilities. * Allow japifix to read to the end of a file to find all the Throwables and then restart using that list for exception-filtering. * Allow specifying a file to japifix that won't be converted but will be scanned for exceptions, so that you can include platform libraries when fixing japis that don't include the full platform. * Fix japize bug causing missort when java.lang is not included but java.lang.(something) is. * Include serialize, serialcompat, build.xml and new runner scripts from Brian Jones. * For 0.9: * Make japifix prune exceptions correctly for the same-throws-clause case. * Bump file format version to force users to update their japis in case they're wrong. * Make japicompat include the per-package and overall summaries on stdout as well as stderr, so that "-q" and a redirect of stdout only gives decent results. * Incorporate patch from Brian Jones to runner scripts and index.html * Make sure all relevant files carry the GPL, and include COPYING * Prepare a nice-looking announcement * This is intended as a vaguely stable milestone before everything breaks again. * For 0.9.1: * Remove "zip" no-op option as useless cruft. * Output in raw mode by default (suggest .japio as file ext). Add a version to japio files similar to that in japi files. * Put all summary information into the japio somehow to allow it to appear in the results files. * japiohtml filter program * japiotext filter program * Include source for jode.bytecode and its dependencies. * Compile jode bits from source and put them in japitools.jar. Remove all references to jode-1.1.1.jar. * Apply theoretical fix to jode breakage with 1.4. * Break down results in japio* by package and etype. * Add links to each package/etype in japiohtml. * For 0.9.2: * Bump the file format version again... * Add an optional creation date on the %%japi line. * Include the japi-creation date in the japio. * Include the japi-creation date in the html and text. * Add "++" in front of java.lang.Object and "+" in front of java.lang.*, and make both japifix and japicompat use straight "cmp" for sorting. * Make japifix add the pluses as needed. * Make japicompat ignore the pluses. * Make japilist ignore the pluses. * Fix/workaround the Jode limitation that inner class static/protected modifiers aren't read right. * Make japifix skip files that are already the right version unless a -f flag is specified. * Make japifix preserve the date field if present. * Put a 12 pixel height on the spacer gif to help opera out. * Use ".bad a { color: foo }" etc to support browsers that can't handle "color: inherit". * "-o filename" flag to japicompat which will write straight to a file rather than stdout. * "-h" and "-t" flags to japicompat will pipe to the relevant program * Store the current japi version in a variable in japifix since it's now used in multiple places. * "-q" flag to japifix to supress messages. * Add "legend" to the top of HTML output indicating the colors for "perfect" thru "completely wrong". * Add class-deprecation support to Jode code. * Add summary totals in both japiotext and japiohtml that appear *before* the full listing of errors. * Add per-package summary of errors in japiohtml, again *before* the actual error listing. Minimize the size of it by putting it in spans using   instead of space, all on one "line", and letting the browser wrap as appropriate. * Do something like readable_member on item names to make them human-readable. * Remove readable_member and anything else that's now japio*'s responsibility from japicompat. * Order the errors in japiotext in the same way japiohtml does. In fact, redo japiotext entirely as a stripped-down version of japiohtml. * Use "minor" instead of "svuid" * Add deprecation testing (it's an error to *un*deprecate an API) * Bump file version and add info to output in japize * Bump version in japifix and add "?" if not present * Bump version in japilist * Bump version in japicompat and test the new field * Add rawbits support in float and double constants (but don't test it, yet). * Include both the bits and the readable number in japize * Have japicompat ignore the bits completely, for now, and not require them to be present. * Support specifying the letter to alt-ify to (eg mi_nor). * Update spec to cover new changes. * For 0.9.3: * Compare rawbits values if both are present, otherwise compare strings as now. Make the output always include both if present. * Fix bug that reports deprecation problems only on classes and interfaces. * Fix erroneous } that slipped into constants * Escape slashes in "was" and "is" to avoid breakage when / appears in, say, a constant string. * Fix problem causing japize to fail on Classpath. * This is a 1.0 "Release candidate" * Skipped up to 0.9.5... * Fix bug causing minor not to be recognized as 'ok'. Now a package with only minor bugs will get the 'good-100pct' CSS class. * Update webpage docs to cover the changes in the 0.9.x series. * Bump the file format version. * Ability to filter out differences between, say, jdk11 and jdk14 when comparing jdk11 against $freevm - For 0.9.6: - Remove Jode support entirely - Preliminary generics, enum, annotation etc support - Generics, specifically: - Need to be aware of the containing class(es) when doing read_japi_item. How? - By putting stuff in the $japi hash. BUT WHAT? - A single $clitem which is the japi entry of the single containing class. When a class(/interface/etc) is found, replace $clitem with this one. - Also add the $clitem to the item hash. This provides a chain to find the container of an inner class. Make sure to store the previous $clitem for items that represent an inner class of that item, but not for brand new classes. - We can probably stop storing $oclitem and $nclitem in compare_japis - And stop passing it to output_error - $item->{member} should be the sanitized string, for compatibility - $item->{gmember} should be the real, generic-aware string - Sounds like what we really want is: sanitize_typesig(str, item) sanitize_member(item) - works on $item->{gmember} sanitize_member looks for (...), splits it on comma, and calls sanitize_typesig on each part. sanitize_typesig makes the following changes: - Removes anything between <> - Replaces any @n with the nth generic parameter type - Replaces any leading . with a [ - - For 0.9.7: - Finish generics, enum, annotation etc support - Array and annotation-typed annotation method defaults - Annotations on packages, types and members - Constraints on generic types - Investigate improving representations eg of inner classes of generic types - Try to be less naive about reporting every difference as an error - For 1.0: - Fix any bugs reported in 0.9.7. - Do 1.0.x releases for other (hopefully minor) bugfixes. - For 1.1.1: - Add "link to Sun's documentation" capability somehow. - Use environment variables - JAPI_DOC_LINK=http://java.sun.com/j2se/1.4/docs/api - JAPI_DOC_FORMAT=1.1 or 1.0 (1.2 and up format assumed if missing) - How to deal with methods inherited from superclass? In general such methods shouldn't cause problems - except when overridden in the free API but not in the original. - First pass: don't even try to link to the method, just the class or the package summary. - Add capability to specify args to japize in a file (eg .japispec) - Add more filtering options to japilist: -s superclass, -t throwabletype, -f (public|protected|abstract|concrete|static|instance|final|nonfinal| constant|variable|deprecated|undeprecated) - Document japilist (both help messages and webpage documentation). - Document japifix (-? option, webpage documentation). - Convert all non-"a-zA-Z0-9_$" characters in class and method names into "\uXXXX". Do the same with characters in constant strings, only use a wider range (eg everything in the ascii charset from space to the end). Update the spec to document the fact that this is now truly supported, rather than just theoretically specified. - Find out how to take a \uXXXX escape and turn it into an &#xxxx; HTML one. - This is a 1.2.0 Release Candidate. - For 1.2.0: - Any bug fixes reported against 1.1.1.japitools-0.9.7/design/genericnotes.txt0000644000175000017500000003500310334251201020354 0ustar sballardsballardTo handle generics we need a more unified/flexible way of dealing with types in general. Today we have the following situation: interface ClassWrapper extends Wrapper { ... ClassWrapper getSuperclass(); ClassWrapper[] getInterfaces(); ... } interface FieldWrapper extends Wrapper, Comparable { ... String getType(); ... ClassWrapper getDeclaringClass(); } interface CallWrapper extends Wrapper, Comparable { String[] getParameterTypes(); String[] getExceptionTypes(); ... String getReturnType(); ClassWrapper getDeclaringClass(); ... } And who knows exactly what format the string returns from getType(), getParameterType(), getExceptionTypes() and getReturnType() are actually in? I propose the following: interface Type { String getTypeSig(); } class PrimitiveType implements Type { } class ArrayType implements Type { } class TypeParam implements Type { } interface ClassWrapper implements Type { } There's still an issue as to exactly what the role of ClassWrapper is, though, because it's currently fulfilling two roles. ClassWrapper is used for both List and Collection in the context of List implements Collection. There's a key difference in what information is needed for the two halves of that statement, as is clear by how they'll show up in the japi file: interface*java.util.Collection<@0> In other words, in the case of List we need to know the type *parameters*, but in the case of Collection we need to know the type *arguments* - the *values* of the parameters *in this particular case*. The ClassWrapper of List is being used to represent the generic, un-instantiated List type. The ClassWrapper of Collection is being used to represent a particular instantiation of the Collection type. The situations are different and really should be recognized as such, despite the fact that most of the same information is needed. We do this by having a hierarchy coming down from Type which knows how to represent instantiated types, potentially with arguments. It's possible to get from a ClassType to the associated ClassWrapper but to do the other direction you have to supply the type arguments, or say that there aren't any. We should change the japi file spec so that it understands the inheritance from containing classes because it makes the japi file clearer and represents fairyland better, which is the whole point really. Interesting issue. This is a setup that could not occur under 1.4. class Super { String foo(T t); } class Sub extends Super { Object foo(Object o); String foo(String s); } Sub has these methods: String foo(Object=String) inherited (Foo=Bar notation intended to convey that the VM thinks it's object but the compiler knows it's String) String foo(Object) BRIDGE (inserted by the compiler, calls foo((String) o)) String foo(String) (declared) Object foo(Object) (declared) We've established that we want to ignore bridge methods. That leaves: p,Sub!foo(Ljava/lang/Object;=Ljava/lang/String;) Pcinu Ljava/lang/String; p,Sub!foo(Ljava/lang/Object;) Pcinu Ljava/lang/Object; p,Sub!foo(Ljava/lang/String;) Pcinu Ljava/lang/String; This situation can't happen in 1.4 so it's okay that the first two would be contradictory (since 1.4 ignores the bit after =). However if we have a class that *doesn't* declare the Object foo(Object) method, the situation is murkier. p,Sub!foo(Ljava/lang/Object;=Ljava/lang/String;) Pcinu Ljava/lang/String; p,Sub!foo(Ljava/lang/String;) Pcinu Ljava/lang/String; 1.5 says these are clearly, trivially, the same method. 1.4 says equally clearly and trivially that they're not. In other words, if the previous non-generic version had: p,Sub!foo(Ljava/lang/Object;) Pcinu Ljava/lang/String; Under 1.4 rules that's totally compatible. Under 1.5 rules it's not. On the other hand, if the non-generic version had: p,Sub!foo(Ljava/lang/String;) Pcinu Ljava/lang/String; Under 1.4 rules that's *incompatible* but under 1.5 rules it's compatible. Seems that the key is to use 1.5 rules when they apply on the "orig" half of the API - that is, the one we're comparing against. If we represent the method as p,Sub!foo(Ljava/lang/Object;=Ljava/lang/String;) and *sort* it as if it were java/lang/String, then we can say that such a method is completely compatible with a method that's p,Sub!foo(Ljava/lang/String;), and not compatible with a method that's Ljava/lang/Object. Do we need to store Object here at all or do we just need to recognize that it's only "conceptually" String and that 1.5 rules need to be applied to it? Then the trick is to notice when it's appropriate to insert the "=". Previously once we found foo(String) in Sub we would use it unmodified and ignore any identical method on Super. Now we need to notice when we find the identical method on Super that we need to annotate the Sub method as being Object=String instead of just String. Consider the 1.4a, 1.4b and 1.5 versions of Super and Sub. The 1.4a version has: p,Sub!foo(Ljava/lang/String;) Pcinu Ljava/lang/String; 1.4b has: p,Sub!foo(Ljava/lang/Object;) Pcinu Ljava/lang/String; and 1.5 has: p,Sub!foo(Ljava/lang/Object;=Ljava/lang/String;) Pcinu Ljava/lang/String; japicompat v15.japi.gz v14a.japi.gz should say nothing. That's completely compatible by *1.5* rules, and since the 1.5 line is on the RHS, that's the rules that apply. japicompat v15.japi.gz v14b.japi.gz should flag an error, p.Sub.foo(String) missing in v14b. japicompat v14a.japi.gz v15.japi.gz should flag an error, p.Sub.foo(String) missing in v15, because that comparison should be done by 1.4 rules, and 1.4 rules say that v15 contains foo(Object). japicompat v14b.japi.gz v15.japi.gz should *not* flag an error, because by 1.4 rules foo(Object) is required, and that's what exists. Solution: 1.5 japi includes the method twice, once sorted each way. p,Sub!foo(Ljava/lang/Object;}Ljava/lang/String;) Pcinu Ljava/lang/String; p,Sub!foo(Ljava/lang/RuntimeException;) (just to show the sorting behavior...) p,Sub!foo(Ljava/lang/String;{Ljava/lang/Object;) Pcinu Ljava/lang/String; The first sorts as object, the second as string. When japicompat sees the { or } characters in the "orig" part of a comparison it knows that it should use 1.5 rules, so it ignores the } version and treats the { version as if it just said "string". When it's in the "new" part of a comparison and an equivalent method exists in the "orig" part without the "}", it should use that one and treat it as if it were Object. Alternatively just annotate the method like this: p,Sub!foo(Ljava/lang/Object;)- Pcinu Ljava/lang/String; p,Sub!foo(Ljava/lang/String;)+ Pcinu Ljava/lang/String; The "-" says "this is a 1.4 representation of a 1.5 method, only consider it when comparing against 1.4-style methods". The "+" says "this is a 1.5 representation of a 1.5 method, ignore it when comparing against 1.4-style methods". It's legal to have this: p,Sub!foo(Ljava/lang/Object;)- Pcinu Ljava/lang/String; p,Sub!foo(Ljava/lang/Object;) Pcinu V p,Sub!foo(Ljava/lang/String;)+ Pcinu Ljava/lang/String; but these combinations are illegal: p,Sub!foo(Ljava/lang/Object;)- Pcinu Ljava/lang/String; p,Sub!foo(Ljava/lang/Object;) Pcinu Ljava/lang/String; p,Sub!foo(Ljava/lang/String;)+ Pcinu Ljava/lang/String; p,Sub!foo(Ljava/lang/Object;)- Pcinu Ljava/lang/String; p,Sub!foo(Ljava/lang/String;) Pcinu V p,Sub!foo(Ljava/lang/String;)+ Pcinu Ljava/lang/String; That is to say, clashes between a "-"-annotated method and a non-annotated one are legal as long as the return type differs, but clashes between a "+"-annotated method and a non-annotated one are always illegal. The "-"-annotated method should represent the 1.4 return type and the "+"-annotated method the 1.5 return type. That is: class Super { Object getFoo(); } class Sub { String getFoo(); } gives: p,Sub!getFoo()- Pcinu Ljava/lang/Object; p,Sub!getFoo()+ Pcinu Ljava/lang/String; This means that clashes between a "+" and "-" method are also ok as long as the return type is different. The "+" and "-" entries should appear only if they are "essentially" different. class Super { T get(); } p,Super! Pcsnu class p,Super!getFoo() Pcinu @0 *not* p,Super! Pcsnu class p,Super!getFoo()- Pcinu Ljava/lang/Object; p,Super!getFoo()+ Pcinu @0 Reason is that simple generic-removal is sufficient to get from one to the other. However, class Sub extends Super { } p,Sub! Pcsnu class p,Sub!getFoo()- Pcinu Ljava/lang/Object; p,Sub!getFoo()+ Pcinu @0 because @0 resolves to String. TODO: * Fix bugs that show up today in the results: * Eliminate interface methods that are also in Object (GSSCredential!hashCode is the test) * Be more intelligent about the tostring-independent floats and doubles bit when deciding what errors to ignore * Generic support: * Bind type parameters correctly in superclasses and superinterfaces * Make Japize, not ClassFile, responsible for recursing up through superclasses and superinterfaces to find methods and fields. * Trick is we need to be able to store the result of binding the parameters etc of the method and type of the field. * Start testing japicompat between these japis * Add a flag to toggle between 1.5-style and 1.4-style comparison because simply looking at the method isn't sufficient - Sub should still be equally compatible even if Super doesn't have a foo method. - Update the japi spec to discuss + and - annotated methods and other changes, like the fact that inner classes are considered to automatically inherit the type params of the container - Getting japicompat to handle prettifying generic types and handle them in bounds, various problems: - Parsing nested <> is tough, can we actually avoid it? - Basic prettification of either a single gen type or a list of them - Superclasses - Interfaces - Exceptions - Method parameters - Return types and field types - The problem of a list of them is almost idential to the problem of a single one because a single one can have a list of parameter types Ljava/util/Map;>; s/L([^;]+);/$1/g gives: The L has to be at the beginning or after a , or <. That works if you stick a , at the beginning and remove it afterwards... java/util/Map;>; again gives java/util/Map>; java/util/Map> Ooops! Even the trick we're using now doesn't work because we also want to handle lists of the things. * Recognizing the end of a <>-contained list that may include such things * De-generifying backrefs - Wildcard types and multiple bounds: Foo Foo! Pcsnu class ? extends T becomes {@0 ? super T becomes }@0 ? is naturally {Ljava/lang/Object; but of course should be specialcased. You can't specify both extends and super on the same wildcard so no syntax is needed for that. In the Signature attribute it's + and - instead of { and }, should we adopt this? Annotationotes: An annotation is represented as [pkg.Type|name=value|name=value] In the japi file they appear here: pkg,Foo!#member Pcsfu[pkg.Type][pkg.Type2|name=value] I methods of the annotation sort alphabetically annotation types sort alphabetically by FQCN method parameter annotations go inline with the parameters they refer to: pkg,Foo!method([pkg.Type]I,[pkg.Type][pkg.Type2]Ljava/lang/Object;) Value - legal types are: primitive string class annotation array of legal type Encoded means s-\-\\-, s-(newline)-\n-, s-[^0-9a-zA-Z]-\u(4-digit-hex-unicode-number)- We update the encoding spec to say that constant strings must always be encoded this way (and actually implement it, unlike the old encoding spec). Primitives: lettervalue eg I3, F0.1\u002f34a2321c (accounts for characters ZCBSIJFD) String: svalue eg sHello\u0020World Class: Ljava\u002flang\u002fString\u003b Annotation: A\u005bpkg.Type\u007c...\u005b Array: \u005bsvalue\u007cs\u007csvalue\u007c\u007csvalue\u005d means {"value", "", "value", null, "value"} Then we do: primitive, string are represented just like the constant format. Only special character possible is "/" in floats and doubles, plus \. class is represented in typesig representation and encoded. Special characters would be /;<>, but since they're encoded they're not. Annotation is represented as [pkg.Type|name=value] etc but encoded. Special characters would be []|= but since they're encoded they're not. Array is represented as [value|value|value|value] but encoded. Special characters would be []| but since they're encoded they're not. Problem with array rep: how to distinguish null from empty string? Maybe say that string, always, is represented as [value]? [[value]|[value]|[]||[value]] represents {"value", "value", "", null, "value"} This means that annotations as a whole can only be [\[\]\|A-Za-z0-9\/\=\.\$], specifically, an annotation is: /\[([A-Za-z0-9\.\$\\]+)(\|[A-Za-z0-9\\]+=[A-Za-z0-9\/\\]*)*\]/ with $1 as the type and $2 matching repeatedly as the name=value pair. Todo: - Go through all the places that say "OPENQ" and figure out what the right thing to do is. - Handle multiple "-" annotated methods with different return values (can't happen yet though) - -P option which suppresses "missing package" errors and also omits the content of those packages from consideration for percentages etc. - Compare error-by-error the cvs output against the "production" output and make sure all differences are for known and expected reasons. - Annotation support. - Testing system: - test/ - orig/ - japitest/ - TnnnName.java - TnnnName_Helper.java - new/ - japitest/ - ignore/ - japitest/ - expected/ TnnnName_orig.japi TnnnName_new.japi TnnnName_ignore.japi TnnnName.txt - results/ - perl script that scans orig, identifies all the TnnnName strings, and loops over them doing: - cd orig; javac japitest/TnnnName.java japitest/TnnnName_*.java - japize unzip as results/TnnnName_orig classes japitest (classpath) +japitest.TnnnName +japitest.TnnnName_Helper ... - if TnnnName.java exists in new or ignore, do the same thing - if it existed in new, japicompat -tvo results/TnnnName.txt [-i TnnnName_ignore.japi] TnnnName_orig.japi TnnnName_new.japi - for each of TnnnName_orig.japi, TnnnName_new.japi, TnnnName_ignore.japi, TnnnName.txt: - diff expected/$n results/$n - except that the date-dependant bits need to be ignored, figure out a solution to thisjapitools-0.9.7/design/japi-spec-0.9.7.txt0000644000175000017500000006056010413241064020225 0ustar sballardsballardThis document specifies the format of a .japi file, version 0.9.7. The actual implementations of japize, japifix and japicompat may not honor this spec exactly. If they do not, it is generally a bug or missing feature in the tool; this spec notes such bugs when they are known. Purpose of Version 0.9.7 ------------------------ Version 0.9.7 of the japi file format is a work in progress on the road to figuring out how to deal with the new language constructs introduced in Java 1.5. Most of the new constructs are now representable, but some are ignored because a suitable representation has not yet been determined. Currently, this document and the tools using it are evolving in parallel, so 0.9.7 does not refer to a single fixed format. Whether or not 0.9.7 is eventually frozen or a 0.9.8 version is defined instead once the issues have been worked out has not yet been decided. The only remaining feature to be inadequately representable in this version is annotations. Specifically, 0.9.7 japi files cannot yet represent: - Annotations applied to classes and members. The plan is to include all annotations that are @Documented. - Default values of annotation methods that are of Array and Annotation types. Types ----- Types in a japi file get represented in different ways, depending on where they appear. This spec identifies which representation should be used for each item. The possible representations are: - Java Language representation: Can only be used to represent classes and interfaces. The format is simply the format of a fully-qualified class name in the Java language, eg "java.lang.Exception". This format is used when only classes and interfaces can appear, eg in superclasses and exceptions. Inner classes are represented using the name that the compiler gives them: the name of the outer class followed by a "$" followed by the inner class name, eg "java.util.Map$Entry". Generic classes are represented by appending a comma-separated list of type arguments, enclosed in angle brackets, in Type Signature representation (see below). For example "java.util.List". For inner classes of generic classes, the type arguments of the outer class are prepended to the list, eg the Entry inner class of Map would be "java.util.Map$Entry". (This may change in a future version to more accurately reflect the semantics, but it is a simplistic approach that is easier to implement for now). Type parameters are represented as "@0", "@1", "@2" etc in the order they appear on the containing class. Type parameters of an inner class are numbered starting where the parameters of the outer class leaves off unless the inner class is static. Likewise, type parameters of a method are numbered starting where the declaring class's parameters leave off, unless the method is static. There is NO representation of primitive types OR ARRAYS in Java Language representation (emphasis because it's easy to forget that JL representation can't express all reference types). - Type Signature representation: Can represent any Java type, including primitive types and arrays. Up to Java 1.4 this was the format used internally within the JVM, but japitools has diverged from the JVM in its representation of the new 1.5 features. The format looks like this: - Primitive types are represented as single letter codes, specifically: boolean=Z, byte=B, char=C, short=S, int=I, long=J, float=F, double=D, void=V - Classes and interfaces are represented as the letter L, followed by the fully-qualified classname with periods replaced by slashes, followed by a semicolon. The class generally known as java.io.Writer would be represented as "Ljava/io/Writer;". Inner classes are represented by the name the compiler gives them, just as above. - Array types are represented by the character "[" followed by the type of elements in the array. For example, an array of java.util.Dates would be represented as "[Ljava/util/Date;" and a two-dimensional array of ints (an array of arrays of ints) would be "[[I". - In the special case of a method which takes a variable number of arguments (which Java implements internally as an array parameter), the leading "[" should be replaced by a ".". For example, a method taking a variable number of int arguments would be represented as ".I" and a variable number of arrays of strings would be ".[Ljava/lang/String;". Note that a special rule applies for ordering methods with this kind of parameter - see "Ordering", below. - Generic types are represented by inserting a comma-separated list of type arguments in Type Signature representation, enclosed in angle brackets, immediately before the trailing semicolon. For example "Ljava/util/Map;". Note that a special rule applies for ordering methods with generic-typed parameters - see "Ordering", below. - Type parameters are represented as "@0", "@1", "@2", exactly as in Java Language representation. Note that a special rule applies for ordering methods with parameters of these types - see "Ordering", below. - Wildcard types are represented by the upper or lower bound, as follows: - "? extends pkg.Foo" becomes "{Lpkg/Foo;" - "? super pkg.Foo" becomes "}Lpkg/Foo;" - "?" is equivalent to "? extends java.lang.Object" and hence becomes "{Ljava/lang/Object;" Open question is whether the actual bound of the parameter the wildcard is being used in should appear here instead; this may make a difference to erasure. - Japi sortable representation: Designed to make it easy to sort a japi file into a convenient order. The format is: ,[$]. In this format, is the package that the class appears in (dot separated, eg "java.util"), is the full name of the outer class (eg "Map") and is the name of the inner class (eg "Entry"), if applicable. The $ is omitted for top-level classes. Note that it is theoretically possible for a class to have a $ in its name without being an inner class: the $ in that case should be \u escaped (see "Escaping", below; note that this is not yet implemented by any of the japitools programs). Multiple levels of inner- classness are represented the obvious way. So the class java.util.Map is represented as "java.util,Map", and its Entry inner class is represented as "java.util,Map$Entry". No special consideration applies to generic types in this representation: they never appear here. Escaping -------- Note: Much of this section is not yet implemented by any existing japitools program. The Java language is a fully unicode-enabled language with support for multibyte characters at every level of the language. However, the japi file format is strictly 7-bit ASCII, for ease of processing in (for example) older versions of perl, which are not unicode-capable. To support this, characters outside the normal ASCII ranges are escaped. Characters are escaped as follows: The newline character is escaped to "\n", the backslash character is escaped to "\\", and all other characters are escaped as "\uXXXX", where XXXX is the lowercase hexadecimal rendition of the integer value of the "char" type in java. In class names, all characters should be escaped except for A-Z, a-z, 0-9, _ and any metacharacters that have meaning in the particular representation being used. In Java Language representation, the metacharacters are ".$"; in Type Signature representation, they are "/$;"; and in Japi Sortable representation they are ".,$". In field and method names, all characters should be escaped except for A-Z, a-z, 0-9 and _. In constant strings, all characters outside the range from " " to "~" in ASCII value should be escaped, along with the backslash ("\") character. Existing implementations only escape backslash and newline, and only do so in constant strings. This covers the vast majority of what will actually arise. Inclusion --------- Japi files may be created for any set of classes and interfaces, although typically they will be made for particular complete packages that make up a public API. However, it is not permitted to create a japi file for only part of a class, and it is not permitted to create a japi file for an inner class without it's containing class, or vice versa. Only public and protected classes and interfaces can be included in a japi file. For each class and/or interface that is included, all its public and protected fields, methods and constructors must be included. This includes fields and methods (but not constructors!) inherited from public or protected superclasses. Due to the way generics are implemented in Java there are situations where a method may appear to be present to a generic-aware compiler but not to a pre-generics compiler, or vice versa; or that the same method may appear to take different typed arguments depending on whether the compiler is generic or not. An example: class Super { void meth(T t); } class Sub extends Super { } Sub appears to have a meth(String) method on a generic-aware compiler and a meth(Object) method on a pre-generics compiler. In order to support comparisons of either kind of API, the method will be included both ways in the japi file. See the discussion of how methods are represented for more details. Ordering -------- In order to allow efficient comparison of japi files, the items in the file are required to be in a strict order. The items are sorted first by package, then by class (or interface), then by member. Packages are sorted alphabetically by name, except that java.lang and all its subpackages (eg java.lang.reflect and java.lang.ref) are placed first. Classes and interfaces are sorted by name, with inner classes coming after the corresponding outer class, except for java.lang.Object which sorts first of everything. Within a class or interface, the class (or interface) itself comes first, followed by its fields (in alphabetical order by name), followed by its constructors (in alphabetical order by parameter types), followed by its methods (in alphabetical order by name and parameter types). "By parameter types" here means by the result of concatenating the types of all the parameters in type signature format, EXCEPT that all 1.5-specific features are ignored. Specifically: - Generic type parameters (@0, @1 etc) are replaced by the constraining type. - Types that *have* generic type parameters (anything in <>s) have them removed. - Varargs array types (starting with ".") are treated as regular array types (replacing the "." with "["). This resulting string does not appear anywhere in the japi file; it is merely constructed in memory for the purposes of sorting. The purpose of this algorithm is to ensure that the ordering of methods using 1.5-specific constructs is identical to the ordering they would have under an older JDK version. This is necessary to support meaningful comparisons between, for example, non-generic and generic versions of the same API, such as java.util between JDK1.4 and JDK1.5. If any package, class or member names include any escaped characters, it is the escaped string that is compared, rather than the original. File Format: Compression ------------------------ Japi files may optionally be compressed by gzip. A compressed japi file should be named *.japi.gz; an uncompressed one should be named *.japi. Tools for reading japi files may rely on this naming convention; tools for creating japi files should enforce it where possible. File Format: First Line ----------------------- The first line of a japi file indicates the file format version. This line is guaranteed to follow the same format for all future releases. Thus, this section (only!) of the spec covers all japi file format versions, past and future. With this information you can identify whether a file is a japi file, and which version it is. However, this spec does not provide any information about the content of the rest of the file for any version other than 0.9.7. Both past and future versions can be assumed to be entirely incompatible with this spec from the second line onwards. The first line of any japi file since version 0.8.1 is as follows: %%japi [ ] indicates the version number of the japi file format. While this version number vaguely correlates with the version number of japitools releases, it is not the same number. Often japitools releases do not require a file format change, and sometimes I mess with the file version number for other reasons, with mixed results. The contents of may vary from version to version, or it and its leading space may be omitted altogether; implementations should parse and ignore it (if present) except when the file format version indicates they can understand it. For completeness, I should mention file format versions prior to 0.8.1. Version 0.7 can be recognized by the following regular expression: /^[^ ]+#[^ ]* (public|protected) (abstract|concrete) (static|instance) (final|nonfinal) / Version 0.8 can be recognized by the following regular expression: /^[^ ]+#[^ ]* [Pp][ac][si][fn] / These version numbers were invented retroactively, of course :) File Format: File information ----------------------------- The field contains a list of space-separated name=value pairs. Unknown names should be ignored. Neither the name nor value may include spaces. The following names and values are permitted: date=yyyy/mm/dd_hh:mm:ss_TZ creator= origver= noserial= serial= The format of the last two items is a semicolon-separated list with packages represented as "pkg.name," and classes as "pkg.name,ClassName". File Format: API Items ---------------------- Every other line in a japi file represents an individual item in the API. These lines must appear in a specific order; see "Ordering" above for the specific requirements. The format of these lines is as follows: ! is "++" for all members of java.lang.Object, "+" for all members of all classes in java.lang and its subpackages, or nothing ("") for all other API items. This allows java.lang.Object and java.lang to appear in the right places in a purely alphabetical sort. is the name of the class, in Japi Sortable representation. is one of the following depending on what type of API item is being represented: - The empty string, to refer to the class or interface itself. - #, to refer to a field. - (), to refer to a constructor. - (), to refer to a method. and are simply the name of the field or method, eg "toString". is a comma-separated list of argument types, with each one listed in Type Signature representation, eg "[B,I,I,Ljava/lang/String;". is usually the empty string, but for methods that are only present for a generics-aware compiler, a "+" appears here, and for methods that are only present to a pre-generics compiler, a "-" appears here. Thus the class Sub used as an example under "Inclusion" above would get: ,Sub!foo(Ljava/lang/Object;)- ,Sub!foo(Ljava/lang/String;)+ With the combination of covariance and generics it is possible to end up with two or more methods with exactly the same parameter types, differing only in return type. Only one of these methods can ever be applicable to a generics-aware compiler. When this happens, all the methods appear in the japi file; the methods that are *not* visible to a generics-aware compiler appearing first sorted by the erased type signature of the return type, and the method (if any) that *is* visible to a generics-aware compiler appearing last. In addition, all the methods *except* the last get a double minus "--" instead of a single "-". The algorithm for determining exactly which methods should be annotated this way is complex and attempting to specify it here would almost certainly lead to simply enshrining bugs in the algorithm as spec requirements. The code of Japize gives one possible implementation, but it will be fixed if it turns out not to reflect what's really visible to the two different kinds of compiler. is a six-character string indicating the modifiers of the item: - The first character is either "P" for public or "p" for protected. Package-private (default access) and private items do not appear in japi files. - The second character is either "a" for abstract or "c" for concrete (non-abstract). Interfaces, and methods on interfaces, are always considered abstract regardless of whether they are explicitly set as such. - The third character is either "s" for static or "i" for instance (non-static). Top-level classes are always considered static. - The fourth character is either "f" for final, "n" for nonfinal, or "e" for the special fields that are the values of an "enum" type. Methods on final classes are always considered final regardless of whether they are explicitly set as such. - The fifth character is either "d" for deprecated, "u" for undeprecated, or "?" if the deprecation status is unknown. Members of a deprecated type are considered deprecated; inner classes of a deprecated type are not, unless explicitly marked so. This is modeled after the logic that javadoc uses. - The sixth character is either "S" for stub or "r" for real. This requires a little background. When implementing an API it is sometimes desirable or necessary to create placeholder implementations of some methods, either to allow programs to compile (if they use that method in a codepath that will not actually be invoked at runtime, for example) or to provide a starting point for implementation. Normally japitools would simply report that such methods are present and correct, even though a human could easily tell that they do nothing useful. If a convention for identifying such stubs is established, they may be marked with "S" in this field. Japize, for example, will mark a method as a stub if it contains a "NotImplementedException" in its throws clause. is a catch-all for general information about the item. What exactly appears here depends on what type of item this is. - For classes, the field consists of the following parts: - The word "class" - If the class has any generic parameters, the character "<", followed by a comma-separated list of the bounds of each parameter in Type Signature representation, followed by the character ">". Type parameters with multiple bounds are separated by "&" just as in the Java language. The type parameters of the containing type are not included; it is up to the consumer of the japi file to calculate the total list of parameters if it needs them (eg to determine out which parameter "@n" refers to for some n). Note that the types in this list may include references to other types in the same list - the famous example being Enum> which renders as "class;>". Another example would be Foo which would render as 'class". - If the class is serializable, the character "#" followed by the SerialVersionUID of the class, represented in decimal. If the class is not serializable, this section does not appear. - For each superclass, in order, the character ":" followed by the name of the superclass in Java Language representation. Superclasses that are neither public nor protected are omitted. In the case of java.lang.Object, which has no superclass, this section does not appear. - For each interface implemented by the class, in *any* order, the character "*" followed by the name of the interface in Java Language representation. Interfaces that are neither public nor protected are omitted. Note that interfaces implemented by superclasses, or by other interfaces, must also be included, even if the superclass that implements the interface isn't public or protected. For example, for the class java.util.ArrayList, the typeinfo would be something like: class#99999999999999:java.util.AbstractList<@0>:java.lang.AbstractCollection<@0>:java.lang.Object*java.io.Serializable*java.lang.Cloneable*java.util.List<@0>*java.util.Collection<@0> - For interfaces, the format is the same except that "interface" appears instead of "class", and the SerialVersionUID and superclasses are not applicable. - For enums, the format is identical to that of classes except that "enum" appears instead of "class", and the serialVersionUID is never included because the enum serialization standard does not require one. - For annotations, the format is identical to that of interfaces except that "annotation" appears instead of "interface". - For fields, the field consists of the following parts: - The type of the field, in Type Signature format. - If all the following criteria are met, the character "=" followed by the name of the class that declares it, in Java Language representation. Only the name of the class appears, not any of its generic parameters. The criteria are: - The field is not final, AND - EITHER: - The field is public, OR - The field is static - If the field is a constant value other than null, the character ":" followed by the value of the field. In the case of a char value, this is the integer value of the character; in the case of a string, it is the escaped string. For all other values it is the result of Java's default conversion of that type to a string. For float and double constants, the stringified value may be followed by "/" and the result of toHexString() called on the result of floatToRawIntBits() or doubleToRawLongBits(), respectively. This is unambigous since there is no way for the stringified value of a float or double to contain "/". This hex string is optional but it is recommended to include it if possible. - For constructors, the field consists of the following parts: - The word "constructor". - For every exception type thrown by this constructor, the character "*" followed by the name of the exception type, in Java Language representation. These may appear in any order. Subclasses of java.lang.RuntimeException and java.lang.Error must be omitted, as must any exception that is a subclass of another exception that can be thrown (eg if both java.io.IOException and java.io.FileNotFoundException can be thrown, only java.io.IOException should be listed). - For methods, the field consists of the following parts: - If the method is a generic method, the character "<", followed by a comma-separated list of type parameter bounds, followed by the character ">". The format of the type parameters is identical to that used for the type parameters of classes, described above. - The return type of the method, in Type Signature format. - For every exception type thrown by this method, the character "*" followed by the name of the exception type, in Java Language representation. These may appear in any order. Subclasses of java.lang.RuntimeException and java.lang.Error must be omitted, as must any exception that is a subclass of another exception that can be thrown (eg if both java.io.IOException and java.io.FileNotFoundException can be thrown, only java.io.IOException should be listed). When a parameter type (@0 etc) is thrown, it should currently be included regardless of whether its bounds guarantee that it is a subclass of RuntimeException, Error or another thrown exception. This is likely to change in a future version of this specification. - If the method is part of an annotation, has a default value, and is of type String, Class, or a primitive type, the character ":" followed by the default value. The format of the default value is identical to that used by constant fields for Strings and primitive types, or the name of the class in Type Signature representation for Class types. Default values on array- and annotation-typed methods are not yet supported. Conclusion ---------- This specification should provide enough information to both read and write japi files. Any questions or comments, especially if anything in this spec is unclear, should be directed to stuart.a.ballard@gmail.com. japitools-0.9.7/design/japi-spec.txt0000644000175000017500000003411010144734126017553 0ustar sballardsballardThis document specifies the format of a .japi file, version 0.9.6. The actual implementations of japize, japifix and japicompat may not honor this spec exactly. If they do not, it is generally a bug or missing feature in the tool; this spec notes such bugs when they are known. Types ----- Types in a japi file get represented in different ways, depending on where they appear. This spec identifies which representation should be used for each item. The possible representations are: - Java Language representation: Can only be used to represent classes and interfaces. The format is simply the format of a fully-qualified class name in the Java language, eg "java.lang.Exception". This format is used when only classes and interfaces can appear, eg in superclasses or exceptions. Inner classes are represented using the name that the compiler gives them: the name of the outer class followed by a "$" followed by the inner class name, eg "java.util.Map$Entry". - Type Signature representation: Can represent any Java type, including primitive types and arrays. This is the format used in type signatures internally within the Java virtual machine. The format looks like this: - Primitive types are represented as single letter codes, specifically: boolean=Z, byte=B, char=C, short=S, int=I, long=J, float=F, double=D, void=V - Classes and interfaces are represented as the letter L, followed by the fully-qualified classname with periods replaced by slashes, followed by a semicolon. The class generally known as java.io.Writer would be represented as "Ljava/io/Writer;". Inner classes are represented by the name the compiler gives them, just as above. - Array types are represented by the character "[" followed by the type of elements in the array. For example, an array of java.util.Dates would be represented as "[Ljava/util/Date;" and a two-dimensional array of ints (an array of arrays of ints) would be "[[I". - Japi sortable representation: Designed to make it easy to sort a japi file into a convenient order. The format is: ,[$]. In this format, is the package that the class appears in (dot separated, eg "java.util"), is the full name of the outer class (eg "Map") and is the name of the inner class (eg "Entry"), if applicable. The $ is omitted for top-level classes. Note that it is theoretically possible for a class to have a $ in its name without being an inner class: the $ in that case should be \u escaped (see "Escaping", below; note that this is not yet implemented by any of the japitools programs). Multiple levels of inner- classness are represented the obvious way. So the class java.util.Map is represented as "java.util,Map", and its Entry inner class is represented as "java.util,Map$Entry". Escaping -------- Note: Much of this section is not yet implemented by any existing japitools program. The Java language is a fully unicode-enabled language with support for multibyte characters at every level of the language. However, the japi file format is strictly 7-bit ASCII, for ease of processing in (for example) older versions of perl, which are not unicode-capable. To support this, characters outside the normal ASCII ranges are escaped. Characters are escaped as follows: The newline character is escaped to "\n", the backslash character is escaped to "\\", and all other characters are escaped as "\uXXXX", where XXXX is the lowercase hexadecimal rendition of the integer value of the "char" type in java. In class names, all characters should be escaped except for A-Z, a-z, 0-9, _ and any metacharacters that have meaning in the particular representation being used. In Java Language representation, the metacharacters are ".$"; in Type Signature representation, they are "/$;"; and in Japi Sortable representation they are ".,$". In field and method names, all characters should be escaped except for A-Z, a-z, 0-9 and _. In constant strings, all characters outside the range from " " to "~" in ASCII value should be escaped, along with the backslash ("\") character. Existing implementations only escape backslash and newline, and only do so in constant strings. This covers the vast majority of what will actually arise. Inclusion --------- Japi files may be created for any set of classes and interfaces, although typically they will be made for particular complete packages that make up a public API. However, it is not permitted to create a japi file for only part of a class. Only public and protected classes and interfaces can be included in a japi file. For each class and/or interface that is included, all its public and protected fields, methods and constructors must be included. This includes fields and methods (but not constructors!) inherited from public or protected superclasses. Ordering -------- In order to allow efficient comparison of japi files, the items in the file are required to be in a strict order. The items are sorted first by package, then by class (or interface), then by member. Packages are sorted alphabetically by name, except that java.lang and all its subpackages (eg java.lang.reflect and java.lang.ref) are placed first. Classes and interfaces are sorted by name, with inner classes coming after the corresponding outer class, except for java.lang.Object which sorts first of everything. Within a class or interface, the class (or interface) itself comes first, followed by its fields (in alphabetical order by name), followed by its constructors (in alphabetical order by argument types), followed by its methods (in alphabetical order by name and argument types). "By argument types" here means by the result of concatenating all the types of the arguments, comma separated, in type signature format. If any package, class or member names include any escaped characters, it is the escaped string that is compared, rather than the original. Note: the separating characters in a japi file were chosen so that a straight alphabetical sort is sufficient to achieve this ordering. File Format: Compression ------------------------ Japi files may optionally be compressed by gzip. A compressed japi file should be named *.japi.gz; an uncompressed one should be named *.japi. Tools for reading japi files may rely on this naming convention; tools for creating japi files should enforce it where possible. File Format: First Line ----------------------- The first line of a japi file indicates the file format version. This line is guaranteed to follow the same format for all future releases. Thus, this section (only!) of the spec covers all japi file format versions, past and future. With this information you can identify whether a file is a japi file, and which version it is. However, this spec does not provide any information about the content of the rest of the file for any version other than 0.9.6. Both past and future versions can be assumed to be entirely incompatible with this spec from the second line onwards. The first line of any japi file since version 0.8.1 is as follows: %%japi [ ] indicates the version number of the japi file format. While this version number vaguely correlates with the version number of japitools releases, it is not the same number. Often japitools releases do not require a file format change, and sometimes I mess with the file version number for other reasons, with mixed results. The contents of may vary from version to version, or it and its leading space may be omitted altogether; implementations should parse and ignore it (if present) except when the file format version indicates they can understand it. For completeness, I should mention file format versions prior to 0.8.1. Version 0.7 can be recognized by the following regular expression: /^[^ ]+#[^ ]* (public|protected) (abstract|concrete) (static|instance) (final|nonfinal) / Version 0.8 can be recognized by the following regular expression: /^[^ ]+#[^ ]* [Pp][ac][si][fn] / These version numbers were invented retroactively, of course :) File Format: File information ----------------------------- The field contains a list of space-separated name=value pairs. Unknown names should be ignored. Neither the name nor value may include spaces. The following names and values are permitted: date=yyyy/mm/dd_hh:mm:ss_TZ creator= origver= File Format: API Items ---------------------- Every other line in a japi file represents an individual item in the API. These lines must appear in a specific order; see "Ordering" above for the specific requirements. The format of these lines is as follows: ! is "++" for all members of java.lang.Object, "+" for all members of all classes in java.lang and its subpackages, or nothing ("") for all other API items. This allows java.lang.Object and java.lang to appear in the right places in a purely alphabetical sort. is the name of the class, in Japi Sortable representation. is one of the following depending on what type of API item is being represented: - The empty string, to refer to the class or interface itself. - #, to refer to a field. - (), to refer to a constructor. - (), to refer to a method. and are simply the name of the field or method, eg "toString". is a comma-separated list of argument types, with each one listed in Type Signature representation, eg "[B,I,I,Ljava/lang/String;". is a five-character string indicating the modifiers of the item: - The first character is either "P" for public or "p" for protected. Package-private (default access) and private items do not appear in japi files. - The second character is either "a" for abstract or "c" for concrete (non-abstract). Interfaces, and methods on interfaces, are always considered abstract regardless of whether they are explicitly set as such. - The third character is either "s" for static or "i" for instance (non-static). Top-level classes are always considered static. - The fourth character is either "f" for final or "n" for nonfinal. Methods on final classes are always considered final regardless of whether they are explicitly set as such. - The fifth character is either "d" for deprecated, "u" for undeprecated, or "?" if the deprecation status is unknown. is a catch-all for general information about the item. What exactly appears here depends on what type of item this is. - For classes, the field consists of the following parts: - The word "class" - If the class is serializable, the character "#" followed by the SerialVersionUID of the class, represented in decimal. If the class is not serializable, this section does not appear. - For each superclass, in order, the character ":" followed by the name of the superclass in Java Language representation. Superclasses that are neither public nor protected are omitted. In the case of java.lang.Object, which has no superclass, this section does not appear. - For each interface implemented by the class, in *any* order, the character "*" followed by the name of the interface in Java Language representation. Interfaces that are neither public nor protected are omitted. Note that interfaces implemented by superclasses, or by other interfaces, must also be included, even if the superclass that implements the interface isn't public or protected. For example, for the class java.util.ArrayList, the typeinfo would be something like: class#99999999999999:java.util.AbstractList:java.lang.AbstractCollection:java.lang.Object*java.io.Serializable*java.lang.Cloneable*java.util.List*java.util.Collection - For interfaces, the format is the same except that "interface" appears instead of "class", and the SerialVersionUID and superclasses are not applicable. - For fields, the field consists of the following parts: - The type of the field, in Type Signature format. - If the field is a constant value other than null, the character ":" followed by the value of the field. In the case of a char value, this is the integer value of the character; in the case of a string, it is the character '"' followed by the escaped string. For all other values it is the result of Java's default conversion of that type to a string. For float and double constants, the stringified value may be followed by "/" and the result of toHexString() called on the result of floatToRawIntBits() or doubleToRawLongBits(), respectively. This is unambigous since there is no way for the stringified value of a float or double to contain "/". This hex string is optional but it is recommended to include it if possible. - For constructors, the field consists of the following parts: - The word "constructor". - For every exception type thrown by this constructor, the character "*" followed by the name of the exception type, in Java Language representation. These may appear in any order. Subclasses of java.lang.RuntimeException and java.lang.Error must be omitted, as must any exception that is a subclass of another exception that can be thrown (eg if both java.io.IOException and java.io.FileNotFoundException can be thrown, only java.io.IOException should be listed). - For methods, the field consists of the following parts: - The return type of the method, in Type Signature format. - For every exception type thrown by this method, the character "*" followed by the name of the exception type, in Java Language representation. These may appear in any order. Subclasses of java.lang.RuntimeException and java.lang.Error must be omitted, as must any exception that is a subclass of another exception that can be thrown (eg if both java.io.IOException and java.io.FileNotFoundException can be thrown, only java.io.IOException should be listed). Conclusion ---------- This specification should provide enough information to both read and write japi files. Any questions or comments, especially if anything in this spec is unclear, should be directed to stuart.a.ballard@gmail.com. japitools-0.9.7/design/japi.css0000644000175000017500000000307110334251636016577 0ustar sballardsballard/* Percentage goodness colors */ .ok-100pct { background-color: #00cc00 } .ok-90pct { background-color: #99dd00 } .ok-80pct { background-color: #dddd00 } .ok-70pct { background-color: #ddc100 } .ok-60pct { background-color: #ddaa00 } .ok-50pct { background-color: #d39900 } .ok-40pct { background-color: #cc8800 } .ok-30pct { background-color: #cc7700 } .ok-20pct { background-color: #cc6600 } .ok-10pct { background-color: #cc5500 } .ok-0pct { background-color: #cc4400 } .ok-none { background-color: #cc0000 } .ok-moot { color: #555555 } /* Error category colors */ .good, .good a { color: #00cc00 } .good-bar { background-color: #00cc00 } .minor, .minor a { color: #009999 } .minor-bar { background-color: #009999 } .bad, .bad a { color: #0000ff } .bad-bar { background-color: #0000ff } .missing, .missing a { color: #555555 } .missing-bar { background-color: #999999 } .abs-add, .abs-add a { color: #8800aa } .abs-add-bar { background-color: #8800aa } /* Layout properties */ th { font-size: 12px; text-align: right } .colhead th { text-align: center } .alternating-1 { background-color: #ddddff } .alternating-2 { background-color: #eeeeee } .datestamp { text-align: right; color: #bbbbbb; margin-top: 3px; margin-bottom: 3px; font-size: 80%} a { color: black; text-decoration: none } a:hover { text-decoration: underline } .ehead { margin-bottom: 2px; padding-bottom: 2px } .phead { background-color: #ddddff } .sp { font-size: 0 } .notify { color: #440000; margin-top: 5px; margin-bottom: 5px; font-size: 90%} .ok-moot a { color: #555555 } body, table { font-family: sans-serif } japitools-0.9.7/design/japio-spec.txt0000644000175000017500000000722107575204656017753 0ustar sballardsballardRequirements: - Include summary information sufficient to generate output equivalent to what we have now. - Include the actual error messages in a form sufficient to filter by them, much like the current internal format. Format: %%japio 0.9.1 @ @ ... is optional, but if present should be in the format yyyy/mm/dd_hh:mm:ss_TZ determines the format of the rest of the line. Possibilities are: categories =a b c ... This indicates all the categories that will be included in this japio file. The usual categories line would look something like: categories =good =svuid bad missing abs.add The "=" in front of good and svuid indicates that these values are considered "okay"; for example, in japiohtml, a package with 85% good and 5% svuid will get the ok-90pct CSS class. Any name can appear in this list, and all names are optional, but the name "good" is special: it implies there are *no* actual errors associated with it. In the future this will perhaps be expressed in the categories line, eg "==good". rawsummary : : ... eg: rawsummary java.lang good:934 bad:46 missing:11 +abs.add:3 This indicates that of 934+46+11 tested API items in java.lang, 934 were good, 46 bad, and 11 missing, and that additionally, 3 items were "abs.add". The "+" before abs.add indicates that it should not be counted towards the total count, so the totals of good+bad+missing equate to 100% and abs.add is an extra few percent. The names can be anything but must be listed in the "categories" line, which must appear before any rawsummary lines. A rawsummary line should be emitted for every package processed, and additionally for the special package # (everything). These numbers indicate "raw" data: it is not expected that every error should correspond one-to-one with the bad/missing counts. Rather, it is expected that the numbers should be weighted in such a way that interpreting them as percentages is as meaningful as possible. error ,! / is a category name such as "bad", "missing" or "abs.add". Again, it must have appeared in the categories line. is one of "package", "class", "interface", "field", "method" or "constructor". , and are rendered just like in a japi file, and must correspond to the itemtype given. A package is rendered as java.lang,! is a list of all superclasses and superinterfaces of the class, colon separated. This field can be omitted entirely, eg for a package or for java.lang.Object, in which case there will be two consecutive spaces between and . and describe the substance of the error, by specifying what the item "is" compared to what it "was" in the old file. For example, a method that has illegally changed from static to instance would have a of "static" and an of "instance". can be left empty for errors where the gives all the information you need (for example, if is "missing"). Both and can contain any character, including spaces, but / should be escaped as ~s and ~ as ~t. The only hard-and-fast rules for the choices of and are that and cannot be the same, and that they have to be interchangeable: if the same error would apply in a reverse comparison, the resulting line must be the same with and swapped. Some other examples: instance/static constant [foo]/nonconstant constant [foo]/constant [bar] /a new abstract method nonfinal/final concrete/abstract japitools-0.9.7/design/multicompat.txt0000644000175000017500000000410207566443542020251 0ustar sballardsballardTake multiple items on the cmdline. Have an LHS, an RHS, and a center. japicompat -l jdk11 -l jdk12 -l jdk13 -l jdk14 -c classpath -c kaffe -r jdk14 The following tests will be done: Foreach a < b, test L(a)=>L(b) Foreach a, b test L(a)=>C(b) excluding errors from L(a)=>L(*). Foreach a, b test C(a)=>R(b) Plan is to read *all* japi files simultaneously and process in parallel. This'll be a good trick since we won't have one file that we can cue from. The plan will be something like: Have all of the files aware of what has been read from all the others, and have something like a "conditional_read_item" function that only reads an item if none of the others are "behind". Perhaps arrange to do the read in order of "behindness" so that other files that are less behind read in sync. a a a a b b c c c c d d d e e e e Idea is to have, at any time, all the records of the same letter being read, but without any knowledge of which, if any, file has "everything". Perhaps the trick is for each file to readahead one line? Seems probable, else how do you deal with #4 over there? So for each file, readahead one line. Store everything there is to know about the item that was read-ahead. Determine the lowest "next" item, and process it. "Process" means to advance each file with that as their "next" item, and do all the comparisons on it (missing, bad, etc). While doing so, keep the "lowest next item" counter up to date, with the "next-lowest-next-item" taking over after this one is gone. Data structure to keep track of lowest-ness could be a simple sorted set: pop off an item and process it, and insert an item for each "next" record read. Since there's no sortedset structure in perl and this isn't performance-critical (but is clarity-of-code critical), using $foo->{$x}=1 and sort(keys(%$foo)) is probably the easiest way. When doing processing, likeliest way seems to be to have a way to ask a particular file "do you have record x next"? All comparisons can be done on this basis. japitools-0.9.7/design/newcompat.txt0000644000175000017500000000665007507112770017711 0ustar sballardsballardHere's the plan: - Generate .japi files in alphabetical order by membername. This makes it possible to merge in O(1) space and O(N) time. Specialcase j.l.Object to go first, because that can save a pass later. Allow japi2new to sort japis as well as messing with the formatting. - japicompat will generate a .jace file. This will consist of all errors, in the order they were found in the japi. jace = Java API Compatibility Errors. The format will look something like this: a.b.C:a.b.CSuper:java.lang.Object # extends_#!_in_#O_but_not_in_#N a.b.AnotherCSuper #foo() method_present_in_#O_but_not_in_#N a.b.CSuper:java.lang.Object ... Classes will only appear if there are errors in them; each error will appear on a # line by itself. #! indicates a positional parameter. #O and #N indicate the original and new japi filenames. Parameters will be encoded similarly to the way prim consts are in the japi. - japifilter will make several passes through the two jaces. Each pass will operate on a particular subset of classes and interfaces. Specifically: - Pass 1 will start with j.l.Object (hence its positioning first in the file), then it will move on to interfaces that have no superinterfaces. In the process it will also do all "missing package" and "missing class" checks, and figure out how many subsequent passes of each type are needed. - Pass 2 will operate on interfaces with exactly 1 superinterface; pass 3 on interfaces with 2 superinterfaces, etc, until all interfaces have been processed (it might be possible to skip some numbers if pass 1 determined that there were no interfaces with that number of superinterfaces). Say that this process continues up to pass N-1. - Pass N will operate on classes with exactly one superclass (j.l.Object). Pass N+1 will operate on classes with 2 superclasses, etc (as with interfaces, it might be possible to skip some numbers). When all classes have been processed at pass M, we're done. In determining which classes to process in which pass, only the "new" japi is considered. Hence a class with 2 superclasses in the original and 3 superclasses in the new would be processed as having 3 superclasses. So that gives an order to process in, but doesn't define what "process" means. It means the following: - Determine any errors on that class or interface. - In memory, construct a tree of class -> superclass -> ... j.l.Object. - Check for the same errors on all superclasses. If present, don't add the error to the tree. Don't even add the class to the tree! - If not present already on a superclass, add the class and the error to the tree, and write it to the output file. - japifilter can also provide some other options too: - Take multiple jace files and combine them. - Write output as either a fresh jace file or as human-readable output. - Include/exclude serialversion errors, abstract method additions and/or single-buttocked selections. This can be implemented easily as a generic filter on the error strings, with particular filters tied to cmdline flags. - Take one or more jace files and *exclude* all errors that occur in said jace files. The exclusion file(s) will be processed first and added to the tree *without* writing them to output. This must be done on a per-pass basis: do pass 1 on the exclude files, then on the include files, then pass 2 on the exclude, etc. japitools-0.9.7/design/newjapize.txt0000644000175000017500000000564107507112770017707 0ustar sballardsballardTo identify the classes to process, work as follows: - Allow two forms of packagepath to be specified on the commandline: "a.b,C" and "a.b.c,". The first is a class C in package a.b and the second is a package a.b.c. If the user specifies a.b.c with no disambiguation, both forms will be added, as if the user had specified "+a.b,c +a.b.c,". Note that comma sorts before period, and both sort before any alphanumerics. - I think we can also provide a way for the user to specify what type of roots these are without knowing this obscure syntax. For any ambiguous packagepath (ie one not explicitly identified as either ",X" or "x,"): - "packages" => It's a package. - "classes" => It's a class. - "apis" => Make no guesses, include both possibilities. Guaranteed to get it right but potentially at the cost of performance. - "byname" => If the first letter after the last "." is uppercase, treat it as a class, otherwise as a package. This heuristic will work most of the time but not for, say, +org.omg.CORBA which would require a trailing ",". - "explicitly" => An ambiguous packagepath is an error. - Sort all "plus" items - these will be our "roots". - Identify whether java.lang.Object falls within the scope of things to process. If it does, process it as a class root and then add "-java.lang,Object" to the list of exclusions. - Iteratively process each root in order. After processing each root, skip any following roots that lie between "root" and "root/". Since slash sorts after comma and period but before alphanumerics, this will exclude any subpackages and classes but not anything like a.b.CD. - "Process" for a class root is defined as follows: Scan all zips and directories for this class, and if it's found, Japize it. Optimization: on the first scan through, compare all classes to all class roots and remove class roots that aren't found. Also set a flag to indicate that this has been done - then you can skip the scan for all subsequent class roots. - "Process" for a package root is a recursive function defined as follows: - Scan all zips and directories for (a) classes in this package directly, and (b) immediate subpackages of this package. Store everything that is found. - Sort and then iterate over the items found in (a): - Skip the class if there is an exclusion ("-" form) for this class specified on the commandline. - Otherwise Japize the class. - Sort and then iterate over the items found in (b): - If there is an exclusion for this subpackage found on the commandline, skip it, but also do the following: - Using SortedSet.subSet(), identify if there are any global roots that lie between "excludedpkg" and "excludedpkg/". If there are, process those in order using the appropriate process method for the type. - If the package is not excluded, process it recursively using this process method. japitools-0.9.7/design/percent-rounding.txt0000644000175000017500000001221707564456414021204 0ustar sballardsballardFor the bar chart in the HTML output, we need to get a the percentages to all be integers and yet still all add up to 100% exactly. We also want anything nonzero to show up at least one pixel. Note that if there were more than 100 categories these constraints could not be met, and that even with less than 100 there's potential for distortion of the results. For example, consider the following two sets of "real" percentages: 98.1, 1.9, 0, 0 99.6, 0.2, 0.1, 0.1 The first would, quite reasonably, be represented as 98, 2, 0, 0. But to meet all the constraints, the second would have to be represented as 97, 1, 1, 1. So although the first value is 1.5% higher in the second set of figures, it renders as 1% lower. Although this is annoying, it's considered worth it because the other constraints are so useful, and the inaccuracy is bounded by the number of values minus 1, so 3% in this example. In reality we're dealing with three numbers, so the worst inaccuracy in any figure would be 2%. Here's an algorithm we can use to get this behavior: 0) Identify which, if any, nonzero values are less than 1% of the total. Calculate what you would have to increase them (and the total) to to make them at least 1%. Do it. To do this: - Solve the simultaneous equation 100n = mn + t, where m is the (known) number of values to be increased, n is the desired solution, and t is the total of all the other values. The solution goes like (100-m)n = t => n = t/(100-m). We can get this all in integers by multiplying *all* the values involved by 100-m, and setting the values-to-be-increased to t. - Note that this process may in theory adjust other values to be below 1% that previously weren't. In order to compensate for this, instead of just checking whether this value is less than 1% of the total, keep a running total of t and m and check whether the *adjusted* value is less than 1%, that is, whether value*(100-m) < t. Since m starts off as zero and t starts off as total, this degenerates to the right thing on the first pass. Repeat this pass until m doesn't get any larger over an entire iteration. - # Start off assuming nothing's less than 1%, and iterate until we find # that we're right. m is the number of adjustable (<1%) items, and t is # the total of the non-adjustable items. Lastm keeps track of what we # thought m was last time round. We loop until we do an entire pass without # m ending up different from lastm. Lastm starts off as -1 just so that the # m == lastm test fails first time around. t = total; m = 0; lastm = -1; until (m == lastm) { lastm = m; # Loop over the items that haven't already been marked adjustable. For # each such item, determine whether it needs to be adjustable based on # the current values of m and t. If it does, mark it as adjustable and # update m and t accordingly. foreach item { if (! adjustable(item)) { if (value(item) * (100-m) < t) { t -= value; m++; adjustable(item) = true; } } } } # Having calculated the final values of m and t, and also knowing exactly # which items are adjustable, we can now calculate the adjusted totals. # Non-adjustable items are scaled up by a constant factor; adjustable # items are all set to exactly 1% of the scaled total. adjtotal = 100 * t; foreach item { if (adjustable(item)) { adjvalue(item) = t; } else { adjvalue(item) = value(item) * (100-m); } } 1) Calculate the actual percentages (based on the adjusted numbers from step 0) to the best precision possible. 2) Round down to integers, but store the difference between the real and rounded value for each item. Assert that no nonzero real value should ever round to zero, because of step 0. 3) Sum the rounded values to find out how close we are to 100% (we must be <= 100% because we rounded everything down). # Calculate the percentage rounded *down* to the nearest integer, and also # calculate the magnitude of the difference between the integer percentage # and the actual percentage. This is still all done in integer math... # While we're at it, sum the percentages so we can see how close we got, # later. totalpct = 0; foreach item { percent(item) = (adjvalue(item) * 100) intdiv adjtotal; diff(item) = adjvalue(item) * 100 - percent(item) * adjtotal; totalpct += percent(item); } 4) Loop until we reach 100% doing the following: Pick the item with the largest stored difference, add 1 to its rounded figure, and set its difference to zero. # Find the items with the largest differences, and adjust them upwards, until # 100% is reached. No need to reset the difference since we're looping # through the items and will never repeat: it's easy to show that the upper # bound on the number of upwards adjustments needed is smaller than the # number of items. foreach (item sorted by diff(item), descending) { if (totalpct == 100) return; percent(item)++; totalpct++; } japitools-0.9.7/design/sample.html0000644000175000017500000000445007564456414017327 0ustar sballardsballard Sample of proposed japiohtml output

    Compatibility of FreeImpl with JDK1.x

    Summary

    Note that summary information is calculated prior to filtering the results (for duplicates, etc). It is possible to show a non-zero "bad" or "missing" score without any actual errors being listed.

    Good Bad Missing Abs.Add
    java.lang: 100%
    .
    java.lang.reflect: 90% 8% 2%
    . .
    java.lang.ref: 80% 10% 10% 7%
    . . . .
    japitools-0.9.7/design/side-by-side.txt0000644000175000017500000000321307566443542020173 0ustar sballardsballardThoughts on doing side-by-side comparisons of different free implementations versus different jdk versions. JDK1.1 JDK1.2 JDK1.3 java.lang Classpath |=|==| 100% |===| 90%/10% |==|=| 80%/20% Kaffe |=|==| 100% |===| 90%/10% |==|=| 80%/20% libgcj |=|==| 100% |===| 90%/10% |==|=| 80%/20% j.l.r.proxy Classpath |==|=| 80%/20% Kaffe |==|=| 80%/20% libgcj |==|=| 80%/20% javax.swing Classpath |===| 90%/10% |==|=| 80%/20% Kaffe |===| 90%/10% |==|=| 80%/20% libgcj |===| 90%/10% |==|=| 80%/20% Put a 2-3px border around each Cp/k/lgcj cell in the "ok-xxpct" class to pick out the problematic items in red etc. Leave a slightly larger gap between packages than between implementations of the same package. Left-align the bar graphs so they can be compared. JDK11 JDK12 JDK13 Classpath Kaffe libgcj java.lang.Foo N/A N/A Present N/A Missing Missing N/A N/A static instance N/A N/A javax.swing N/A Present Present N/A Missing N/A javax.swing.Foo N/A Present Present Missing N/A N/A Print the N/A in a pale grey so that the stuff that isn't N/A stands out. The "was" stuff should be green, the "is" stuff red to indicate a problem. Perhaps the background itself should be red etc. Would be nice to correlate the colors with the class of error, but that would make missing items a boring grey... japitools-0.9.7/test/0000755000175000017500000000000010526243211014640 5ustar sballardsballardjapitools-0.9.7/test/net/0000755000175000017500000000000010526243211015426 5ustar sballardsballardjapitools-0.9.7/test/net/wuffies/0000755000175000017500000000000010526243211017076 5ustar sballardsballardjapitools-0.9.7/test/net/wuffies/japi/0000755000175000017500000000000010526243211020021 5ustar sballardsballardjapitools-0.9.7/test/net/wuffies/japi/AntUtils.java0000644000175000017500000002132510517000764022436 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2006 Jaroslav Tulach // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.net.InetAddress; import java.net.URL; import java.security.Permission; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.zip.GZIPInputStream; import junit.framework.Assert; import junit.framework.AssertionFailedError; import junit.framework.TestCase; /** Handy utilities for execution of Ant scripts from tests. * * @author Jaroslav Tulach */ final class AntUtils extends Object { private AntUtils () { } static File getWorkDir(TestCase t) throws IOException { String name = t.getName(); File f = File.createTempFile(name, ".dir"); f.delete(); f.mkdirs(); Assert.assertTrue("Directory created: " + f, f.isDirectory()); return f; } final static String readFile (java.io.File f, boolean gzip) throws java.io.IOException { if (!gzip) { int s = (int)f.length (); byte[] data = new byte[s]; Assert.assertEquals ("Read all data", s, new FileInputStream (f).read (data)); return new String (data); } else { GZIPInputStream is = new GZIPInputStream(new FileInputStream(f)); byte[] arr = new byte[256 * 256]; int first = 0; for(;;) { int len = is.read(arr, first, arr.length - first); if (first + len < arr.length) { return new String(arr, 0, first + len); } } } } final static File extractString(String res) throws Exception { File f = File.createTempFile("res", ".xml"); f.deleteOnExit (); return extractString(f, res); } final static File extractString (File f, String res) throws Exception { FileOutputStream os = new FileOutputStream(f); InputStream is = new ByteArrayInputStream(res.getBytes("UTF-8")); for (;;) { int ch = is.read (); if (ch == -1) break; os.write (ch); } os.close (); return f; } final static File extractResource(String res) throws Exception { URL u = AntUtils.class.getResource(res); Assert.assertNotNull ("Resource should be found " + res, u); File f = File.createTempFile("res", ".xml"); f.deleteOnExit (); FileOutputStream os = new FileOutputStream(f); InputStream is = u.openStream(); for (;;) { int ch = is.read (); if (ch == -1) break; os.write (ch); } os.close (); return f; } final static void execute (String res, String[] args) throws Exception { execute (extractResource (res), args); } private static ByteArrayOutputStream out; private static ByteArrayOutputStream err; final static String getStdOut() { return out.toString(); } final static String getStdErr() { return err.toString(); } final static void execute(File f, String[] args) throws Exception { // we need security manager to prevent System.exit if (! (System.getSecurityManager () instanceof MySecMan)) { out = new java.io.ByteArrayOutputStream (); err = new java.io.ByteArrayOutputStream (); System.setOut (new java.io.PrintStream (out)); System.setErr (new java.io.PrintStream (err)); System.setSecurityManager (new MySecMan ()); } MySecMan sec = (MySecMan)System.getSecurityManager(); List arr = new ArrayList(); arr.add ("-f"); arr.add (f.toString ()); arr.addAll(Arrays.asList(args)); out.reset (); err.reset (); try { sec.setActive(true); org.apache.tools.ant.Main.main ((String[])arr.toArray (new String[0])); } catch (MySecExc ex) { Assert.assertNotNull ("The only one to throw security exception is MySecMan and should set exitCode", sec.exitCode); ExecutionError.assertExitCode ( "Execution has to finish without problems", sec.exitCode.intValue () ); } finally { sec.setActive(false); } } static class ExecutionError extends AssertionFailedError { public final int exitCode; public ExecutionError (String msg, int e) { super (msg); this.exitCode = e; } public static void assertExitCode (String msg, int e) { if (e != 0) { throw new ExecutionError ( msg + " was: " + e + "\nOutput: " + out.toString () + "\nError: " + err.toString (), e ); } } } private static class MySecExc extends SecurityException { public void printStackTrace() { } public void printStackTrace(PrintStream ps) { } public void printStackTrace(PrintWriter ps) { } } private static class MySecMan extends SecurityManager { public Integer exitCode; private boolean active; public void checkExit (int status) { if (active) { exitCode = new Integer (status); throw new MySecExc (); } } public void checkPermission(Permission perm, Object context) { } public void checkPermission(Permission perm) { /* if (perm instanceof RuntimePermission) { if (perm.getName ().equals ("setIO")) { throw new MySecExc (); } } */ } public void checkMulticast(InetAddress maddr) { } public void checkAccess (ThreadGroup g) { } public void checkWrite (String file) { } public void checkLink (String lib) { } public void checkExec (String cmd) { } public void checkDelete (String file) { } public void checkPackageAccess (String pkg) { } public void checkPackageDefinition (String pkg) { } public void checkPropertyAccess (String key) { } public void checkRead (String file) { } public void checkSecurityAccess (String target) { } public void checkWrite(FileDescriptor fd) { } public void checkListen (int port) { } public void checkRead(FileDescriptor fd) { } public void checkAccess (Thread t) { } public void checkConnect (String host, int port, Object context) { } public void checkRead (String file, Object context) { } public void checkConnect (String host, int port) { } public void checkAccept (String host, int port) { } public void checkMemberAccess (Class clazz, int which) { } public void checkSystemClipboardAccess () { } public void checkSetFactory () { } public void checkCreateClassLoader () { } public void checkAwtEventQueueAccess () { } public void checkPrintJobAccess () { } public void checkPropertiesAccess () { } void setActive(boolean b) { active = b; } } // end of MySecMan } japitools-0.9.7/test/net/wuffies/japi/JapiantTaskTest.java0000644000175000017500000000736310517000764023752 0ustar sballardsballard/////////////////////////////////////////////////////////////////////////////// // Japize - Output a machine-readable description of a Java API. // Copyright (C) 2006 Jaroslav Tulach // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package net.wuffies.japi; import java.io.File; import java.util.zip.GZIPInputStream; import junit.framework.TestCase; import junit.framework.*; import org.apache.tools.ant.Task; /** * * @author Jaroslav Tulach */ public class JapiantTaskTest extends TestCase { public JapiantTaskTest(String testName) { super(testName); } protected void setUp() throws Exception { } protected void tearDown() throws Exception { } public void testSupportsNonRecursivePackages() throws Exception { doCompute("x.y.*", false); } public void testSupportsRecursivePackages() throws Exception { doCompute("x.y.**", true); } public void testSupportsListOfPackages() throws Exception { doCompute("x.y.*, x.y.subpkg.*", true); } private void doCompute(String pkgs, boolean expectSubpkg) throws Exception { File sources = new File(AntUtils.getWorkDir(this), "src"); sources.mkdirs(); File build = new File(AntUtils.getWorkDir(this), "build"); AntUtils.extractString(new File(sources, "A.java"), "package x.y;" + "public class A {" + "}" + "" ); AntUtils.extractString(new File(sources, "B.java"), "package x.y.subpkg;" + "public class B {" + "}" + "" ); File out = new File(sources, "out.japi"); out.delete(); String script = "" + "" + " " + "" + " " + " " + " " + " " + " " + " " + " " + "" + ""; File buildScript = AntUtils.extractString(script); AntUtils.execute (buildScript, new String[] { "-verbose" }); assertTrue("Output generated: " + out, out.isFile()); String txt = AntUtils.readFile(out, false); if (!expectSubpkg) { if (txt.indexOf("subpkg") >= 0) { fail("No subpkg should be there:\n" + txt); } } else { if (txt.indexOf("subpkg") < 0) { fail("subpkg should be there:\n" + txt); } } if (txt.indexOf("x.y") < 0) { fail("x.y should be there:\n" + txt); } } }