| /* |
| * 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 |
| <<<<<<< Updated upstream |
| * |
| * 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 |
| ======= |
| * |
| * https://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 |
| >>>>>>> Stashed changes |
| * limitations under the License. |
| */ |
| |
| package org.apache.jdo.tck.util.signature; |
| |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.LineNumberReader; |
| import java.io.PrintWriter; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Member; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.text.ParseException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| /** |
| * Tests classes for correct signatures. |
| * |
| * @author Martin Zaun |
| */ |
| public class SignatureVerifier { |
| /** The new-line character on this system. */ |
| protected static final String NL = System.getProperty("line.separator"); |
| |
| /** |
| * All modifiers defined in java.lang.reflect.Modifier. This field is used to filter out |
| * vm-specific modifiers such as found in Sun's vm to identify Enum and Annotation. |
| */ |
| protected static final int ALL_MODIFIERS = |
| Modifier.ABSTRACT |
| | Modifier.FINAL |
| | Modifier.INTERFACE |
| | Modifier.NATIVE |
| | Modifier.PRIVATE |
| | Modifier.PROTECTED |
| | Modifier.PUBLIC |
| | Modifier.STATIC |
| | Modifier.STRICT |
| | Modifier.SYNCHRONIZED |
| | Modifier.TRANSIENT |
| | Modifier.VOLATILE; |
| |
| /** Pseudo modifier for annotation */ |
| protected static final int ANNOTATION = 0x2000; |
| |
| /** Pseudo modifier for enum */ |
| protected static final int ENUM = 0x4000; |
| |
| /** A writer for standard output. */ |
| protected final PrintWriter log; |
| |
| /** Flag to print error output only. */ |
| private final boolean quiet; |
| |
| /** Flag to also print verbose output. */ |
| private final boolean verbose; |
| |
| /** 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<Member> members = new HashSet<>(); |
| |
| /** Collects names of loadable classes. */ |
| private final Set<String> loading = new TreeSet<>(); |
| |
| /** Collects names of unloadable classes. */ |
| private final Set<String> 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. |
| * |
| * @param loader ClassLoader |
| * @param log writer for logging |
| * @param quiet quiet option |
| * @param verbose verbose option |
| */ |
| public SignatureVerifier(ClassLoader loader, PrintWriter log, boolean quiet, boolean verbose) { |
| classLoader = this.getClass().getClassLoader(); |
| this.log = log; |
| this.quiet = quiet; |
| this.verbose = (!quiet && verbose); |
| } |
| |
| /** |
| * Constructs a test instance. |
| * |
| * @param log writer for logging |
| * @param quiet quiet option |
| * @param verbose verbose option |
| */ |
| public SignatureVerifier(PrintWriter log, boolean quiet, boolean verbose) { |
| this(SignatureVerifier.class.getClassLoader(), log, quiet, verbose); |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Local Logging Methods |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * Prints an error message. |
| * |
| * @param msg error message |
| */ |
| protected void logError(String msg) { |
| log.println(msg); |
| } |
| |
| /** |
| * Prints an info message. |
| * |
| * @param msg info message |
| */ |
| protected void logInfo(String msg) { |
| if (!quiet) { |
| log.println(msg); |
| } |
| } |
| |
| /** |
| * Prints a verbose message. |
| * |
| * @param msg verbose message |
| */ |
| protected void logVerbose(String msg) { |
| if (verbose) { |
| log.println(msg); |
| } |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Test Methods |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * Tests the signature of classes (in the specified classloader) 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 |
| * @throws IOException error reading file |
| * @throws ParseException error during parsing |
| */ |
| public int test(List<String> descrFileNames) throws IOException, ParseException { |
| // check argument |
| if (descrFileNames == null || descrFileNames.isEmpty()) { |
| final String m = ("ERROR: No signature descriptor file to parse."); |
| logError(m); |
| return -1; |
| } |
| |
| // 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); |
| } |
| |
| /** Reports the results of the last signature test run. */ |
| public void report() { |
| logInfo(""); |
| logInfo("Signature Test Results"); |
| logInfo("======================"); |
| logInfo(""); |
| logInfo(" tested features: " + tested); |
| logInfo(""); |
| logInfo("Successes:"); |
| logInfo(" matching features: " + matching); |
| logInfo(" loadable classes: " + loading.size()); |
| logInfo(""); |
| logInfo("Failures:"); |
| logInfo(" missing features: " + missing); |
| logInfo(" non-matching features: " + mismatch); |
| logInfo(" non-standard features: " + nonStandard); |
| logInfo(" unloadable classes: " + notLoading.size()); |
| logInfo(" other problems: " + otherProblems); |
| logInfo(""); |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Test Logic |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * Handles class loading problems. |
| * |
| * @param t Throwable |
| */ |
| protected void handleNotLoading(Throwable t) { |
| notLoading.add(t.getMessage().replace('/', '.')); |
| final String m = ("--- failed loading class;" + NL + " caught: " + t); |
| logError(m); |
| } |
| |
| /** |
| * Handles missing members. |
| * |
| * @param msg message |
| * @param exp expected |
| */ |
| protected void handleMissing(String msg, String exp) { |
| missing++; |
| final String m = |
| ("--- " |
| + msg |
| + NL |
| + " expected: " |
| + exp |
| + NL |
| + " class: " |
| + Formatter.toString(cls)); |
| logError(m); |
| } |
| |
| /** |
| * Handles non-matching features. |
| * |
| * @param msg message |
| * @param exp expected |
| * @param fnd feature |
| */ |
| 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)); |
| logError(m); |
| } |
| |
| /** |
| * Handles public non-standard features. |
| * |
| * @param msg message |
| * @param fnd feature |
| */ |
| protected void handleNonStandard(String msg, String fnd) { |
| nonStandard++; |
| final String m = |
| ("--- " |
| + msg |
| + NL |
| + " found: " |
| + fnd |
| + NL |
| + " class: " |
| + Formatter.toString(cls)); |
| logError(m); |
| } |
| |
| /** |
| * Handles other problems. |
| * |
| * @param msg message |
| * @param exp expected |
| */ |
| protected void handleProblem(String msg, String exp) { |
| otherProblems++; |
| final String m = |
| ("--- " |
| + msg |
| + NL |
| + " expected: " |
| + exp |
| + NL |
| + " class: " |
| + Formatter.toString(cls)); |
| logError(m); |
| } |
| |
| /** |
| * Handles a perfect feature match. |
| * |
| * @param msg message |
| * @param fnd feature |
| */ |
| protected void handleMatch(String msg, String fnd) { |
| matching++; |
| final String m = ("+++ " + msg + fnd); |
| logVerbose(m); |
| } |
| |
| /** |
| * Returns the class objects for given (Java) user type names. |
| * |
| * @param userTypeName user type names |
| * @return class objects |
| */ |
| protected Class<?>[] getClasses(String[] userTypeName) { |
| @SuppressWarnings("rawtypes") |
| 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. |
| * |
| * @param userTypeName user type name |
| * @return class object |
| */ |
| 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 = Class.forName(r, false, classLoader); |
| loading.add(userTypeName); |
| } catch (LinkageError | ClassNotFoundException err) { |
| handleNotLoading(err); |
| } |
| return cls; |
| } |
| |
| /** |
| * Check an expected value expression (the value of a static field or the default value of an |
| * annotation method), comparing it to the actual value from the Field or Method object. Only |
| * supports primitive, enum, empty array of enum, empty array of annotation, and String. |
| * |
| * @param value the String form of the value from the signature file |
| * @param type the type as declared in the class |
| * @param actual the actual value |
| * @return the description of the expected value, or null if ok |
| * @throws java.lang.NumberFormatException format exception |
| */ |
| protected String checkValue(String value, String type, Object actual) |
| throws NumberFormatException { |
| // note array type |
| boolean isArray = false; |
| if (type.endsWith("[]")) { |
| isArray = true; |
| type = type.substring(0, type.length() - 2); |
| // remove { from beginning and } from end |
| value = value.substring(1, value.length() - 1); |
| } |
| // first check primitive type |
| final Object exp; |
| Class<?> expClass; |
| final boolean ok; |
| switch (type) { |
| case "byte": |
| if (isArray) { |
| ok = actual.getClass().getComponentType().equals(byte.class); |
| } else { |
| ok = Byte.valueOf(value).equals(actual); |
| } |
| break; |
| case "boolean": |
| if (isArray) { |
| ok = actual.getClass().getComponentType().equals(boolean.class); |
| } else { |
| ok = Boolean.valueOf(value).equals(actual); |
| } |
| break; |
| case "short": |
| if (isArray) { |
| ok = actual.getClass().getComponentType().equals(short.class); |
| } else { |
| ok = Short.valueOf(value).equals(actual); |
| } |
| break; |
| case "int": |
| if (isArray) { |
| ok = actual.getClass().getComponentType().equals(int.class); |
| } else { |
| ok = Integer.valueOf(value).equals(actual); |
| } |
| break; |
| case "long": |
| if (isArray) { |
| ok = actual.getClass().getComponentType().equals(long.class); |
| } else { |
| ok = Long.valueOf(value).equals(actual); |
| } |
| break; |
| case "float": |
| if (isArray) { |
| ok = actual.getClass().getComponentType().equals(float.class); |
| } else { |
| ok = Float.valueOf(value).equals(actual); |
| } |
| break; |
| case "double": |
| if (isArray) { |
| ok = actual.getClass().getComponentType().equals(double.class); |
| } else { |
| ok = Double.valueOf(value).equals(actual); |
| } |
| break; |
| case "char": |
| if (isArray) { |
| ok = actual.getClass().getComponentType().equals(char.class); |
| } else { |
| ok = Character.valueOf(value.charAt(1)).equals(actual); |
| } |
| // next check Class |
| break; |
| case "java.lang.Class": |
| if (isArray) { |
| ok = actual.getClass().getComponentType().equals(Class.class); |
| } else { |
| // strip ".class" from type name |
| int offset = value.indexOf(".class"); |
| value = value.substring(0, offset); |
| ok = getClass(value).equals(actual); |
| } |
| // next check String |
| break; |
| case "java.lang.String": |
| if (isArray) { |
| ok = actual.getClass().getComponentType().equals(String.class); |
| } else { |
| // cut off '\"' chars at begin and end |
| final String s; |
| if (value.length() > 1) { |
| s = value.substring(1, value.length() - 1); |
| } else { |
| s = ""; |
| } |
| ok = s.equals(actual); |
| } |
| // now check non-java.lang annotations and enums |
| break; |
| default: |
| expClass = getClass(type); |
| if (isArray) { |
| // check if the actual component type is the right class |
| // don't check the actual values because only empty arrays |
| // are supported. |
| ok = actual.getClass().getComponentType().equals(expClass); |
| } else if (expClass == null) { |
| System.out.println( |
| "WARNING : checkValue value=" |
| + value |
| + " type=" |
| + type |
| + " comes up with null" |
| + " class"); |
| ok = false; |
| } else if (expClass.isAnnotation()) { |
| // check whether the type isAssignableFrom the class of the actual value, |
| // if type is an annotation. The actual value is a dynamic proxy, |
| // so an equals comparison does not work. |
| ok = expClass.isAssignableFrom(actual.getClass()); |
| } else { |
| // get the actual value which must be a static class.field |
| Object expectedValue = null; |
| try { |
| // now get actual value |
| // separate value name into class and field name |
| int lastDot = value.lastIndexOf("."); |
| String expectedClassName = value.substring(0, lastDot); |
| String expectedFieldName = value.substring(lastDot + 1); |
| // get Class object from class name |
| Class<?> expectedClass = getClass(expectedClassName); |
| if (expectedClass == null) throw new ClassNotFoundException(); |
| // get Field object from Class and field name |
| Field expectedField = expectedClass.getField(expectedFieldName); |
| expectedValue = expectedField.get(null); |
| } catch (NoSuchFieldException |
| | ClassNotFoundException |
| | IllegalAccessException |
| | IllegalArgumentException |
| | SecurityException ex) { |
| handleNotLoading(ex); |
| } |
| ok = expectedValue.equals(actual); |
| } |
| break; |
| } |
| // return message if not ok |
| if (ok) return null; |
| else return value; |
| } |
| |
| /** |
| * Validates a field against a prescribed signature. |
| * |
| * @param mods modifier |
| * @param type type |
| * @param name name |
| * @param value value |
| */ |
| 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 != convertModifiers(field)) { |
| 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 actualValue = 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 { |
| // compare field's expected with found value |
| try { |
| actualValue = field.get(null); |
| } catch (IllegalAccessException ex) { |
| handleProblem( |
| "field declaration: cannot access field " + "value, exception: " + ex + ";", |
| Formatter.toString(mods, type, name, value)); |
| } |
| String error = checkValue(value, type, actualValue); |
| if (error != null) { |
| handleMismatch( |
| "field declaration: non-matching values;", |
| Formatter.toString(mods, type, name, error), |
| Formatter.toString(field, "\"" + actualValue + "\"")); |
| } |
| } |
| } |
| |
| // field OK |
| members.remove(field); |
| handleMatch("has field: ", Formatter.toString(field, actualValue)); |
| } |
| |
| /** |
| * Validates a constructor against a prescribed signature. |
| * |
| * @param mods modifier |
| * @param params parameters |
| * @param excepts excepts |
| */ |
| 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. |
| * |
| * @param mods modifier |
| * @param result result |
| * @param name name |
| * @param params parameters |
| * @param excepts excepts |
| * @param value value |
| */ |
| protected void checkMethod( |
| int mods, String result, String name, String[] params, String[] excepts, String value) { |
| 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 |
| mods |= Modifier.PUBLIC; |
| } |
| Class<?> resultType = getClass(result); |
| if (resultType == null) { |
| System.out.println( |
| "WARNING : checkMethod " |
| + name |
| + " result=" |
| + result |
| + " comes up with null resultType!"); |
| } else if (resultType.isAnnotation()) { |
| // add ANNOTATION modifier if the result type is an annotation |
| mods |= ANNOTATION; |
| } |
| if (mods != convertModifiers(method)) { |
| 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)); |
| } |
| |
| // check default value of an annotation element (method) |
| if (value != null) { |
| Object actualValue = null; |
| try { |
| actualValue = method.getDefaultValue(); |
| } catch (Exception ex) { |
| handleProblem( |
| "method declaration: cannot access default " + "value, exception: " + ex + ";", |
| Formatter.toString(mods, result, name, value)); |
| } |
| // check value expected versus actual |
| String wrong = checkValue(value, result, actualValue); |
| |
| if (wrong != null) { |
| handleMismatch( |
| "method declaration: non-matching default value;", |
| Formatter.toString(mods, result, name + "() default ", wrong), |
| Formatter.toString(method)); |
| } |
| } |
| |
| // method OK |
| members.remove(method); |
| handleMatch("has method: ", Formatter.toString(method)); |
| } |
| |
| /** |
| * Validates a class declaration against a prescribed signature. |
| * |
| * @param mods modifier |
| * @param name name |
| * @param ext ext |
| * @param impl implementation |
| */ |
| protected void checkClass(int mods, String name, String[] ext, String[] impl) { |
| logVerbose(""); |
| logVerbose("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 != convertModifiers(cls)) { |
| 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 (final Member m : members) { |
| if ((m.getModifiers() & Modifier.PUBLIC) != 0) { |
| handleNonStandard("non-standard, public member;", Formatter.toString(m)); |
| } |
| } |
| } |
| |
| /** |
| * Return modifiers for the class, but handling enum and annotation as if they had a special |
| * modifier. |
| * |
| * @param cls the class |
| * @return modifiers of the class with extra flags for enum and annotation |
| */ |
| protected int convertModifiers(Class<?> cls) { |
| int result = cls.getModifiers(); |
| // first remove extraneous stuff |
| result &= ALL_MODIFIERS; |
| // if enum, set pseudo enum flag |
| if (cls.isEnum()) result |= ENUM; |
| // if annotation, set pseudo annotation flag |
| if (cls.isAnnotation()) result |= ANNOTATION; |
| return result; |
| } |
| |
| /** |
| * Return modifiers for the method, but handling enum and annotation as if they had a special |
| * modifier. |
| * |
| * @param method the method |
| * @return modifiers of the class with extra flags for enum and annotation |
| */ |
| protected int convertModifiers(Method method) { |
| int result = method.getModifiers(); |
| // first remove extraneous stuff |
| result &= ALL_MODIFIERS; |
| // if enum return type, set pseudo enum flag |
| if (method.getReturnType().isEnum()) result |= ENUM; |
| // if annotation, set pseudo annotation flag |
| if (method.getReturnType().isAnnotation()) result |= ANNOTATION; |
| // if return type is an enum class, un-set FINAL modifier in all methods |
| // because in Java 5, methods are generated as final; in Java 6, not |
| if (method.getDeclaringClass().isEnum()) result &= ~Modifier.FINAL; |
| return result; |
| } |
| |
| /** |
| * Return modifiers for the field, but handling enum as if it had a special modifier. |
| * |
| * @param field the field |
| * @return modifiers of the class with extra flag for enum |
| */ |
| protected int convertModifiers(Field field) { |
| int result = field.getModifiers(); |
| // first remove extraneous stuff |
| result &= ALL_MODIFIERS; |
| // if enum, set pseudo enum flag |
| if (field.isEnumConstant()) result |= ENUM; |
| return result; |
| } |
| |
| // ---------------------------------------------------------------------- |
| // 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 unexpected end of file. |
| * |
| * @return message |
| */ |
| protected String msgUnexpectedEOF() { |
| return ("unexpected end of file at line: " |
| + ir.getLineNumber() |
| + ", file: " |
| + descriptorFile.getPath()); |
| } |
| |
| /** |
| * Returns an error message reporting an unextected token. |
| * |
| * @param t token |
| * @return error message |
| */ |
| 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. |
| * |
| * @return look ahead |
| */ |
| protected String getLookAhead() { |
| final String t = nextToken; |
| nextToken = null; |
| return t; |
| } |
| |
| /** |
| * Sets the look-ahead token to be parsed next. |
| * |
| * @param t token |
| */ |
| protected void setLookAhead(String t) { |
| // assert (nextToken == null); |
| nextToken = t; |
| } |
| |
| /** |
| * Skips any "white space" and /* style comments and returns whether there are more characters |
| * to be parsed. |
| * |
| * @return true if there are more characters to be parsed |
| * @throws IOException IO error |
| */ |
| protected boolean skip() throws IOException { |
| Character c; |
| Character cnext; |
| // Permitted states are white, commentSlashStar, commentSlashSlash |
| String state = "white"; |
| final Character SLASH = '/'; |
| final Character STAR = '*'; |
| while (true) { |
| c = peek(); |
| if (c == null) return false; |
| if (state.equals("commentSlashStar") && STAR.equals(c)) { |
| cnext = peek(); |
| if (cnext == null) return false; |
| if (SLASH.equals(cnext)) { |
| state = "white"; |
| } |
| continue; |
| } |
| if (state.equals("white")) { |
| if (SLASH.equals(c)) { |
| cnext = peek(); |
| if (cnext == null) return false; |
| if (STAR.equals(cnext)) { |
| state = "commentSlashStar"; |
| continue; |
| } |
| if (SLASH.equals(cnext)) { |
| state = "commentSlashSlash"; |
| continue; |
| } |
| } |
| if (!Character.isWhitespace(c)) { |
| break; |
| } |
| } |
| if (state.equals("commentSlashSlash") |
| && (c == Character.LINE_SEPARATOR || c == '\n' || c == '\r')) { |
| state = "white"; |
| continue; |
| } |
| } |
| ir.reset(); |
| return true; |
| } |
| |
| /** |
| * Returns next char, or null if there is none |
| * |
| * @return next char |
| * @throws IOException IO error |
| */ |
| protected Character peek() throws IOException { |
| ir.mark(1); |
| int i; |
| if ((i = ir.read()) < 0) { |
| return null; |
| } |
| return Character.valueOf((char) i); |
| } |
| |
| /** |
| * Scans for an (unqualified) identifier. |
| * |
| * @return <code>null</code> if the next token is not an identifier |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| 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 StringBuilder sb = new StringBuilder(); |
| 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 <code>null</code> if the next token is not a number |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| 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 StringBuilder sb = new StringBuilder(); |
| 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 <code>null</code> if the next token is not a character |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| 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 StringBuilder sb = new StringBuilder(); |
| 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 <code>null</code> if the next token is not a string |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| 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 StringBuilder sb = new StringBuilder(); |
| 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(); |
| } |
| |
| /** |
| * Scans for an array literal. Limitation: only the empty array "{}" can be scanned. |
| * |
| * @return <code>null</code> if the next token is not an array |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String scanArrayLiteral() throws IOException, ParseException { |
| // parse stored token if any |
| String t; |
| if ((t = getLookAhead()) != null) { |
| if (t.charAt(0) != '{' || t.charAt(1) != '}') { |
| setLookAhead(t); // not an array 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 an array literal |
| return null; |
| } |
| c = (char) ir.read(); |
| if (c != '}') { |
| ir.reset(); // not end of an array literal |
| return null; |
| } |
| |
| return "{}"; |
| } |
| |
| /** |
| * Scans for an at-sign |
| * |
| * @return <code>null</code> if the next token is not an at-sign |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String scanAtSign() throws IOException, ParseException { |
| // parse stored token if any |
| String t; |
| if ((t = getLookAhead()) != null) { |
| if (t.charAt(0) != '@') { |
| setLookAhead(t); // not an at-sign |
| 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 an at-sign |
| return null; |
| } |
| |
| return "@"; |
| } |
| |
| /** |
| * Returns the next token to be parsed. |
| * |
| * @return never <code>null</code> |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| 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 if ((t = scanAtSign()) != 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()); |
| } |
| // log.println("parseToken() : '" + t + "'"); |
| return t; |
| } |
| |
| /** |
| * Parses the next token and validates it against an expected one. |
| * |
| * @param token the token |
| * @return never <code>null</code> |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| 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 <code>null</code> if the next token is not a literal |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String parseLiteral() throws IOException, ParseException { |
| String t; |
| if ((t = scanNumberLiteral()) != null) { |
| } else if ((t = scanStringLiteral()) != null) { |
| } else if ((t = scanCharacterLiteral()) != null) { |
| } else if ((t = scanArrayLiteral()) != null) { |
| } |
| // log.println("parseLiteral() : '" + t + "'"); |
| return t; |
| } |
| |
| /** |
| * Parses the next token and validates that it is a literal. |
| * |
| * @return never <code>null</code> |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String demandLiteral() throws IOException, ParseException { |
| final String l = parseLiteral(); |
| if (l == null) { |
| throw new ParseException(msgUnexpectedToken(parseToken()), 0); |
| } |
| return l; |
| } |
| |
| /** |
| * Parses the next token and validates that it is a constant, which is either a literal or a |
| * static member. |
| * |
| * @return the description of the field |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String demandConstant() throws IOException, ParseException { |
| final String literal = parseLiteral(); |
| if (literal != null) { |
| return literal; |
| } |
| final String field = demandIdentifier(); |
| if (field == null) { |
| throw new ParseException(msgUnexpectedToken(parseToken()), 0); |
| } |
| return field; |
| } |
| |
| /** |
| * Parses an annotation which is an at-sign followed by a fully qualified interface name. |
| * |
| * @return <code>null</code> if the next token is not an annotation, otherwise return the |
| * annotation type name without the at-sign. |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String parseAnnotation() throws IOException, ParseException { |
| String t; |
| // parse stored token if any |
| if ((t = getLookAhead()) != null) { |
| if (t.charAt(0) != '@') { |
| setLookAhead(t); // not a annotation |
| return null; |
| } |
| } else if ((t = scanAtSign()) != null) { |
| } else { |
| return null; |
| } |
| |
| final String identifier = demandIdentifier(); |
| if (identifier == null) { |
| throw new ParseException(msgUnexpectedToken(parseToken()), 0); |
| } |
| |
| return identifier; |
| } |
| |
| /** |
| * Parses the default value specification of an annotation element and validates that it is an |
| * element value, which is either an annotation or a constant. |
| * |
| * @return never <code>null</code> |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String demandElementValue() throws IOException, ParseException { |
| final String annotation = parseAnnotation(); |
| if (annotation != null) { |
| return annotation; |
| } |
| final String constant = demandConstant(); |
| if (constant == null) { |
| throw new ParseException(msgUnexpectedToken(parseToken()), 0); |
| } |
| return constant; |
| } |
| |
| /** |
| * Parses any available Java modifiers. |
| * |
| * @return an int value with the parsed modifiers' bit set |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected int parseModifiers() throws IOException, ParseException { |
| int m = 0; |
| label: |
| while (true) { |
| // parse known modifiers |
| final String t = parseToken(); |
| switch (t) { |
| case "abstract": |
| m |= Modifier.ABSTRACT; |
| break; |
| case "annotation": |
| m |= (ANNOTATION + Modifier.ABSTRACT + Modifier.INTERFACE); |
| break; |
| case "enum": |
| m |= ENUM; |
| break; |
| case "final": |
| m |= Modifier.FINAL; |
| break; |
| case "interface": |
| m |= Modifier.INTERFACE; |
| break; |
| case "native": |
| m |= Modifier.NATIVE; |
| break; |
| case "private": |
| m |= Modifier.PRIVATE; |
| break; |
| case "protected": |
| m |= Modifier.PROTECTED; |
| break; |
| case "public": |
| m |= Modifier.PUBLIC; |
| break; |
| case "static": |
| m |= Modifier.STATIC; |
| break; |
| case "strictfp": |
| m |= Modifier.STRICT; |
| break; |
| case "synchronized": |
| m |= Modifier.SYNCHRONIZED; |
| break; |
| case "transient": |
| m |= Modifier.TRANSIENT; |
| break; |
| case "varargs": |
| m |= Modifier.TRANSIENT; |
| break; |
| case "volatile": |
| m |= Modifier.VOLATILE; |
| break; |
| default: |
| setLookAhead(t); // not a modifier |
| |
| break label; |
| } |
| } |
| // log.println("parseModifiers() : '" + Modifier.toString(m) + "'"); |
| return m; |
| } |
| |
| /** |
| * Parses a (qualified) identifier. |
| * |
| * @return <code>null</code> if the next token is not an identifier |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String parseIdentifier() throws IOException, ParseException { |
| String t = scanIdentifier(); |
| if (t != null) { |
| // parse dot-connected identifiers |
| final StringBuilder id = new StringBuilder(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(); |
| } |
| // log.println("parseIdentifier() : '" + t + "'"); |
| return t; |
| } |
| |
| /** |
| * Parses the next token(s) and validates that it is an identifier. |
| * |
| * @return never <code>null</code> |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| 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 <code>null</code> |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String[] demandIdentifierList() throws IOException, ParseException { |
| final List<String> ids = new ArrayList<>(); |
| ids.add(demandIdentifier()); |
| String t; |
| while ((t = parseToken()).equals(",")) { |
| ids.add(demandIdentifier()); |
| } |
| setLookAhead(t); // not an identifier |
| return ids.toArray(new String[ids.size()]); |
| } |
| |
| /** |
| * Parses a type expression. |
| * |
| * @return <code>null</code> if the next token is not a type |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String parseType() throws IOException, ParseException { |
| String t = parseIdentifier(); |
| if (t != null) { |
| // parse array dimensions |
| final StringBuilder type = new StringBuilder(t); |
| while ((t = parseToken()).equals("[")) { |
| demandToken("]"); |
| type.append("[]"); |
| } |
| setLookAhead(t); // not an open bracket token |
| t = type.toString(); |
| } |
| // log.println("parseType() : '" + t + "'"); |
| return t; |
| } |
| |
| /** |
| * Parses the next token and validates that it is a type expression. |
| * |
| * @return never <code>null</code> |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| 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 <code>null</code> |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| protected String[] parseParameterList() throws IOException, ParseException { |
| final List<String> 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 types.toArray(new String[types.size()]); |
| } |
| |
| /** |
| * Parses a class member declaration and provides the information to a field, constructor, or |
| * method handler. |
| * |
| * @return <code>null</code> if there's no member declaration |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| 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(); |
| switch (tvp) { |
| case ";": |
| value = null; |
| params = null; |
| excepts = null; |
| break; |
| case "=": |
| // parse field value |
| value = demandLiteral(); |
| demandToken(";"); |
| params = null; |
| excepts = null; |
| break; |
| case "(": |
| // parse optional parameter and exception list |
| params = parseParameterList(); |
| demandToken(")"); |
| final String tt = parseToken(); |
| switch (tt) { |
| case "throws": |
| excepts = demandIdentifierList(); |
| demandToken(";"); |
| value = null; |
| break; |
| case ";": |
| excepts = new String[] {}; |
| value = null; |
| break; |
| case "default": |
| value = demandElementValue(); |
| demandToken(";"); |
| excepts = new String[] {}; |
| break; |
| default: |
| throw new ParseException(msgUnexpectedToken(tt), 0); |
| } |
| break; |
| default: |
| 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, value); |
| } |
| } |
| |
| // log.println("parseMember() : " + name); |
| return name; |
| } |
| |
| /** |
| * Parses a class definition and provides the information to a handler. |
| * |
| * @return <code>null</code> if there's no class definition |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| @SuppressWarnings("empty-statement") |
| 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; empty-statement intentional |
| while (parseMember() != null) |
| ; |
| demandToken("}"); |
| |
| // verify class |
| postCheckClass(); |
| |
| // log.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 |
| * @throws IOException IO error |
| * @throws ParseException parsing error |
| */ |
| @SuppressWarnings("empty-statement") |
| public void parse(List<String> descrFileNames) throws IOException, ParseException { |
| for (final String fileName : descrFileNames) { |
| logInfo(""); |
| logInfo("parsing descriptor file: " + fileName); |
| try { |
| descriptorFile = new File(fileName); |
| ir = new LineNumberReader(new FileReader(descriptorFile)); |
| ir.setLineNumber(1); |
| setLookAhead(null); |
| // empty-statement intentional |
| while (parseClass() != null) |
| ; |
| } finally { |
| descriptorFile = null; |
| if (ir != null) { |
| ir.close(); |
| } |
| setLookAhead(null); |
| } |
| } |
| } |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Stand-Alone Command-Line Interface |
| // ---------------------------------------------------------------------- |
| |
| /** A writer for standard output. */ |
| protected static final PrintWriter out = new PrintWriter(System.out, true); |
| |
| /** Command line arguments */ |
| public static final List<String> descrFileNames = new ArrayList<>(); |
| |
| /** Command line option 'quiet'. */ |
| private static boolean optionQuiet; |
| |
| /** Command line option 'verbose'. */ |
| private static boolean optionVerbose; |
| |
| /** Prints the CLI usage. */ |
| public static void printUsage() { |
| out.println(); |
| out.println("usage: SignatureVerifier [options] arguments"); |
| out.println("options:"); |
| out.println(" [-h|--help print usage and exit]"); |
| out.println(" [-q|--quiet only print error messages]"); |
| out.println(" [-v|--verbose print verbose messages]"); |
| out.println("arguments:"); |
| out.println(" <signature descriptor file>..."); |
| out.println(); |
| } |
| |
| /** |
| * Parses command line arguments. |
| * |
| * @param args arguments |
| * @return status code |
| */ |
| public static int parseArgs(String[] args) { |
| out.println("parse main() arguments"); |
| |
| // parse this class' options and arguments |
| for (String arg : args) { |
| if (arg == null) { |
| continue; |
| } |
| if (arg.equalsIgnoreCase("-h") || arg.equalsIgnoreCase("--help")) { |
| return -1; |
| } |
| if (arg.equalsIgnoreCase("-v") || arg.equalsIgnoreCase("--verbose")) { |
| optionVerbose = true; |
| continue; |
| } |
| if (arg.equalsIgnoreCase("-q") || arg.equalsIgnoreCase("--quiet")) { |
| optionQuiet = true; |
| continue; |
| } |
| if (arg.startsWith("-")) { |
| out.println("Usage Error: unknown option " + arg); |
| return -1; |
| } |
| // collect argument |
| descrFileNames.add(arg); |
| } |
| |
| // print args |
| if (false) { |
| out.println("descrFileNames = {"); |
| for (String descrFileName : descrFileNames) { |
| out.println(" " + descrFileName); |
| } |
| out.println(" }"); |
| out.println("optionQuiet = " + optionQuiet); |
| out.println("optionVerbose = " + optionVerbose); |
| } |
| |
| // check args |
| if (descrFileNames.isEmpty()) { |
| out.println("Usage Error: Missing argument " + "<signature descriptor file>..."); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /** |
| * Runs the signature test and exits with a status code. |
| * |
| * @param args command line arguments |
| */ |
| public static void main(String[] args) { |
| out.println("run SignatureVerifier ..."); |
| if (parseArgs(args) != 0) { |
| printUsage(); |
| out.println("abort."); |
| System.exit(-1); |
| } |
| |
| int status = 0; |
| try { |
| final SignatureVerifier s = new SignatureVerifier(out, optionQuiet, optionVerbose); |
| status = s.test(descrFileNames); |
| } catch (IOException ex) { |
| out.println("ERROR: exception caught: " + ex); |
| // ex.printStackTrace(); |
| out.println("abort."); |
| System.exit(-2); |
| } catch (ParseException ex) { |
| out.println("ERROR: exception caught: " + ex); |
| // ex.printStackTrace(); |
| out.println("abort."); |
| System.exit(-3); |
| } |
| |
| out.println(); |
| out.println("done."); |
| System.exit(status); |
| } |
| } |