blob: 8839a8ccc395826c523adb3bc9ddd350895f389b [file] [log] [blame]
#!/usr/bin/perl
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# Compare the native API with native methods in the Java API.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# TODO:
# - use method name plus return type plus args as key instead of
# only method name
# -----------------------------------------------------------------------------
#
use strict;
# Modules needed, should all be standard
# Commandline option parsing
use Getopt::Std;
# dirname function
use File::Basename;
# Traverse through directory structure and execute
# callback for each file
use File::Find;
# Platform agnostic concatenation of directory and file names
use File::Spec::Functions;
################### BEGIN VARIABLES WHICH MUST BE MAINTAINED #####################
# Script version, printed via getopts with "--version"
our $VERSION = '1.0';
# Sub directories containing c native API and Java classes.
# Path relative to tcnative main project directory.
my $C_NATIVE_SUBDIR = 'native';
my $JAVA_API_SUBDIR = 'java';
# Macro used for marking C native API
my $C_NATIVE_DECLARATION = 'TCN_IMPLEMENT_CALL';
# Macro used for marking C native API arguments
my $C_NATIVE_ARGUMENTS = 'TCN_STDARGS';
################### END VARIABLES WHICH MUST BE MAINTAINED #####################
# Global data variables
# Print verbose output?
my $verbose = 0;
# Signature structures
my %cSignature;
my %javaSignature;
# Usage/Help
sub HELP_MESSAGE {
my $fh = shift;
print $fh "Usage:: $0 [ -h ] [ -v ] [ -d DIRECTORY ]\n";
print $fh " -h: Print help\n";
print $fh " -v: Verbose output\n";
print $fh " -d DIRECTORY: Path to tcnative project main directory.\n";
print $fh " Default is '..\\..' relative to the script directory \n";
}
# Parse arguments:
# -h: help
# -v: verbose
# -d: tcnative project main directory
$Getopt::Std::STANDARD_HELP_VERSION = 1;
our ($opt_h, $opt_v, $opt_d);
getopts('hvd:');
if ($opt_h) {
HELP_MESSAGE(*STDOUT);
exit 1;
}
if ($opt_v) {
$verbose = 1;
}
my $directory = $opt_d;
if (!defined($directory) || $directory eq '') {
$directory = dirname($0);
$directory = catfile($directory, ('..', '..'));
}
if (! -d $directory) {
print STDERR "Invalid tcnative project directory '$directory' - Aborting!\n";
exit 1;
}
sub mapTypes {
my $types = shift;
# Replace "Array" by "[]"
$types =~ s/Array/\\[\\]/g;
# Remove leading "j" from scalar types
$types =~ s/j(byte|short|int|long|boolean)/$1/g;
# Replace string type
$types =~ s/jstring/String/g;
# Replace object type with pattern
$types =~ s/jobject/([A-Z][A-Za-z0-9]*|[a-z]+\\[\\])/g;
return $types;
}
# Extract C native API from one file
sub cNativeApi {
# Only check C source files
return if $_ !~ /\.c$/;
my $file = $_;
my ($m, $signature, $type, $args, $ret, $class, $method);
print "Checking C API file $file\n" if $verbose;
open(IN, "<$file") or do {
print "ERROR: Ignoring file '$file', could not open file for read: $!\n";
return;
};
while(<IN>) {
# Example declaration:
#TCN_IMPLEMENT_CALL(jlong, Socket, create)(TCN_STDARGS, jint family,
# jint type, jint protocol,
# jlong pool)
if ($_ =~ /^\s*$C_NATIVE_DECLARATION\s*/o) {
chomp();
$signature = $_;
# Concat next line until signature is complete
while($signature !~ /\([^\)]*\)\s*\([^\)]*\)/ && $signature !~ /;/ && ($_ = <IN>)) {
chomp();
$signature .= $_;
}
# Strip C-style comments. See: "perldoc -q comment"
$signature =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
# Save one declaration
if ($signature =~ /^\s*$C_NATIVE_DECLARATION\s*\(([^\)]*)\)\s*\(([^\)]*)\)/) {
($type, $args) = ($1, $2);
# Normalize return type and method name
# Collapse multiple spaces
$type =~ s/\s+/ /g;
# Remove spaces around commas
$type =~ s/, /,/g;
$type =~ s/ ,/,/g;
# Trim spaces at start and end
$type =~ s/^ //;
$type =~ s/ $//;
($ret, $class, $method) = split(/,/, $type);
# Map return type
$ret = mapTypes($ret);
# Normalize argument list
# Collapse multiple spaces
$args =~ s/\s+/ /g;
# Remove spaces around commas
$args =~ s/, /,/g;
$args =~ s/ ,/,/g;
# Remove argument names, only types are needed
$args =~ s/ [^, ]+//g;
# Trim spaces at start and end
$args =~ s/^ //;
$args =~ s/ $//;
# Remove leading JNI arguments macro
$args =~ s/^$C_NATIVE_ARGUMENTS,?//o;
# Map argument list types
$args = mapTypes($args);
$m = $cSignature{$class};
if (!defined($m)) {
$m = $cSignature{$class} = {};
}
if (exists($m->{$method}) && ($m->{$method}->{return} ne $ret || $m->{$method}->{args} ne $args)) {
print "ERROR: C native file $file method $method in class $class will be overwritten!";
print "\tOld signature: '$m->{$method}->{return} $m->{$method}->{method}($m->{$method}->{args})\n";
print "\tNew signature: '$ret $method($args)\n";
}
# XXX Use method plus ret plus args as key instead
$m = $m->{$method} = {};
$m->{method} = $method;
$m->{return} = $ret;
$m->{args} = $args;
print "\tFound C API in $file class $class: '$m->{return} $m->{method}($m->{args})'\n" if $verbose;
} else {
print "ERROR: Incomplete C signature in file $file ignored in '$signature'";
}
}
}
close(IN);
}
# Extract native methods in Java API from one file
sub javaNativeApi {
# Only check Java source files
return if $_ !~ /\.java$/;
my $file = $_;
my ($m, $signature, $type, $args, $ret, $class, $method);
print "Checking Java API file $file\n" if $verbose;
open(IN, "<$_") or do {
print "ERROR: Ignoring file '$file', could not open file for read: $!\n";
return;
};
$class = $file;
$class =~ s/\.java$//;
while(<IN>) {
# Example declaration
#public static native long uid(String username, long p)
# throws Error;
if ($_ =~ /^\s*((public|protected|private)\s+)?([^\(]*)\(/ && ($type = $3) && $type =~ /\snative\s/) {
chomp();
$signature = $_;
# Concat next line until signature is complete
while($signature !~ /\([^\)]*\)/ && $signature !~ /;/ && ($_ = <IN>)) {
chomp();
$signature .= $_;
}
# Save one declaration
if ($signature =~ /^\s*([^\(]+)\(([^\)]*)\)/) {
($type, $args) = ($1, $2);
# Normalize return type and method name
# Remove unused specifiers
$type =~ s/\b(public|protected|private)\b//g;
$type =~ s/\bnative\b//g;
$type =~ s/\bstatic\b//g;
# Collapse multiple spaces
$type =~ s/\s+/ /g;
# Trim spaces at start and end
$type =~ s/^ //;
$type =~ s/ $//;
($ret, $method) = split(/\s+/, $type);
# Normalize argument list
# Collapse multiple spaces
$args =~ s/\s+/ /g;
# Remove spaces around commas
$args =~ s/, /,/g;
$args =~ s/ ,/,/g;
# Remove spaces in front of array "[]"
$args =~ s/ \[\]/[]/g;
# Remove argument names, only types are needed
$args =~ s/ [^, ]+//g;
# Trim spaces at start and end
$args =~ s/^ //;
$args =~ s/ $//;
$m = $javaSignature{$class};
if (!defined($m)) {
$m = $javaSignature{$class} = {};
}
if (exists($m->{$method})) {
print "ERROR: Java file $file method $method in class $class will be overwritten!";
print "\tOld signature: '$m->{$method}->{return} $m->{$method}->{method}($m->{$method}->{args})\n";
print "\tNew signature: '$ret $method($args)\n";
}
# XXX Use method plus ret plus args as key instead
$m = $m->{$method} = {};
$m->{method} = $method;
$m->{return} = $ret;
$m->{args} = $args;
print "\tFound native Java API in $file class $class: '$m->{return} $m->{method}($m->{args})'\n" if $verbose;
} else {
print "ERROR: Incomplete Java signature in file $file ignored in '$signature'";
}
}
}
close(IN);
}
# Search for C native API
find(\&cNativeApi, catfile($directory, $C_NATIVE_SUBDIR));
# Search for native methods in Java API
find(\&javaNativeApi, catfile($directory, $JAVA_API_SUBDIR));
print "Comparing C and Java APIs...\n";
# Check whether all native methods have correct Java counterparts
for my $class (sort keys %cSignature) {
my $c = $cSignature{$class};
my $j = $javaSignature{$class};
if (!defined($j)) {
print "ERROR: No Java API defined for class '$class'\n";
for my $method (sort keys %{$c}) {
my $m = $c->{$method};
print "\tMissing Java method: $m->{return} $m->{method}($m->{args})\n";
}
} else {
for my $method (sort keys %{$c}) {
my $m = $c->{$method};
if (!exists($j->{$method})) {
print "ERROR: Missing Java method in class '$class': $m->{return} $m->{method}($m->{args})\n";
} else {
my $n = $j->{$method};
if ($n->{return} !~ /^$m->{return}$/ || $n->{args} !~ /^$m->{args}$/) {
print "ERROR: Incompatible API in class '$class'!\n";
print "\tC signature: $m->{return} $m->{method}($m->{args})\n";
print "\tJava signature: $n->{return} $n->{method}($n->{args})\n";
}
delete($j->{$method});
}
}
}
}
# Check whether any native Java API methods remain
for my $class (sort keys %javaSignature) {
my $j = $javaSignature{$class};
my $c = $cSignature{$class};
if (!defined($c)) {
print "ERROR: No C API defined for class '$class'!\n";
for my $method (sort keys %{$j}) {
my $m = $j->{$method};
print "\tMissing C method: $m->{return} $m->{method}($m->{args})\n";
}
} else {
for my $method (sort keys %{$j}) {
my $m = $j->{$method};
if (!exists($c->{$method})) {
print "ERROR: Missing C method in class '$class': $m->{return} $m->{method}($m->{args})\n";
} else {
my $n = $c->{$method};
if ($m->{return} !~ /^$n->{return}$/ || $m->{args} !~ /^$n->{args}$/) {
print "ERROR: Incompatible API in class '$class'!\n";
print "\tC signature: $n->{return} $n->{method}($n->{args})\n";
print "\tJava signature: $m->{return} $m->{method}($m->{args})\n";
}
delete($c->{$method});
}
}
}
}
print "Done.\n";