japitools-0.9.7/ 0000755 0001750 0001750 00000000000 10526243261 013666 5 ustar sballard sballard japitools-0.9.7/bin/ 0000755 0001750 0001750 00000000000 10526243211 014431 5 ustar sballard sballard japitools-0.9.7/bin/.run.bat 0000644 0001750 0001750 00000003446 10412547363 016023 0 ustar sballard sballard @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.sh 0000644 0001750 0001750 00000003735 07561562470 015677 0 ustar sballard sballard #!/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/japicompat 0000644 0001750 0001750 00000116226 10524167047 016525 0 ustar sballard sballard #!/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 =~ /^([^<>]+);
$mitem->{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 =~ /^([^<>]+);
$mitem->{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 =~ /^([^<>]+);
unless ($nitem->{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 =~ /^([^<>]+);
unless ($nitem->{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/japiextractpkgs 0000755 0001750 0001750 00000000171 10517553175 017576 0 ustar sballard sballard #!/usr/bin/perl
while (<>) {
print "=$1,\n" if /package-frame.html" (?:TARGET|target)="packageFrame">([^<]+)<\/A>/;
}
japitools-0.9.7/bin/japilist 0000644 0001750 0001750 00000005350 10316116735 016205 0 ustar sballard sballard #!/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/japiohtml 0000644 0001750 0001750 00000037305 10335652663 016370 0 ustar sballard sballard #!/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 "
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 <
$pkga:
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 <
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 <
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;
$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/japiotext 0000644 0001750 0001750 00000016755 10335652663 016416 0 ustar sballard sballard #!/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/japize 0000644 0001750 0001750 00000006401 10526243036 015644 0 ustar sballard sballard #!/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.bat 0000644 0001750 0001750 00000002726 07556010756 016431 0 ustar sballard sballard @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/japizejdks 0000755 0001750 0001750 00000003554 10311130500 016510 0 ustar sballard sballard #!/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/ 0000755 0001750 0001750 00000000000 10526243211 014450 5 ustar sballard sballard japitools-0.9.7/src/net/ 0000755 0001750 0001750 00000000000 10526243211 015236 5 ustar sballard sballard japitools-0.9.7/src/net/wuffies/ 0000755 0001750 0001750 00000000000 10526243211 016706 5 ustar sballard sballard japitools-0.9.7/src/net/wuffies/japi/ 0000755 0001750 0001750 00000000000 10526243211 017631 5 ustar sballard sballard japitools-0.9.7/src/net/wuffies/japi/ArrayType.java 0000644 0001750 0001750 00000004131 10315337652 022424 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000013751 10404333242 022345 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000004571 10404333242 023021 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000004155 10404333242 022513 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000005673 10404333242 023361 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000003074 10314136577 022727 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000127277 10526120766 022372 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000012301 10315337652 022411 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000003253 10334251201 023100 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000002716 10310622422 023062 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000003660 10311016023 023405 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000012275 10517000764 022720 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000154253 10526230672 021737 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000002235 10314136577 023541 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000005173 10315337652 023325 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000002124 10313077663 022063 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000011661 10316617261 021431 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000013174 10316617261 022413 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000002704 10312316547 023076 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000005326 10520727635 023110 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000002156 10310622422 022114 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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/.cvsignore 0000644 0001750 0001750 00000000036 10413255766 015675 0 ustar sballard sballard *.jar
*.class
net
build
share
japitools-0.9.7/COPYING 0000644 0001750 0001750 00000043110 07561562145 014732 0 ustar sballard sballard 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/Makefile 0000644 0001750 0001750 00000000421 10310622422 015312 0 ustar sballard sballard all: 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/README 0000644 0001750 0001750 00000002653 10142441626 014553 0 ustar sballard sballard This 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.xml 0000644 0001750 0001750 00000015425 10517000764 015515 0 ustar sballard sballard
japitools-0.9.7/design/ 0000755 0001750 0001750 00000000000 10526243211 015132 5 ustar sballard sballard japitools-0.9.7/design/ANNOUNCEMENT 0000644 0001750 0001750 00000005124 07561760756 017000 0 ustar sballard sballard JAPITOOLS 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/TODO 0000644 0001750 0001750 00000027474 10233004675 015644 0 ustar sballard sballard * 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 xxx; 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.txt 0000644 0001750 0001750 00000035003 10334251201 020354 0 ustar sballard sballard To 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 this japitools-0.9.7/design/japi-spec-0.9.7.txt 0000644 0001750 0001750 00000060560 10413241064 020225 0 ustar sballard sballard This 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.txt 0000644 0001750 0001750 00000034110 10144734126 017553 0 ustar sballard sballard This 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.css 0000644 0001750 0001750 00000003071 10334251636 016577 0 ustar sballard sballard /* 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.txt 0000644 0001750 0001750 00000007221 07575204656 017753 0 ustar sballard sballard Requirements:
- 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.txt 0000644 0001750 0001750 00000004102 07566443542 020251 0 ustar sballard sballard Take 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.txt 0000644 0001750 0001750 00000006650 07507112770 017711 0 ustar sballard sballard Here'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.txt 0000644 0001750 0001750 00000005641 07507112770 017707 0 ustar sballard sballard To 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.txt 0000644 0001750 0001750 00000012217 07564456414 021204 0 ustar sballard sballard For 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.html 0000644 0001750 0001750 00000004450 07564456414 017327 0 ustar sballard sballard
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.txt 0000644 0001750 0001750 00000003213 07566443542 020173 0 ustar sballard sballard Thoughts 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/ 0000755 0001750 0001750 00000000000 10526243211 014640 5 ustar sballard sballard japitools-0.9.7/test/net/ 0000755 0001750 0001750 00000000000 10526243211 015426 5 ustar sballard sballard japitools-0.9.7/test/net/wuffies/ 0000755 0001750 0001750 00000000000 10526243211 017076 5 ustar sballard sballard japitools-0.9.7/test/net/wuffies/japi/ 0000755 0001750 0001750 00000000000 10526243211 020021 5 ustar sballard sballard japitools-0.9.7/test/net/wuffies/japi/AntUtils.java 0000644 0001750 0001750 00000021325 10517000764 022436 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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.java 0000644 0001750 0001750 00000007363 10517000764 023752 0 ustar sballard sballard ///////////////////////////////////////////////////////////////////////////////
// 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);
}
}
}