/* * 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); } } }