/*
* Copyright 2005 The Apache Software Foundation.
*
* Licensed 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.
*/
package org.apache.jdo.tck.api;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.Map;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.TreeSet;
import java.util.HashSet;
import java.util.HashMap;
import java.text.ParseException;
import java.io.PrintWriter;
import java.io.IOException;
import java.io.File;
import java.io.FileReader;
import java.io.LineNumberReader;
/**
* A helper class for translating between Java user type names and
* reflection type names.
*/
class TypeHelper {
/** Error message for format errors with a user type name. */
static private final String MSG_ILLEGAL_USR_TYPE
= "illegal user type name: ";
/** Error message for format errors with a reflection type name. */
static private final String MSG_ILLEGAL_RFL_TYPE
= "illegal reflection type name: ";
/** Throws an IllegalArgumentException if a given condition is violated. */
static private void check(boolean cond, String msg) {
if (!cond) throw new IllegalArgumentException(msg);
}
/** Maps primitive reflection type names to (Java) user names. */
static private final HashMap userTypeNames = new HashMap();
/** Maps primitive (Java) user type names to reflection names. */
static private final HashMap reflectionTypeNames = new HashMap();
/** Maps primitive type names to class objects. */
static private final HashMap primitiveClasses = new HashMap();
// initializes the type maps
static {
userTypeNames.put("B", "byte");
userTypeNames.put("C", "char");
userTypeNames.put("D", "double");
userTypeNames.put("F", "float");
userTypeNames.put("I", "int");
userTypeNames.put("J", "long");
userTypeNames.put("S", "short");
userTypeNames.put("Z", "boolean");
userTypeNames.put("V", "void");
for (Iterator i = userTypeNames.entrySet().iterator();
i.hasNext();) {
final Map.Entry e = (Map.Entry)i.next();
reflectionTypeNames.put(e.getValue(), e.getKey());
}
primitiveClasses.put("byte", byte.class);
primitiveClasses.put("char", char.class);
primitiveClasses.put("double", double.class);
primitiveClasses.put("float", float.class);
primitiveClasses.put("int", int.class);
primitiveClasses.put("long", long.class);
primitiveClasses.put("short", short.class);
primitiveClasses.put("boolean", boolean.class);
primitiveClasses.put("void", void.class);
}
/** Returns the (Java) user name for a reflection type name. */
static public String userTypeName(String name) {
check(name != null, MSG_ILLEGAL_RFL_TYPE + name);
// count array dimensions from start
final int n = name.length();
check(n > 0, MSG_ILLEGAL_RFL_TYPE + name);
int i = 0;
final StringBuffer sb = new StringBuffer();
while (name.charAt(i) == '[') {
sb.append("[]");
i++;
check(i < n, MSG_ILLEGAL_RFL_TYPE + name);
}
// no translation of primitive type names if not an array type
if (i == 0) {
return name;
}
// translate and recompose name
final String s;
if (name.charAt(i) == 'L') {
check(name.endsWith(";"), MSG_ILLEGAL_RFL_TYPE + name);
s = name.substring(i + 1, n - 1);
} else {
s = (String)userTypeNames.get(name.substring(i));
check(s != null, MSG_ILLEGAL_RFL_TYPE + name);
}
return (s + sb.toString());
}
/** Returns the (Java) user names for reflection type names. */
static public String[] userTypeNames(String[] names) {
final String[] u = new String[names.length];
for (int i = names.length - 1; i >= 0; i--) {
u[i] = userTypeName(names[i]);
}
return u;
}
/** Returns the reflection name for a (Java) user type name. */
static public String reflectionTypeName(String name) {
check(name != null, MSG_ILLEGAL_USR_TYPE + name);
// count array dimensions from end
final int n = name.length();
check(n > 0, MSG_ILLEGAL_USR_TYPE + name);
int i = n - 1;
final StringBuffer sb = new StringBuffer();
while (name.charAt(i) == ']') {
i--;
check(name.charAt(i) == '[', MSG_ILLEGAL_USR_TYPE + name);
sb.append("[");
i--;
check(i >= 0, MSG_ILLEGAL_USR_TYPE + name);
}
// no translation of primitive type names if not an array type
if (++i == n) {
return name;
}
// translate and recompose name
final String s = name.substring(0, i);
final String p = (String)reflectionTypeNames.get(s);
return sb.append(p != null ? p : "L" + s + ";").toString();
}
/** Returns the (Java) user names for reflection type names. */
static public String[] reflectionTypeNames(String[] names) {
final String[] r = new String[names.length];
for (int i = names.length - 1; i >= 0; i--) {
r[i] = reflectionTypeName(names[i]);
}
return r;
}
/**
* Returns the class object for a primitive type name, or
* null if the name does not denote a primitive type
* (class objects of primitive types cannot be loaded with reflection).
*/
static public Class primitiveClass(String name) {
return (Class)primitiveClasses.get(name);
}
/** Tests if a name denotes a primitive type. */
static public boolean isPrimitive(String name) {
return primitiveClasses.containsKey(name);
}
/** Returns the component type name of a (Java) user type name. */
static public String componentUserTypeName(String name) {
check(name != null, MSG_ILLEGAL_USR_TYPE + name);
final int n = name.length();
check(n > 0, MSG_ILLEGAL_USR_TYPE + name);
final int i = name.indexOf('[');
if (i >= 0) {
check(i > 0, MSG_ILLEGAL_USR_TYPE + name);
name = name.substring(0, i);
}
return name;
}
/**
* Returns the java.lang.-qualified name for a given
* unqualified (Java) user type name.
*/
static public String qualifiedUserTypeName(String name) {
final String c = componentUserTypeName(name);
return ((isPrimitive(c) || c.indexOf('.') >= 0)
? name : "java.lang." + name);
}
/**
* Returns the java.lang.-qualified names for given
* unqualified (Java) user type names.
*/
static public String[] qualifiedUserTypeNames(String[] names) {
final String[] q = new String[names.length];
for (int i = names.length - 1; i >= 0; i--) {
q[i] = qualifiedUserTypeName(names[i]);
}
return q;
}
/**
* Compares a type name with a class objects for equality in the name.
*/
static public boolean isNameMatch(String userTypeName, Class cls) {
final String c = (cls == null ? null : userTypeName(cls.getName()));
return (userTypeName == null ? (c == null) : userTypeName.equals(c));
}
/**
* Compares an array of type names with an array of class objects
* for set-equality in the names (i.e., ignoring order).
*/
static public boolean isNameMatch(String[] userTypeName, Class[] cls) {
final Set s = new HashSet(Arrays.asList(userTypeName));
for (int i = cls.length - 1; i >= 0; i--) {
if (!s.remove(userTypeName(cls[i].getName()))) {
return false;
}
}
return s.isEmpty();
}
}
/**
* A helper class for building exhaustive string descriptions of
* class, field, constructor, or method declarations.
*
* @author Martin Zaun
*/
class Formatter {
/**
* Returns a string formatting an array of names as
* comma-separated list.
*/
static public String toString(String prefix, String[] names) {
final StringBuffer s = new StringBuffer();
if (names != null && names.length > 0) {
s.append(prefix == null ? "" : prefix).append(names[0]);
for (int i = 1; i < names.length; i++) {
s.append(", ").append(names[i]);
}
}
return s.toString();
}
/**
* Returns a string formatting an array of class objects as
* comma-separated list of (Java) user type names.
*/
static public String toString(String prefix, Class[] cls) {
final StringBuffer s = new StringBuffer();
if (cls != null && cls.length > 0) {
String n = TypeHelper.userTypeName(cls[0].getName());
s.append(prefix == null ? "" : prefix).append(n);
for (int i = 1; i < cls.length; i++) {
n = TypeHelper.userTypeName(cls[i].getName());
s.append(", ").append(n);
}
}
return s.toString();
}
/**
* Returns an exhaustive string description of a Field
* presenting types as (Java) user type names.
*/
static public String toString(Field field, Object value) {
final StringBuffer s = new StringBuffer();
s.append(Modifier.toString(field.getModifiers())).append(" ");
s.append(TypeHelper.userTypeName(field.getType().getName()));
s.append(" ");
s.append(field.getName());
s.append(value == null ? "" : " = " + value);
return s.toString();
}
/**
* Returns an combined string description of a field declaration.
*/
static public String toString(int mods, String type, String name,
String value) {
final StringBuffer s = new StringBuffer();
s.append(Modifier.toString(mods)).append(" ");
s.append(type).append(" ");
s.append(name);
s.append(value == null ? "" : " = " + value);
return s.toString();
}
/**
* Returns an exhaustive string description of a
* Constructor presenting types as (Java) user type names.
*/
static public String toString(Constructor ctor) {
final StringBuffer s = new StringBuffer();
s.append(Modifier.toString(ctor.getModifiers())).append(" ");
s.append(ctor.getName()).append("(");
s.append(toString("", ctor.getParameterTypes())).append(")");
s.append(toString(" throws ", ctor.getExceptionTypes()));
return s.toString();
}
/**
* Returns an exhaustive string description of a Method
* presenting types as (Java) user type names.
*/
static public String toString(Method method) {
final StringBuffer s = new StringBuffer();
s.append(Modifier.toString(method.getModifiers())).append(" ");
final String r = method.getReturnType().getName();
s.append(TypeHelper.userTypeName(r)).append(" ");
s.append(method.getName()).append("(");
s.append(toString("", method.getParameterTypes())).append(")");
s.append(toString(" throws ", method.getExceptionTypes()));
return s.toString();
}
/**
* Returns an combined string description of a constructor or
* method declaration.
*/
static public String toString(int mods, String result, String name,
String[] params, String[] excepts) {
final StringBuffer s = new StringBuffer();
s.append(Modifier.toString(mods)).append(" ");
s.append(result == null ? "" : result).append(" ");
s.append(name).append("(").append(toString("", params)).append(")");
s.append(toString(" throws ", excepts));
return s.toString();
}
/**
* Returns an exhaustive string description of a Class
* presenting types as (Java) user type names.
*/
static public String toString(Class cls) {
final StringBuffer s = new StringBuffer();
s.append(Modifier.toString(cls.getModifiers()));
s.append(cls.isInterface() ? " " : " class ").append(cls.getName());
final Class superc = cls.getSuperclass();
final Class[] interf = cls.getInterfaces();
if (cls.isInterface()) {
s.append(toString(" extends ", interf));
} else {
s.append(superc == null ? "" : " extends " + superc.getName());
s.append(toString(" implements ", interf));
}
return s.toString();
}
/**
* Returns an combined string description of a class header declaration.
*/
static public String toString(int mods, String name,
String[] ext, String[] impl) {
final StringBuffer s = new StringBuffer();
s.append(Modifier.toString(mods));
final boolean isInterface = ((mods & Modifier.INTERFACE) != 0);
s.append(isInterface ? " " : " class ").append(name);
s.append(toString(" extends ", ext));
s.append(toString(" implements ", impl));
return s.toString();
}
/**
* Returns an exhaustive string description of a Member
* presenting types as (Java) user type names.
*/
static public String toString(Member member) {
final String s;
if (member instanceof Field) {
s = toString((Field)member, null);
} else if (member instanceof Constructor) {
s = toString((Constructor)member);
} else if (member instanceof Method) {
s = toString((Method)member);
} else {
s = null;
}
return s;
}
}
/**
* Tests classes for correct signatures.
*
* @author Martin Zaun
*/
public class SignatureTest {
/** The new-line character on this system. */
static protected final String NL = System.getProperty("line.separator");
/** A writer for standard output. */
protected PrintWriter out = new PrintWriter(System.out, true);
/** A writer for error output. */
protected PrintWriter err = new PrintWriter(System.err, true);
/** The parse to be used for parsing signature descriptor files. */
protected final Parser parser = new Parser();
/** The classloader to be used for loading types. */
protected final ClassLoader classLoader;
/** The currently tested Class. */
protected Class cls;
/** All untested, declared members of the current class. */
protected final Set members = new HashSet();
/** Collects names of loadable classes. */
private final Set loading = new TreeSet();
/** Collects names of unloadable classes. */
private final Set notLoading = new TreeSet();
/** Counts tested features (class, constructor, fields, methods). */
private int tested;
/** Counts missing members (constructor, fields, methods). */
private int missing;
/** Counts non-matching features (class, constructor, fields, methods). */
private int mismatch;
/** Counts matching features (class, constructor, fields, methods). */
private int matching;
/** Counts public non-standard members (constructor, fields, methods). */
private int nonStandard;
/** Counts other reported problems (e.g., accessing field values). */
private int otherProblems;
/** Constructs a test instance. */
public SignatureTest() {
classLoader = this.getClass().getClassLoader();
}
// ----------------------------------------------------------------------
// Test Methods
// ----------------------------------------------------------------------
/**
* Tests the signature of classes (in the classpath) against a
* list of signature descriptor files; returns with a status code.
* @param descrFileNames list of signature descriptor file names
* @return zero if all tests have passed and no problems were detected
*/
public int test(List descrFileNames)
throws IOException, ParseException {
// clear statistics
loading.clear();
notLoading.clear();
tested = 0;
missing = 0;
mismatch = 0;
matching = 0;
nonStandard = 0;
otherProblems = 0;
// process descriptor files
parser.parse(descrFileNames);
report();
// return a positive value in case of any problems
return (notLoading.size() + missing + mismatch + nonStandard
+ otherProblems);
}
/**
* Tests the signature of classes (in the classpath) against a
* list of signature descriptor files.
*/
public void report() {
out.println();
out.println();
out.println("Signature Test Results");
out.println("======================");
out.println();
out.println(" tested features:\t\t" + tested);
out.println();
out.println("Successes:");
out.println(" matching features:\t\t" + matching);
out.println(" loadable classes:\t\t" + loading.size());
out.println();
out.println("Failures:");
out.println(" missing features:\t\t" + missing);
out.println(" non-matching features:\t" + mismatch);
out.println(" non-standard features:\t" + nonStandard);
out.println(" unloadable classes:\t\t" + notLoading.size());
out.println(" other problems:\t\t" + otherProblems);
}
// ----------------------------------------------------------------------
// Test Logic
// ----------------------------------------------------------------------
/** Handles class loading problems. */
protected void handleNotLoading(Throwable t) {
notLoading.add(t.getMessage().replace('/', '.'));
final String m = ("--- failed loading class;" + NL
+ " caught: " + t);
err.println(m);
}
/** Handles missing members. */
protected void handleMissing(String msg, String exp) {
missing++;
final String m = ("--- " + msg + NL
+ " expected: " + exp + NL
+ " class: " + Formatter.toString(cls));
err.println(m);
}
/** Handles non-matching features. */
protected void handleMismatch(String msg, String exp, String fnd) {
mismatch++;
final String m = ("--- " + msg + NL
+ " expected: " + exp + NL
+ " found: " + fnd + NL
+ " class: " + Formatter.toString(cls));
err.println(m);
}
/** Handles public non-standard features. */
protected void handleNonStandard(String msg, String fnd) {
nonStandard++;
final String m = ("--- " + msg + NL
+ " found: " + fnd + NL
+ " class: " + Formatter.toString(cls));
err.println(m);
}
/** Handles other problems. */
protected void handleProblem(String msg, String exp) {
otherProblems++;
final String m = ("--- " + msg + NL
+ " expected: " + exp + NL
+ " class: " + Formatter.toString(cls));
err.println(m);
}
/** Handles a perfect feature match. */
protected void handleMatch(String msg, String fnd) {
matching++;
final String m = ("+++ " + msg + fnd);
out.println(m);
}
/** Returns the class objects for given (Java) user type names. */
protected Class[] getClasses(String[] userTypeName) {
final Class[] cls = new Class[userTypeName.length];
for (int i = userTypeName.length - 1; i >= 0; i--) {
cls[i] = getClass(userTypeName[i]);
}
return cls;
}
/** Returns the class object for a given (Java) user type name. */
protected Class getClass(String userTypeName) {
// use helper for retrieving class objects for primitive types
Class cls = TypeHelper.primitiveClass(userTypeName);
if (cls != null) {
return cls;
}
// load class
try {
final String r = TypeHelper.reflectionTypeName(userTypeName);
cls = classLoader.loadClass(r);
loading.add(userTypeName);
} catch (LinkageError err) {
handleNotLoading(err);
} catch (ClassNotFoundException ex) {
handleNotLoading(ex);
}
return cls;
}
/** Validates a field against a prescribed signature. */
protected void checkField(int mods, String type, String name,
String value) {
tested++;
type = TypeHelper.qualifiedUserTypeName(type);
// get field
final Field field;
try {
field = cls.getDeclaredField(name);
} catch (NoSuchFieldException ex) {
handleMissing(
"missing field: ",
Formatter.toString(mods, type, name, value));
return;
} catch (LinkageError err) {
handleNotLoading(err);
return;
}
// check modifiers
if (cls.isInterface()) {
// fields interfaces are implicitly public, static, and final
mods |= Modifier.PUBLIC;
mods |= Modifier.STATIC;
mods |= Modifier.FINAL;
}
if (mods != field.getModifiers()) {
handleMismatch(
"field declaration: non-matching modifiers;",
Formatter.toString(mods, type, name, null),
Formatter.toString(field, null));
}
// check type
if (!TypeHelper.isNameMatch(type, field.getType())) {
handleMismatch(
"field declaration: non-matching type;",
Formatter.toString(mods, type, name, null),
Formatter.toString(field, null));
}
// check field value if any
Object fieldValue = null;
if (value != null) {
// only support for public, static, and final fields
final int m = (Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
if ((mods & m) == 0) {
handleProblem("field declaration: ignoring field value "
+ "definition in descriptor file;",
Formatter.toString(mods, type, name, value));
} else {
// only support for primitive types and String
final Object exp;
if (type.equals("byte")) {
exp = Byte.valueOf(value);
} else if (type.equals("short")) {
exp = Short.valueOf(value);
} else if (type.equals("integer")) {
exp = Integer.valueOf(value);
} else if (type.equals("long")) {
exp = Long.valueOf(value);
} else if (type.equals("float")) {
exp = Float.valueOf(value);
} else if (type.equals("double")) {
exp = Double.valueOf(value);
} else if (type.equals("char")) {
// cut off '\'' char at begin and end
exp = Character.valueOf(value.charAt(1));
} else if (type.equals("java.lang.String")) {
// cut off '\"' chars at begin and end
final String s = value.substring(1, value.length() - 1);
exp = String.valueOf(s);
} else {
exp = null;
}
// compare field's expected with found value
try {
fieldValue = field.get(null);
} catch (IllegalAccessException ex) {
handleProblem("field declaration: cannot access field "
+ "value, exception: " + ex + ";",
Formatter.toString(mods, type, name, value));
}
if (exp != null && !exp.equals(fieldValue)) {
handleMismatch(
"field declaration: non-matching values;",
Formatter.toString(mods, type, name, exp.toString()),
Formatter.toString(field, fieldValue));
}
}
}
// field OK
members.remove(field);
handleMatch("has field: ", Formatter.toString(field, fieldValue));
}
/** Validates a constructor against a prescribed signature. */
protected void checkConstructor(int mods, String[] params,
String[] excepts) {
tested++;
params = TypeHelper.qualifiedUserTypeNames(params);
excepts = TypeHelper.qualifiedUserTypeNames(excepts);
// get parameter classes
final Class[] prms = getClasses(params);
if (prms == null) {
return;
}
// get constructor
final Constructor ctor;
try {
ctor = cls.getDeclaredConstructor(prms);
} catch (NoSuchMethodException ex) {
String name = cls.getName();
final int i = name.lastIndexOf('.');
name = (i < 0 ? name : name.substring(i));
handleMissing(
"missing constructor: ",
Formatter.toString(mods, null, name, params, excepts));
return;
} catch (LinkageError err) {
handleNotLoading(err);
return;
}
// check modifiers
if (mods != ctor.getModifiers()) {
handleMismatch(
"constructor declaration: non-matching modifiers;",
Formatter.toString(mods, null, cls.getName(), params, excepts),
Formatter.toString(ctor));
}
// check exceptions
if (!TypeHelper.isNameMatch(excepts, ctor.getExceptionTypes())) {
handleMismatch(
"method declaration: non-matching exceptions;",
Formatter.toString(mods, null, cls.getName(), params, excepts),
Formatter.toString(ctor));
}
// constructor OK
members.remove(ctor);
handleMatch("has constructor: ", Formatter.toString(ctor));
}
/** Validates a method against a prescribed signature. */
protected void checkMethod(int mods, String result, String name,
String[] params, String[] excepts) {
tested++;
params = TypeHelper.qualifiedUserTypeNames(params);
excepts = TypeHelper.qualifiedUserTypeNames(excepts);
result = TypeHelper.qualifiedUserTypeName(result);
// get parameter classes
final Class[] prms = getClasses(params);
if (prms == null) {
return;
}
// get method
final Method method;
try {
method = cls.getDeclaredMethod(name, prms);
} catch (NoSuchMethodException ex) {
handleMissing(
"missing method: ",
Formatter.toString(mods, result, name, params, excepts));
return;
} catch (LinkageError err) {
handleNotLoading(err);
return;
}
// check modifiers
if (cls.isInterface()) {
// methods in interfaces are implicitly public and abstract final
mods |= Modifier.PUBLIC;
mods |= Modifier.ABSTRACT;
}
if (mods != method.getModifiers()) {
handleMismatch(
"method declaration: non-matching modifiers;",
Formatter.toString(mods, result, name, params, excepts),
Formatter.toString(method));
}
// check return type
if (!TypeHelper.isNameMatch(result, method.getReturnType())) {
handleMismatch(
"method declaration: non-matching return type",
Formatter.toString(mods, result, name, params, excepts),
Formatter.toString(method));
}
// check exceptions
if (!TypeHelper.isNameMatch(excepts, method.getExceptionTypes())) {
handleMismatch(
"method declaration: non-matching exceptions;",
Formatter.toString(mods, result, name, params, excepts),
Formatter.toString(method));
}
// method OK
members.remove(method);
handleMatch("has method: ", Formatter.toString(method));
}
/** Validates a class declaration against a prescribed signature. */
protected void checkClass(int mods, String name,
String[] ext, String[] impl) {
out.println();
out.println("testing " + Formatter.toString(mods, name, ext, impl));
tested++;
ext = TypeHelper.qualifiedUserTypeNames(ext);
impl = TypeHelper.qualifiedUserTypeNames(impl);
// get and assign currently processed class
cls = getClass(name);
if (cls == null) {
return; // can't load class
}
// collect all declared members of current class
members.clear();
try {
members.addAll(Arrays.asList(cls.getDeclaredFields()));
members.addAll(Arrays.asList(cls.getDeclaredConstructors()));
members.addAll(Arrays.asList(cls.getDeclaredMethods()));
} catch (LinkageError err) {
handleNotLoading(err);
}
// check modifiers
final boolean isInterface = ((mods & Modifier.INTERFACE) != 0);
if (isInterface) {
mods |= Modifier.ABSTRACT;
}
if (mods != cls.getModifiers()) {
handleMismatch(
"class declaration: non-matching modifiers;",
Formatter.toString(mods, name, ext, impl),
Formatter.toString(cls));
}
// check superclass and extended/implemented interfaces
final Class superclass = cls.getSuperclass();
final Class[] interfaces = cls.getInterfaces();
if (isInterface) {
//assert (impl.length == 0);
if (!TypeHelper.isNameMatch(ext, interfaces)) {
handleMismatch(
"interface declaration: non-matching interfaces;",
Formatter.toString(mods, name, ext, impl),
Formatter.toString(cls));
}
} else {
//assert (ext.length <= 1);
final String s = (ext.length == 0 ? "java.lang.Object" : ext[0]);
if (!TypeHelper.isNameMatch(s, superclass)) {
handleMismatch(
"class declaration: non-matching superclass;",
Formatter.toString(mods, name, ext, impl),
Formatter.toString(cls));
}
if (!TypeHelper.isNameMatch(impl, interfaces)) {
handleMismatch(
"class declaration: non-matching interfaces;",
Formatter.toString(mods, name, ext, impl),
Formatter.toString(cls));
}
}
handleMatch("has class: ", Formatter.toString(cls));
}
/** Runs checks on a class after its members have been validated. */
protected void postCheckClass() {
if (cls == null) {
return; // nothing to do if class couldn't be loaded
}
// check for public non-standard members
for (Iterator i = members.iterator(); i.hasNext();) {
final Member m = (Member)i.next();
if ((m.getModifiers() & Modifier.PUBLIC) != 0) {
handleNonStandard("non-standard, public member;",
Formatter.toString(m));
}
}
}
// ----------------------------------------------------------------------
// Parser for Signature Descriptor Files
// ----------------------------------------------------------------------
/**
* For parsing of signature descriptor files.
*/
protected class Parser {
/** The current descriptor file being parsed. */
private File descriptorFile;
/** The line number reader for the current descriptor file. */
private LineNumberReader ir;
/** A look-ahead token to be read next. */
private String nextToken;
/** Returns an error message reporting an unextected end of file. */
protected String msgUnexpectedEOF() {
return ("unexpected end of file at line: " + ir.getLineNumber()
+ ", file: " + descriptorFile.getPath());
}
/** Returns an error message reporting an unextected token. */
protected String msgUnexpectedToken(String t) {
return ("unexpected token: '" + t + "'"
+ " in line: " + ir.getLineNumber()
+ ", file: " + descriptorFile.getPath());
}
/** Retrieves the look-ahead token to be parsed next. */
protected String getLookAhead() {
final String t = nextToken;
nextToken = null;
return t;
}
/** Sets the look-ahead token to be parsed next. */
protected void setLookAhead(String t) {
//assert (nextToken == null);
nextToken = t;
}
/**
* Skips any "white space" and returns whether there are more
* characters to be parsed.
*/
protected boolean skip() throws IOException {
char c;
do {
ir.mark(1);
int i;
if ((i = ir.read()) < 0) {
return false;
}
c = (char)i;
} while (Character.isWhitespace(c));
ir.reset();
return true;
}
/**
* Scans for an (unqualified) identifier.
* @return null if the next token is not an identifier
*/
protected String scanIdentifier()
throws IOException, ParseException {
// parse stored token if any
String t;
if ((t = getLookAhead()) != null) {
if (!Character.isJavaIdentifierStart(t.charAt(0))) {
setLookAhead(t); // not an identifier
return null;
}
return t;
}
// parse first char
if (!skip()) {
throw new ParseException(msgUnexpectedEOF(), 0);
}
ir.mark(1);
char c = (char)ir.read();
if (!Character.isJavaIdentifierStart(c)) {
ir.reset(); // not start of an identifier
return null;
}
// parse remaining chars
final StringBuffer sb = new StringBuffer();
do {
sb.append(c);
int i;
ir.mark(1);
if ((i = ir.read()) < 0)
break;
c = (char)i;
} while (Character.isJavaIdentifierPart(c));
ir.reset(); // not part of an identifier
return sb.toString();
}
/**
* Scans for a number literal.
* @return null if the next token is not a number
*/
protected String scanNumberLiteral()
throws IOException, ParseException {
// parse stored token if any
String t;
if ((t = getLookAhead()) != null) {
if (!(t.charAt(0) == '-' || Character.isDigit(t.charAt(0)))) {
setLookAhead(t); // not a number literal
return null;
}
return t;
}
// parse first char
if (!skip()) {
throw new ParseException(msgUnexpectedEOF(), 0);
}
ir.mark(1);
char c = (char)ir.read();
if (Character.isDigit(c)) {
} else if (c == '-') {
skip();
} else {
ir.reset(); // not start of a number
return null;
}
// parse remaining chars
final StringBuffer sb = new StringBuffer();
do {
sb.append(c);
int i;
ir.mark(1);
if ((i = ir.read()) < 0)
break;
c = (char)i;
} while (Character.isLetterOrDigit(c) || c == '-' || c == '.');
ir.reset(); // not part of a number
return sb.toString();
}
/**
* Scans for a character literal.
* @return null if the next token is not a character
*/
protected String scanCharacterLiteral()
throws IOException, ParseException {
// parse stored token if any
String t;
if ((t = getLookAhead()) != null) {
if (t.charAt(0) != '\'') {
setLookAhead(t); // not a char literal
return null;
}
return t;
}
// parse first char
if (!skip()) {
throw new ParseException(msgUnexpectedEOF(), 0);
}
ir.mark(1);
char c = (char)ir.read();
if (c != '\'') {
ir.reset(); // not start of a char literal
return null;
}
// parse remaining two chars
final StringBuffer sb = new StringBuffer();
for (int j = 0; j < 2; j++) {
sb.append(c);
int i;
if ((i = ir.read()) < 0) {
throw new ParseException(msgUnexpectedEOF(), 0);
}
c = (char)i;
}
if (c != '\'') {
throw new ParseException(msgUnexpectedToken(String.valueOf(c)),
0);
}
sb.append(c); // keep '\'' part of a char literal
return sb.toString();
}
/**
* Scans for a string literal.
* @return null if the next token is not a string
*/
protected String scanStringLiteral()
throws IOException, ParseException {
// parse stored token if any
String t;
if ((t = getLookAhead()) != null) {
if (t.charAt(0) != '\"') {
setLookAhead(t); // not a string literal
return null;
}
return t;
}
// parse first char
if (!skip()) {
throw new ParseException(msgUnexpectedEOF(), 0);
}
ir.mark(1);
char c = (char)ir.read();
if (c != '\"') {
ir.reset(); // not start of a string literal
return null;
}
// parse remaining chars
final StringBuffer sb = new StringBuffer();
do {
sb.append(c);
int i;
if ((i = ir.read()) < 0) {
throw new ParseException(msgUnexpectedEOF(), 0);
}
c = (char)i;
} while (c != '\"'); // not supported: nested '\"' char sequences
sb.append(c); // keep '\"' part of a string literal
return sb.toString();
}
/**
* Returns the next token to be parsed.
* @return never null
*/
protected String parseToken()
throws IOException, ParseException {
String t;
if ((t = getLookAhead()) != null) {
} else if ((t = scanIdentifier()) != null) {
} else if ((t = scanNumberLiteral()) != null) {
} else if ((t = scanStringLiteral()) != null) {
} else if ((t = scanCharacterLiteral()) != null) {
} else {
setLookAhead(t); // not an identifier, number, or string
// next non-white char
if (!skip()) {
throw new ParseException(msgUnexpectedEOF(), 0);
}
t = String.valueOf((char)ir.read());
}
//out.println("parseToken() : '" + t + "'");
return t;
}
/**
* Parses the next token and validates it against an expected one.
* @return never null
*/
protected String demandToken(String token)
throws IOException, ParseException {
final String t = parseToken();
if (!t.equals(token)) {
throw new ParseException(msgUnexpectedToken(t), 0);
}
return t;
}
/**
* Parses a literal.
* @return null if the next token is not a literal
*/
protected String parseLiteral()
throws IOException, ParseException {
String t;
if ((t = scanNumberLiteral()) != null) {
} else if ((t = scanStringLiteral()) != null) {
} else if ((t = scanCharacterLiteral()) != null) {
}
//out.println("parseLiteral() : '" + t + "'");
return t;
}
/**
* Parses the next token and validates that it is a literal.
* @return never null
*/
protected String demandLiteral()
throws IOException, ParseException {
final String l = parseLiteral();
if (l == null) {
throw new ParseException(msgUnexpectedToken(parseToken()), 0);
}
return l;
}
/**
* Parses any available Java modifiers.
* @return an int value with the parsed modifiers' bit set
*/
protected int parseModifiers()
throws IOException, ParseException {
int m = 0;
while (true) {
// parse known modifiers
final String t = parseToken();
if (t.equals("abstract")) m |= Modifier.ABSTRACT;
else if (t.equals("final")) m |= Modifier.FINAL;
else if (t.equals("interface")) m |= Modifier.INTERFACE;
else if (t.equals("native")) m |= Modifier.NATIVE;
else if (t.equals("private")) m |= Modifier.PRIVATE;
else if (t.equals("protected")) m |= Modifier.PROTECTED;
else if (t.equals("public")) m |= Modifier.PUBLIC;
else if (t.equals("static")) m |= Modifier.STATIC;
else if (t.equals("strictfp")) m |= Modifier.STRICT;
else if (t.equals("synchronized")) m |= Modifier.SYNCHRONIZED;
else if (t.equals("transient")) m |= Modifier.TRANSIENT;
else if (t.equals("volatile")) m |= Modifier.VOLATILE;
else {
setLookAhead(t); // not a modifier
break;
}
}
//out.println("parseModifiers() : '" + Modifier.toString(m) + "'");
return m;
}
/**
* Parses a (qualified) identifier.
* @return null if the next token is not an identifier
*/
protected String parseIdentifier()
throws IOException, ParseException {
String t = scanIdentifier();
if (t != null) {
// parse dot-connected identifiers
final StringBuffer id = new StringBuffer(t);
String tt = parseToken();
while (tt.equals(".")) {
id.append(".");
tt = parseIdentifier();
if (tt == null) {
throw new ParseException(msgUnexpectedToken(tt), 0);
}
id.append(tt);
tt = parseToken();
}
setLookAhead(tt); // not a dot token
t = id.toString();
}
//out.println("parseIdentifier() : '" + t + "'");
return t;
}
/**
* Parses the next token(s) and validates that it is an identifier.
* @return never null
*/
protected String demandIdentifier()
throws IOException, ParseException {
final String id = parseIdentifier();
if (id == null) {
throw new ParseException(msgUnexpectedToken(parseToken()), 0);
}
return id;
}
/**
* Parses a comma-separated list of identifiers.
* @return never null
*/
protected String[] demandIdentifierList()
throws IOException, ParseException {
final ArrayList ids = new ArrayList();
ids.add(demandIdentifier());
String t;
while ((t = parseToken()).equals(",")) {
ids.add(demandIdentifier());
}
setLookAhead(t); // not an identifier
return (String[])ids.toArray(new String[ids.size()]);
}
/**
* Parses a type expression.
* @return null if the next token is not a type
*/
protected String parseType()
throws IOException, ParseException {
String t = parseIdentifier();
if (t != null) {
// parse array dimensions
final StringBuffer type = new StringBuffer(t);
while ((t = parseToken()).equals("[")) {
demandToken("]");
type.append("[]");
}
setLookAhead(t); // not an open bracket token
t = type.toString();
}
//out.println("parseType() : '" + t + "'");
return t;
}
/**
* Parses the next token and validates that it is a type expression.
* @return never null
*/
protected String demandType()
throws IOException, ParseException {
final String id = parseType();
if (id == null) {
throw new ParseException(msgUnexpectedToken(parseToken()), 0);
}
return id;
}
/**
* Parses a comma-separated parameter list.
* @return never null
*/
protected String[] parseParameterList()
throws IOException, ParseException {
final ArrayList types = new ArrayList();
String t = parseType();
if (t != null) {
types.add(t);
parseIdentifier(); // optional parameter name
while ((t = parseToken()).equals(",")) {
types.add(demandType());
parseIdentifier(); // optional parameter name
}
setLookAhead(t); // not a comma token
}
return (String[])types.toArray(new String[types.size()]);
}
/**
* Parses a class member declaration and provides the information
* to a field, constructor, or method handler.
* @return null if there's no member declaration
*/
protected String parseMember()
throws IOException, ParseException {
// parse optional modifiers, type, and member name
final int mods = parseModifiers();
final String typeOrName = parseType();
if (typeOrName == null) {
if (mods != 0) {
throw new ParseException(msgUnexpectedEOF(), 0);
}
return null; // no member to parse
}
final String memberName = parseIdentifier(); // null if constructor
// parse optional field value or parameter+exception list
final String value;
final String[] params;
final String[] excepts;
{
final String tvp = parseToken();
if (tvp.equals(";")) {
value = null;
params = null;
excepts = null;
} else if (tvp.equals("=")) {
// parse field value
value = demandLiteral();
demandToken(";");
params = null;
excepts = null;
} else if (tvp.equals("(")) {
// parse optional parameter and exception list
params = parseParameterList();
demandToken(")");
final String tt = parseToken();
if (tt.equals("throws")) {
excepts = demandIdentifierList();
demandToken(";");
} else if (tt.equals(";")) {
excepts = new String[]{};
} else {
throw new ParseException(msgUnexpectedToken(tt), 0);
}
value = null;
} else {
throw new ParseException(msgUnexpectedToken(tvp), 0);
}
}
// verify field, constructor, or method
String name = memberName;
if (params == null) {
checkField(mods, typeOrName, memberName, value);
} else {
if (memberName == null) {
name = typeOrName;
checkConstructor(mods, params, excepts);
} else {
checkMethod(mods, typeOrName, memberName, params, excepts);
}
}
//out.println("parseMember() : " + name);
return name;
}
/**
* Parses a class definition and provides the information
* to a handler.
* @return null if there's no class definition
*/
protected String parseClass()
throws IOException, ParseException {
// parse optional modifiers, class token, and class name
if (!skip()) {
return null; // eof, no class to parse
}
final int mods = parseModifiers();
final String tc = parseToken();
if (!tc.equals("class")) { // token 'interface' parsed as modifier
setLookAhead(tc);
}
final String name = demandIdentifier();
// parse optional extends and implements clauses
final String[] ext;
final String[] impl;
{
String tei = parseToken();
if (tei.equals("extends")) {
ext = demandIdentifierList();
tei = parseToken();
} else {
ext = new String[]{};
}
if (((mods & Modifier.INTERFACE) == 0)
&& tei.equals("implements")) {
impl = demandIdentifierList();
tei = parseToken();
} else {
impl = new String[]{};
}
if (!tei.equals("{")) {
throw new ParseException(msgUnexpectedToken(tei), 0);
}
}
// verify class header
checkClass(mods, name, ext, impl);
// process members
while (parseMember() != null);
demandToken("}");
// verify class
postCheckClass();
//out.println("parseClass() : " + name);
return name;
}
/**
* Parses a list of signature descriptor files and processes
* the class definitions.
* @param descrFileNames list of signature descriptor file names
*/
public void parse(List descrFileNames)
throws IOException, ParseException {
for (Iterator i = descrFileNames.iterator(); i.hasNext();) {
final String fileName = (String)i.next();
out.println();
out.println("parsing descriptor file: " + fileName);
try {
descriptorFile = new File(fileName);
ir = new LineNumberReader(new FileReader(descriptorFile));
ir.setLineNumber(1);
setLookAhead(null);
while (parseClass() != null);
} finally {
descriptorFile = null;
if (ir != null) {
ir.close();
}
setLookAhead(null);
}
}
}
}
// ----------------------------------------------------------------------
// Stand-Alone Command-Line Interface
// ----------------------------------------------------------------------
/**
* A helper class for running the signature test from the command-line.
*/
static public class CLI {
/** A writer for standard output. */
static protected PrintWriter out = new PrintWriter(System.out, true);
/** A writer for error output. */
static protected PrintWriter err = new PrintWriter(System.err, true);
/** Command line arguments */
static public final List descrFileNames = new ArrayList();
/** Prints the CLI usage. */
static public void printUsage() {
err.println();
err.println("usage: SignatureTest [options] arguments");
err.println("options:");
err.println(" [-h|--help print usage]");
err.println("arguments:");
err.println(" ...");
err.println();
}
/** Parses command line arguments. */
static public int parseArgs(String args[]) {
out.println("parse main() arguments");
// parse this class' options and arguments
for (int i = 0; i < args.length; i++) {
if (args[i] == null) {
continue;
}
if (args[i].equalsIgnoreCase("-h")
|| args[i].equalsIgnoreCase("--help")) {
return -1;
}
if (args[i].startsWith("-")) {
err.println("Usage Error: unknown option " + args[i]);
return -1;
}
// collect argument
descrFileNames.add(args[i]);
}
// print args
if (false) {
out.println("descrFileNames = {");
for (Iterator i = descrFileNames.iterator(); i.hasNext();) {
out.println(" " + i.next());
}
out.println(" }");
}
// check args
if (descrFileNames.isEmpty()) {
err.println("Usage Error: Missing argument "
+ "...");
return -1;
}
return 0;
}
/** Runs the signature test and exits with a status code. */
static public void main(String[] args) {
out.println("run SignatureTest ...");
if (parseArgs(args) != 0) {
printUsage();
out.println("abort.");
System.exit(-1);
}
int status = 0;
try {
final SignatureTest s = new SignatureTest();
status = s.test(descrFileNames);
} catch (IOException ex) {
err.println("ERROR: exception caught: " + ex);
//ex.printStackTrace();
out.println("abort.");
System.exit(-2);
} catch (ParseException ex) {
err.println("ERROR: exception caught: " + ex);
//ex.printStackTrace();
out.println("abort.");
System.exit(-3);
}
out.println();
out.println("done.");
System.exit(status);
}
}
}