From ed96631f11efc4895a90a68f86f0e75eef8e9e2d Mon Sep 17 00:00:00 2001 From: wisberg Date: Wed, 29 Oct 2003 08:56:38 +0000 Subject: [PATCH] revamped option handling: n-element options, collision/conflict detection/resolution via forcing as an attribute, option families. More compiler options actually handled, and error messages for unhandled options. --- .../harness/bridge/AbstractRunSpec.java | 63 + .../testing/harness/bridge/CompilerRun.java | 1510 +++++++++++------ .../testing/harness/bridge/Globals.java | 114 +- .../aspectj/testing/util/options/Option.java | 707 ++++++++ .../aspectj/testing/util/options/Options.java | 152 ++ .../aspectj/testing/util/options/Values.java | 714 ++++++++ testing/testsrc/TestingModuleTests.java | 1 + .../harness/bridge/CompilerRunSpecTest.java | 496 +++++- .../testing/util/options/OptionChecker.java | 195 +++ .../testing/util/options/OptionsTest.java | 486 ++++++ .../testing/util/options/OptionsTests.java | 30 + .../testing/util/options/ValuesTest.java | 69 + 12 files changed, 3928 insertions(+), 609 deletions(-) create mode 100644 testing/src/org/aspectj/testing/util/options/Option.java create mode 100644 testing/src/org/aspectj/testing/util/options/Options.java create mode 100644 testing/src/org/aspectj/testing/util/options/Values.java create mode 100644 testing/testsrc/org/aspectj/testing/util/options/OptionChecker.java create mode 100644 testing/testsrc/org/aspectj/testing/util/options/OptionsTest.java create mode 100644 testing/testsrc/org/aspectj/testing/util/options/OptionsTests.java create mode 100644 testing/testsrc/org/aspectj/testing/util/options/ValuesTest.java diff --git a/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java b/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java index 8339a25ad..b1e3051e1 100644 --- a/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java +++ b/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java @@ -30,7 +30,10 @@ import org.aspectj.bridge.ISourceLocation; import org.aspectj.bridge.MessageHandler; import org.aspectj.bridge.MessageUtil; import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.util.*; import org.aspectj.testing.util.BridgeUtil; +import org.aspectj.testing.util.options.*; +import org.aspectj.testing.util.options.Option.InvalidInputException; import org.aspectj.testing.xml.IXmlWritable; import org.aspectj.testing.xml.SoftMessage; import org.aspectj.testing.xml.XMLWriter; @@ -955,6 +958,66 @@ abstract public class AbstractRunSpec implements IRunSpec { // XXX use MessageHa verbose = toCopy.verbose; } + /** + * Return any parent option accepted by validOptions, + * optionally removing the parent option. + * @param validOptions String[] of options to extract + * @param remove if true, then remove any parent option matched + * @return String[] containing any validOptions[i] in parentOptions + * + */ + public Values extractOptions( + Options validOptions, + boolean remove, + StringBuffer errors) { + Values result = Values.EMPTY; + if (null == errors) { + errors = new StringBuffer(); + } + if (null == validOptions) { + errors.append("null options"); + return result; + } + if (LangUtil.isEmpty(parentOptions)) { + return result; + } + boolean haveOption = false; + String[] parents = (String[]) parentOptions.toArray(new String[0]); + try { + result = validOptions.acceptInput(parents); + } catch (InvalidInputException e) { + errors.append(e.getFullMessage()); + return result; + } + if (remove) { + Option.Value[] values = result.asArray(); + for (int i = 0; i < values.length; i++) { + Option.Value value = values[i]; + if (null == value) { + continue; + } + final int max = i + value.option.numArguments(); + if (max > i) { + if (max >= parents.length) { + errors.append("expecting more args for " + + value.option + + " at [" + + i + + "]: " + + Arrays.asList(parents)); + return result; + } + // XXX verify + for (int j = i; j < max ; j++) { + parentOptions.remove(parents[j]); + } + i = max-1; + } + } + } + return result; + } + /** * Return any parent option which has one of validOptions as a prefix, * optionally absorbing (removing) the parent option. diff --git a/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java b/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java index 172f0de74..0f0385ac2 100644 --- a/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java +++ b/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java @@ -1,5 +1,6 @@ /* ******************************************************************* - * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC), + * 2003 Contributors. * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Common Public License v1.0 @@ -8,34 +9,22 @@ * * Contributors: * Xerox/PARC initial implementation + * Wes Isberg 2003 updates * ******************************************************************/ package org.aspectj.testing.harness.bridge; -import org.aspectj.bridge.ICommand; -import org.aspectj.bridge.IMessage; -import org.aspectj.bridge.IMessageHandler; -import org.aspectj.bridge.MessageUtil; -import org.aspectj.bridge.ReflectionFactory; +import java.io.*; +import java.util.*; + +import org.aspectj.bridge.*; import org.aspectj.testing.ajde.CompileCommand; -import org.aspectj.testing.run.IRunIterator; -import org.aspectj.testing.run.IRunStatus; -import org.aspectj.testing.run.WrappedRunIterator; +import org.aspectj.testing.run.*; import org.aspectj.testing.taskdefs.AjcTaskCompileCommand; -import org.aspectj.testing.xml.SoftMessage; -import org.aspectj.testing.xml.XMLWriter; -import org.aspectj.util.FileUtil; -import org.aspectj.util.LangUtil; - -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; +import org.aspectj.testing.util.options.*; +import org.aspectj.testing.util.options.Option.*; +import org.aspectj.testing.xml.*; +import org.aspectj.util.*; /** * Run the compiler once. @@ -62,40 +51,37 @@ import java.util.ListIterator; * */ public class CompilerRun implements IAjcRun { - static final String BUILDER_COMPILER = - "org.aspectj.ajdt.internal.core.builder.Builder.Command"; - static final String AJDE_COMPILER = CompileCommand.class.getName(); - static final String AJCTASK_COMPILER - = AjcTaskCompileCommand.class.getName(); -// static final String JAVAC_COMPILER -// = JavacCompileCommand.class.getName(); - - static final String[] RA_String = new String[0]; - + // static final String JAVAC_COMPILER + // = JavacCompileCommand.class.getName(); + + static final String[] RA_String = new String[0]; + static final String[] JAR_SUFFIXES = new String[] { ".jar", ".zip" }; - static final String[] SOURCE_SUFFIXES - = (String[]) FileUtil.SOURCE_SUFFIXES.toArray(new String[0]); - + static final String[] SOURCE_SUFFIXES = + (String[]) FileUtil.SOURCE_SUFFIXES.toArray(new String[0]); + /** specifications, set on construction */ Spec spec; //------------ calculated during setup - /** get shared stuff during setup */ + /** get shared stuff during setup */ Sandbox sandbox; - + /** * During run, these String are passed as the source and arg files to compile. * The list is set up in setupAjcRun(..), when arg files are prefixed with "@". */ - final List /*String*/ arguments; - + final List /*String*/ + arguments; + /** * During run, these String are collapsed and passed as the injar option. * The list is set up in setupAjcRun(..). */ - final List /*String*/ injars; - + final List /*String*/ + injars; + private CompilerRun(Spec spec) { if (null == spec) { throw new IllegalArgumentException("null spec"); @@ -103,8 +89,8 @@ public class CompilerRun implements IAjcRun { this.spec = spec; arguments = new ArrayList(); injars = new ArrayList(); - } - + } + /** * This checks that the spec is reasonable and does setup: * *

- * The -source 1.4 flag should always be specified as -source14, - * as this will otherwise fail to process it correctly. - * This converts it back to -source 1.4. *

* This also interprets any relevant System properties, * e.g., from JavaRun.BOOTCLASSPATH_KEY. @@ -773,298 +898,346 @@ public class CompilerRun implements IAjcRun { * and commandOptions is not null, and any test flags (ignore warning, * compiler) are reflected in the TestSetup. * If this fails, then TestSetup.result is false, - * and a TestSetup.failreason is set. + * and a TestSetup.failreason is set. + * This means the test is skipped. * @return TestSetup with results * (TestSetup result=false if the run should not continue) */ protected TestSetup setupArgs(IMessageHandler handler) { // warning: HarnessSelectionTest checks for specific error wording - ArrayList argList = new ArrayList(); - argList.addAll(getOptionsList()); final Spec spec = this; - TestSetup result = new TestSetup(); - if (argList.contains("-source")) { - result.failureReason = "use -source14 for -source 1.4: " + argList; - return result; - } + final TestSetup result = new TestSetup(); result.compilerName = spec.compiler; - String[] globalOptions = spec.runtime.extractOptions(Spec.VALID_OPTIONS, true); - if ((null != globalOptions) && (globalOptions.length > 0)) { - // --- fold in globals, removing conflicts, etc. - for (int i = 0; i < globalOptions.length; i++) { - String globalArg = globalOptions[i]; - if ((null == globalArg) || (2 > globalArg.length())) { - continue; - } else if (globalArg.startsWith(SEEK_PREFIX)) { - result.seek = globalArg.substring(SEEK_PREFIX.length()); - continue; - } else if ("-source".equals(globalArg)) { - result.failureReason = "use -source14 for -source 1.4 [" + i + "]"; - return result; - } - char first = globalArg.charAt(0); - globalArg = globalArg.substring(1); - boolean globalForceOn = (first == '!'); - boolean globalForceOff = (first == '^'); - boolean globalSet = (first == '-'); - if (!globalSet && !globalForceOn && !globalForceOff) { - MessageUtil.info(handler, "ignoring bad global: " + globalOptions[i]); - continue; - } - int argIndex = indexOf(globalArg, argList); - if (-1 == argIndex) { // no apparent conflict - look for eclipse/ajc conflicts XXX unresolved - boolean ajcGlobal = true; - if ("ajc".equals(globalArg)) { - argIndex = indexOf("eclipse", argList); - } else if ("eclipse".equals(globalArg)) { - argIndex = indexOf("ajc", argList); - ajcGlobal = false; - } - if (-1 != argIndex) { // resolve eclipse/ajc conflict - String arg = ((String) argList.get(argIndex)); - char argFirst = arg.charAt(0); - argList.remove(arg); // replace with resolved variant... - char ajcFirst; - char eclipseFirst; - if (ajcGlobal) { - ajcFirst = first; - eclipseFirst = argFirst; - } else { - ajcFirst = argFirst; - eclipseFirst = first; - } - if ('!' == eclipseFirst) { - if ('!' == ajcFirst) { - result.failureReason = "conflict between !eclipse and !ajc"; - return result; - } else { - argList.add("-eclipse"); - } - } else if (('!' == ajcFirst) || ('^' == eclipseFirst)) { - argList.add("-ajc"); - } else if ('^' == ajcFirst) { - argList.add("-eclipse"); - } else if (('-' != ajcFirst) || ('-' != eclipseFirst)) { - result.failureReason = "harness logic error resolving " - + arg + " and global " + globalArg; - return result; - } else if (ajcGlobal) { - argList.add("-ajc"); - } else { - argList.add("-eclipse"); - } - continue; // resolved - } - } - - if (-1 == argIndex) { // no dup, so no conflict - if (!globalForceOff) { - argList.add("-" + globalArg); - } - } else { // have conflict - resolve - String arg = (String) argList.get(argIndex); - first = arg.charAt(0); - boolean localForceOn = (first == '!'); - boolean localForceOff = (first == '^'); - boolean localSet = (first == '-'); - if (!localSet && !localForceOn && !localForceOff) { - result.failureReason = "only handling [-^!]{arg}: " + arg; - return result; - } - if ((localForceOn && globalForceOff) - || (localForceOff && globalForceOn)) { - result.failureReason = "force conflict between arg=" - + arg + " and global=" + globalOptions[i]; - return result; - } - if (globalForceOn) { - if (localForceOn) { // localSet is already correct, localForceOff was conflict - argList.remove(arg); // no !funkiness - argList.add("-" + globalArg); - } - } else if (globalSet) { - if (localSet) { - // do nothing - already correct - } else if (localForceOn) { - argList.remove(arg); // no !funkiness - argList.add("-" + globalArg); - } - } else if (globalForceOff) { - argList.remove(arg); - } else { - throw new Error("illegal arg state?? : " + arg); - //MessageUtil.info(handler, "illegal arg state??: " + arg); - } - } - } + // urk - s.b. null, but expected + Values values = gatherValues(result); + + if ((null == values) || (null != result.failureReason)) { + return checkResult(result); } + // send info messages about // forced staging when -incremental // or staging but no -incremental flag - int incLoc = argList.indexOf("-incremental"); + Option.Family getFamily = + CRSOPTIONS.crsIncrementalOption.getFamily(); + final boolean haveIncrementalFlag = + (null != values.firstInFamily(getFamily)); + if (spec.isStaging()) { - if (-1 == incLoc) { // staging and no flag - MessageUtil.info(handler, "staging but no -incremental flag"); + if (!haveIncrementalFlag) { + MessageUtil.info( + handler, + "staging but no -incremental flag"); } - } else if (-1 != incLoc) { // flagged but not staging - stage + } else if (haveIncrementalFlag) { spec.setStaging(true); MessageUtil.info(handler, "-incremental forcing staging"); } - // remove funky prefixes from remainder, fixup two-part flags - // and interpret special flags - boolean source14 = false; - ArrayList toAdd = new ArrayList(); - for (ListIterator iter = argList.listIterator(); iter.hasNext();) { - String arg = (String) iter.next(); - if (testFlag(arg, result)) { - iter.remove(); - continue; - } - char c = arg.charAt(0); - String rest = arg.substring(1); - if (c == '^') { - iter.remove(); - continue; - } - if (c == '!') { - iter.remove(); - if (!("source14".equals(rest))) { - toAdd.add("-" + rest); - } else { - source14 = true; - } - rest = null; - } else if ("source14".equals(rest)) { - iter.remove(); - source14 = true; - } + + if (hasInvalidOptions(values, result)) { + return checkResult(result); } - if (source14) { - // must run under 1.4 VM or (if ajc) set up bootclasspath - if (!LangUtil.supportsJava("1.4")) { - if (ReflectionFactory.ECLIPSE.equals(result.compilerName)) { - result.failureReason - = "eclipse must run under 1.4 to implement -source 1.4"; - return result; - } - - if (!FileUtil.canReadFile(Globals.J2SE14_RTJAR)) { - result.failureReason - = "unable to get 1.4 libraries to implement -source 1.4"; - return result; - } - toAdd.add("-bootclasspath"); - toAdd.add(Globals.J2SE14_RTJAR.getAbsolutePath()); - } - toAdd.add("-source"); - toAdd.add("1.4"); - } - argList.addAll(toAdd); - - // finally, check for semantic conflicts - String[] badOptions = LangUtil.selectOptions(argList, Spec.INVALID_OPTIONS); - if (!LangUtil.isEmpty(badOptions)) { - result.failureReason = "no support for (normally-valid) options " - + Arrays.asList(badOptions); - } else if (argList.contains("-lenient") && argList.contains("-strict")) { - result.failureReason = "semantic conflict -lenient | -strict"; - } else if (ReflectionFactory.OLD_AJC.equals(result.compilerName)) { - if (spec.isStaging) { - result.failureReason = "OLD_AJC does not do incremental compiles"; - } else if (argList.contains("-usejavac") && !haveJavac()) { - result.failureReason = "-usejavac but no javac on classpath"; - } else { - result.result = true; - } - } else if (ReflectionFactory.ECLIPSE.equals(result.compilerName) - || AJDE_COMPILER.equals(result.compilerName) - || AJCTASK_COMPILER.equals(result.compilerName) - || BUILDER_COMPILER.equals(result.compilerName)) { - badOptions = LangUtil.selectOptions(argList, Spec.INVALID_ECLIPSE_OPTIONS); - if (!LangUtil.isEmpty(badOptions)) { - result.failureReason = "no support in eclipse-based compiler" - + " for (normally-valid) options "+ Arrays.asList(badOptions); - } else { - result.result = true; + + // set compiler in result + getFamily = CRSOPTIONS.ajccompilerOption.getFamily(); + Option.Value compiler = values.firstInFamily(getFamily); + if (null != compiler) { + result.compilerName + = CRSOPTIONS.compilerClassName(compiler.option); + if (null == result.compilerName) { + result.failureReason = + "unable to get class name for " + compiler; + return checkResult(result); } -// } else if (JAVAC_COMPILER.equals(result.compilerName)) { -// // XXX vet -// badOptions = LangUtil.selectOptions(argList, Spec.INVALID_JAVAC_OPTIONS); -// if (!LangUtil.isEmpty(badOptions)) { -// result.failureReason = "no support in javac" -// + " for (normally-valid) options " + Arrays.asList(badOptions); -// } else { -// result.result = true; -// } - } else { - result.failureReason = "unrecognized compiler: " + result.compilerName; } - if (result.result) { - result.commandOptions = argList; + String compilerName = + (null == result.compilerName + ? spec.compiler + : result.compilerName); + // check compiler semantics + if (hasCompilerSpecErrors(compilerName, values, result)) { + return checkResult(result); + } + // add toadd and finish result + ArrayList args = new ArrayList(); + String[] rendered = values.render(); + if (!LangUtil.isEmpty(rendered)) { + args.addAll(Arrays.asList(rendered)); } - return result; + // update bootclasspath + getFamily = CRSOPTIONS.crsSourceOption.getFamily(); + Option.Value source = values.firstInFamily(getFamily); + if (null != source) { + String sourceVersion = source.unflatten()[1]; + ArrayList toAdd = new ArrayList(); + String err = + updateBootclasspathForSourceVersion( + sourceVersion, + spec.compiler, + toAdd); + args.addAll(toAdd); + } + result.commandOptions = args; + result.result = true; + return checkResult(result); } - /** @return true if javac is available on the classpath */ - private boolean haveJavac() { // XXX copy/paste from JavaCWrapper.java - Class compilerClass = null; - try { - compilerClass = Class.forName("com.sun.tools.javac.Main"); - } catch (ClassNotFoundException ce1) { - try { - compilerClass = Class.forName("sun.tools.javac.Main"); - } catch (ClassNotFoundException ce2) { + /** + * Ensure exit invariant: + * result.result == (null == result.failureReason) + * == (null != result.commandOptions) + * @param result the TestSetup to verify + * @return result + * @throws Error if invariant is not true + */ + TestSetup checkResult(TestSetup result) { + String err = null; + if (null == result) { + err = "null result"; + } else if (result.result != (null == result.failureReason)) { + err = + result.result + ? "expected no failure: " + result.failureReason + : "fail for no reason"; + } else if (result.result != (null != result.commandOptions)) { + err = + result.result + ? "expected command options" + : "unexpected command options"; + } + if (null != err) { + throw new Error(err); + } + return result; + } + + boolean hasInvalidOptions(Values values, TestSetup result) { + // not supporting 1.0 options any more + for (Iterator iter = CRSOPTIONS.invalidOptions.iterator(); + iter.hasNext(); + ) { + Option option = (Option) iter.next(); + if (null != values.firstOption(option)) { + result.failureReason = + "invalid option in harness: " + option; + return true; } } - return (null != compilerClass); - } + return false; + } - - /** - * Handle flags that are interpreted by the test rather than the - * underlying command. These flags are to be removed from the - * arg list. - * @return true if this is a flag to remove from the arg list - */ - protected boolean testFlag(String arg, TestSetup result) { - if ("-ajdeCompiler".equals(arg)) { - result.compilerName = AJDE_COMPILER; - return true; - } else if ("-builderCompiler".equals(arg)) { - result.compilerName = BUILDER_COMPILER; - return true; - } else if ("-ajctaskCompiler".equals(arg)) { - result.compilerName = AJCTASK_COMPILER; - return true; -// } else if ("-javac".equals(arg)) { -// result.compilerName = JAVAC_COMPILER; -// return true; - } else if ("-eclipse".equals(arg) || "!eclipse".equals(arg) || "^ajc".equals(arg)) { - result.compilerName = ReflectionFactory.ECLIPSE; - return true; - } else if ("-ajc".equals(arg) || "!ajc".equals(arg) || "^eclipse".equals(arg)) { - result.compilerName = ReflectionFactory.OLD_AJC; + boolean hasCompilerSpecErrors( + String compilerName, + Values values, + TestSetup result) { + /* + * Describe any semantic conflicts between options. + * This skips: + * - old 1.0 options, including lenient v. strict + * - old ajc options, include !incremental and usejavac w/o javac + * - invalid eclipse options (mostly ajc) + * @param compilerName the String name of the target compiler + * @return a String describing any conflicts, or null if none + */ + if (!(ReflectionFactory.ECLIPSE.equals(compilerName) + || ReflectionFactory.OLD_AJC.equals(compilerName) + || CRSOPTIONS.AJDE_COMPILER.equals(compilerName) + || CRSOPTIONS.AJCTASK_COMPILER.equals(compilerName) + )) { + //|| BUILDER_COMPILER.equals(compilerName)) + result.failureReason = + "unrecognized compiler: " + compilerName; return true; - } else if ("-ignoreWarnings".equals(arg)) { - result.ignoreWarnings = true; - result.ignoreWarningsSet = true; + } + // not supporting ajc right now + if (null + != values.firstOption(CRSOPTIONS.ajccompilerOption)) { + result.failureReason = "ajc not supported"; return true; } + // not supporting 1.0 options any more + for (Iterator iter = CRSOPTIONS.ajc10Options.iterator(); + iter.hasNext(); + ) { + Option option = (Option) iter.next(); + if (null != values.firstOption(option)) { + result.failureReason = "old ajc 1.0 option: " + option; + return true; + } + } + return false; } - + + protected Values gatherValues(TestSetup result) { + final Spec spec = this; + // ---- local option values + final Values localValues; + final Options options = CRSOPTIONS.getOptions(); + try { + String[] input = getOptionsArray(); + // this handles reading options, + // flattening two-String options, etc. + localValues = options.acceptInput(input); + // all local values should be picked up + String err = Options.missedMatchError(input, localValues); + if (!LangUtil.isEmpty(err)) { + result.failureReason = err; + return null; + } + } catch (InvalidInputException e) { + result.failureReason = e.getFullMessage(); + return null; + } + + // ---- global option values + StringBuffer errs = new StringBuffer(); + final Values globalValues = + spec.runtime.extractOptions(options, true, errs); + if (errs.length() > 0) { + result.failureReason = errs.toString(); + return null; + } + final Values combined = + Values.wrapValues( + new Values[] { localValues, globalValues }); + + String err = combined.resolve(); + if (null != err) { + result.failureReason = err; + return null; + } + + return handleTestArgs(combined, result); + } + + // final int len = globalValues.length() + localValues.length(); + // final Option.Value[] combinedValues = new Option.Value[len]; + // System.arraycopy( + // globalValues, + // 0, + // combinedValues, + // 0, + // globalValues.length()); + // System.arraycopy( + // localValues, + // 0, + // combinedValues, + // globalValues.length(), + // localValues.length()); + // + // result.compilerName = spec.compiler; + // if (0 < combinedValues.length) { + // // this handles option forcing, etc. + // String err = Options.resolve(combinedValues); + // if (null != err) { + // result.failureReason = err; + // return null; + // } + // if (!handleTestArgs(combinedValues, result)) { + // return null; + // } + // } + // return Values.wrapValues(combinedValues); + // } + /** + * This interprets and nullifies values for the test. + * @param values the Option.Value[] being processed + * @param result the TestSetup to modify + * @return false if error (caller should return), true otherwise + */ + Values handleTestArgs(Values values, final TestSetup result) { + final Option.Family compilerFamily = + CRSOPTIONS.ajccompilerOption.getFamily(); + Values.Selector selector = new Values.Selector() { + protected boolean accept(Option.Value value) { + if (null == value) { + return false; + } + Option option = value.option; + if (compilerFamily.sameFamily(option.getFamily())) { + if (value.prefix.isSet()) { + String compilerClass + = CRSOPTIONS.compilerClassName(option); + if (null == compilerClass) { + result.failureReason = + "unrecognized compiler: " + value; + throw Values.Selector.STOP; + } + if (!CRSOPTIONS.compilerIsLoadable(option)) { + result.failureReason = + "unable to load compiler: " + option; + throw Values.Selector.STOP; + } + result.compilerName = compilerClass; + } + return true; + } else if ( + CRSOPTIONS.crsIgnoreWarnings.sameOptionIdentifier( + option)) { + result.ignoreWarnings = value.prefix.isSet(); + result.ignoreWarningsSet = true; + return true; + } + return false; + } + }; + return values.nullify(selector); + } + + // /** + // * This interprets and nullifies values for the test. + // * @param values the Option.Value[] being processed + // * @param result the TestSetup to modify + // * @return false if error (caller should return), true otherwise + // */ + // boolean handleTestArgs(Option.Value[] values, TestSetup result) { + // if (!LangUtil.isEmpty(values)) { + // for (int i = 0; i < values.length; i++) { + // Option.Value value = values[i]; + // if (null == value) { + // continue; + // } + // Option option = value.option; + // if (option.sameOptionFamily(ECLIPSE_OPTION)) { + // if (!value.prefix.isSet()) { + // values[i] = null; + // continue; + // } + // String compilerClass = + // (String) COMPILER_OPTION_TO_CLASSNAME.get( + // option); + // if (null == compilerClass) { + // result.failureReason = + // "unrecognized compiler: " + value; + // return false; + // } + // result.compilerName = compilerClass; + // values[i] = null; + // } else if ( + // option.sameOptionFamily(crsIgnoreWarnings)) { + // result.ignoreWarnings = value.prefix.isSet(); + // result.ignoreWarningsSet = true; + // values[i] = null; + // } + // } + // } + // return true; + // } + // XXX need keys, cache... /** @return index of global in argList, ignoring first char */ protected int indexOf(String global, ArrayList argList) { int max = argList.size(); for (int i = 0; i < max; i++) { - if (global.equals(((String) argList.get(i)).substring(1))) { + if (global + .equals(((String) argList.get(i)).substring(1))) { return i; - } + } } return -1; } - + /** * Write this out as a compile element as defined in * AjcSpecXmlReader.DOCTYPE. @@ -1087,13 +1260,19 @@ public class CompilerRun implements IAjcRun { out.printAttribute("includeClassesDir", "true"); } if (!LangUtil.isEmpty(argfiles)) { - out.printAttribute("argfiles", XMLWriter.flattenFiles(argfiles)); + out.printAttribute( + "argfiles", + XMLWriter.flattenFiles(argfiles)); } if (!LangUtil.isEmpty(aspectpath)) { - out.printAttribute("aspectpath", XMLWriter.flattenFiles(argfiles)); + out.printAttribute( + "aspectpath", + XMLWriter.flattenFiles(argfiles)); } if (!LangUtil.isEmpty(sourceroots)) { - out.printAttribute("sourceroots", XMLWriter.flattenFiles(argfiles)); + out.printAttribute( + "sourceroots", + XMLWriter.flattenFiles(argfiles)); } out.endAttributes(); if (!LangUtil.isEmpty(dirChanges)) { @@ -1102,7 +1281,7 @@ public class CompilerRun implements IAjcRun { SoftMessage.writeXml(out, getMessages()); out.endElement(xmlElementName); } - + /** * Encapsulate the directives that can be set using * global arguments supplied in {@link Spec.getOptions()}. @@ -1116,22 +1295,22 @@ public class CompilerRun implements IAjcRun { * to ignore warnings in its result evaluation */ boolean ignoreWarningsSet; - + /** if telling AjcMessageHandler, what we tell it */ boolean ignoreWarnings; - + /** false if setup failed */ boolean result; - + /** if setup failed, this has the reason why */ String failureReason; - + /** beyond running test, also seek text in sources */ String seek; - + /** if setup completed, this has the combined global/local options */ ArrayList commandOptions; - + public Object clone() { TestSetup testSetup = new TestSetup(); testSetup.compilerName = compilerName; @@ -1149,13 +1328,326 @@ public class CompilerRun implements IAjcRun { public String toString() { return "TestSetup(" + (null == compilerName ? "" : compilerName + " ") - + (!ignoreWarningsSet ? "" + + (!ignoreWarningsSet + ? "" : (ignoreWarnings ? "" : "do not ") + "ignore warnings ") + (result ? "" : "setup failed") + ")"; } } + + /** + * Options-related stuff in the spec. + */ + static class CRSOptions { + // static final String BUILDER_COMPILER = + // "org.aspectj.ajdt.internal.core.builder.Builder.Command"; + static final String AJDE_COMPILER = + CompileCommand.class.getName(); + + static final String AJCTASK_COMPILER = + AjcTaskCompileCommand.class.getName(); + + private final Map compilerOptionToLoadable = new TreeMap(); + /* + * The options field in a compiler test permits some arbitrary + * command-line options to be set. It does not permit things + * like classpath, aspectpath, files, etc. which are set + * using other fields in the test specification, so the options + * permitted are a subset of those permitted on the command-line. + * + * Global options specified on the harness command-line are + * adopted for the compiler command-line if they are permitted + * in the options field. That means we have to detect each + * permitted option, rather than just letting all through + * for the compiler. + * + * Conversely, some options are targeted not at the compiler, + * but at the test itself (e.g., to ignore warnings, or to + * select a compiler. + * + * The harness can run many compilers, and they differ in + * which options are permitted. You can specify a compiler + * as an option (e.g., -eclipse). So the set of constraints + * on the list of permitted options can differ from test to test. + * + * The following code sets up the lists of permitted options + * and breaks out subsets for different compiler-variant checks. + * Most options are created but not named, but some options + * are named to detect corresponding values for further + * processing. e.g., the compiler options are saved so + * we can do per-compiler option verification. + * + */ + private final Options crsOptions; + private final Family compilerFamily; + private final Option crsIncrementalOption; + private final Option crsSourceOption; + // these are options handled/absorbed by CompilerRun + private final Option crsIgnoreWarnings; + private final Option eclipseOption; + private final Option buildercompilerOption; + private final Option ajdecompilerOption; + private final Option javacOption; + private final Option ajctaskcompilerOption; + private final Option ajccompilerOption; + private final Map compilerOptionToClassname; + private final Set compilerOptions; + // compiler verification - permit but flag ajc 1.0 options + private final List ajc10Options; + private final List invalidOptions; + + private CRSOptions() { + crsOptions = new Options(true); + Option.Factory factory = new Option.Factory("CompilerRun"); + // compiler options go in map + eclipseOption = + factory.create( + "eclipse", + "compiler", + Option.FORCE_PREFIXES, + false); + compilerFamily = eclipseOption.getFamily(); + buildercompilerOption = + factory.create( + "builderCompiler", + "compiler", + Option.FORCE_PREFIXES, + false); + ajctaskcompilerOption = + factory.create( + "ajctaskCompiler", + "compiler", + Option.FORCE_PREFIXES, + false); + ajdecompilerOption = + factory.create( + "ajdeCompiler", + "compiler", + Option.FORCE_PREFIXES, + false); + ajccompilerOption = + factory.create( + "ajc", + "compiler", + Option.FORCE_PREFIXES, + false); + javacOption = + factory.create( + "javac", + "compiler", + Option.FORCE_PREFIXES, + false); + + Map map = new TreeMap(); + map.put(eclipseOption, ReflectionFactory.ECLIPSE); + //map.put(BUILDERCOMPILER_OPTION, BUILDER_COMPILER); + map.put( + ajctaskcompilerOption, + AJCTASK_COMPILER); + map.put(ajdecompilerOption, AJDE_COMPILER); + map.put(ajccompilerOption, ReflectionFactory.OLD_AJC); + //map.put(JAVAC_OPTION, "XXX javac option not supported"); + compilerOptionToClassname = + Collections.unmodifiableMap(map); + + compilerOptions = + Collections.unmodifiableSet( + compilerOptionToClassname.keySet()); + // options not permitted in the harness + List list = new ArrayList(); + list.add(factory.create("workingdir")); + list.add(factory.create("argfile")); + list.add(factory.create("sourceroots")); + list.add(factory.create("outjar")); + invalidOptions = Collections.unmodifiableList(list); + + // other options added directly + crsIncrementalOption = factory.create("incremental"); + + crsIgnoreWarnings = factory.create("ignoreWarnings"); + + crsSourceOption = + factory + .create( + "source", + "source", + Option.FORCE_PREFIXES, + false, + new String[][] { new String[] { "1.3", "1.4" } + }); + + // ajc 1.0 options + // workingdir above in invalid options + list = new ArrayList(); + list.add(factory.create("usejavac")); + list.add(factory.create("preprocess")); + list.add(factory.create("nocomment")); + list.add(factory.create("porting")); + list.add(factory.create("XOcodeSize")); + list.add(factory.create("XTargetNearSource")); + list.add(factory.create("XaddSafePrefix")); + list.add( + factory.create( + "lenient", + "lenient", + Option.FORCE_PREFIXES, + false)); + list.add( + factory.create( + "strict", + "lenient", + Option.FORCE_PREFIXES, + false)); + ajc10Options = Collections.unmodifiableList(list); + + // -warn:.. and -g/-g:.. are not exclusive + if (!(factory.setupFamily("debug", true) + && factory.setupFamily("warning", true))) { + System.err.println("CompilerRun debug/warning fail!"); + } + Option[] options = + new Option[] { + crsIncrementalOption, + crsIgnoreWarnings, + crsSourceOption, + factory.create( + "Xlint", + "XLint", + Option.FORCE_PREFIXES, + true), + factory.create("verbose"), + factory.create("emacssym"), + factory.create("referenceInfo"), + factory.create("nowarn"), + factory.create("deprecation"), + factory.create("noImportError"), + factory.create("proceedOnError"), + factory.create("preserveAllLocals"), + factory.create( + "warn", + "warning", + Option.STANDARD_PREFIXES, + true), + factory.create( + "g", + "debug", + Option.STANDARD_PREFIXES, + false), + factory.create( + "g:", + "debug", + Option.STANDARD_PREFIXES, + true), + factory.create( + "1.3", + "compliance", + Option.FORCE_PREFIXES, + false), + factory.create( + "1.4", + "compliance", + Option.FORCE_PREFIXES, + false), + factory + .create( + "target", + "target", + Option.FORCE_PREFIXES, + false, + new String[][] { new String[] { + "1.1", + "1.2" }}), + factory.create("XnoInline"), + factory.create("XnoWeave"), + factory.create("XserializableAspects") + }; + for (int i = 0; i < options.length; i++) { + crsOptions.addOption(options[i]); + } + for (Iterator iter = compilerOptions.iterator(); + iter.hasNext(); + ) { + crsOptions.addOption((Option) iter.next()); + } + // these are recognized but records with them are skipped + for (Iterator iter = ajc10Options.iterator(); + iter.hasNext(); + ) { + crsOptions.addOption((Option) iter.next()); + } + crsOptions.freeze(); + } + + Options getOptions() { + return crsOptions; + } + + /** + * @return unmodifiable Set of options sharing the + * family "compiler". + */ + Set compilerOptions() { + return compilerOptions; + } + + /** + * @param option the compiler Option to get name for + * @return null if option is null or not a compiler option, + * or the fully-qualified classname of the ICommand + * implementing the compiler. + */ + String compilerClassName(Option option) { + if ((null == option) + || (!compilerFamily.sameFamily(option.getFamily()))) { + return null; + } + return (String) compilerOptionToClassname.get(option); + } + + /** + * Check that the compiler class associated with a compiler + * option can be loaded. This check only happens once; + * the result is cached (by compilerOption key) for later calls. + * @param compilerOption the Option (family compiler) to check + * @return true if compiler class for this option can be loaded + */ + boolean compilerIsLoadable(Option compilerOption) { + LangUtil.throwIaxIfNull(compilerOption, "compilerName"); + synchronized (compilerOptionToLoadable) { + Boolean result = + (Boolean) compilerOptionToLoadable.get( + compilerOption); + if (null == result) { + MessageHandler sink = new MessageHandler(); + String compilerClassname = + (String) compilerOptionToClassname.get( + compilerOption); + if (null == compilerClassname) { + result = Boolean.FALSE; + } else { + ICommand c = + ReflectionFactory.makeCommand( + compilerClassname, + sink); + + if ((null == c) + || sink.hasAnyMessage( + IMessage.ERROR, + true)) { + result = Boolean.FALSE; + } else { + result = Boolean.TRUE; + } + } + compilerOptionToLoadable.put( + compilerOption, + result); + } + return result.booleanValue(); + } + } + } // CompilerRun.Spec.CRSOptions } // CompilerRun.Spec -} // CompilerRun - \ No newline at end of file +} // CompilerRun \ No newline at end of file diff --git a/testing/src/org/aspectj/testing/harness/bridge/Globals.java b/testing/src/org/aspectj/testing/harness/bridge/Globals.java index 9b0b7736c..e4060d50a 100644 --- a/testing/src/org/aspectj/testing/harness/bridge/Globals.java +++ b/testing/src/org/aspectj/testing/harness/bridge/Globals.java @@ -12,7 +12,7 @@ * Xerox/PARC initial implementation * Wes Isberg removed unused globals. * ******************************************************************/ - + package org.aspectj.testing.harness.bridge; import java.io.File; @@ -23,71 +23,105 @@ import org.aspectj.util.LangUtil; /** */ public class Globals { - public static final String FORK_NAME = "harness.fork"; // XXX in testing-drivers/../package.htm + public static final String FORK_NAME = "harness.fork"; + // XXX in testing-drivers/../package.htm /** name/key of the System property to set to define library dir */ public static final String LIBDIR_NAME = "harness.libdir"; /** name/key of the System property to set to define J2SE_HOME dir */ public static final String J2SE14_RTJAR_NAME = "j2se14.rtjar"; + public static final String J2SE13_RTJAR_NAME = "j2se13.rtjar"; /** assumed relative location of a library with required jars */ - public static final String LIBDIR = getSystemProperty(LIBDIR_NAME, "../lib/test"); + public static final String LIBDIR = + getSystemProperty(LIBDIR_NAME, "../lib/test"); /** Path to J2SE_HOME */ - public static final File J2SE14_RTJAR; // XXX used only by 1.0 compiler tests - deprecate? + public static final File J2SE14_RTJAR; + // XXX used only by 1.0 compiler tests - deprecate? + public static final File J2SE13_RTJAR; /** array of parameter types for main(String[]) */ - public static final Class[] MAIN_PARM_TYPES = new Class[] {String[].class}; - public static final String S_testingclient_jar = LIBDIR + "/testing-client.jar"; - public static final String S_aspectjrt_jar = LIBDIR + "/aspectjrt.jar"; - public static final File F_testingclient_jar = new File(S_testingclient_jar); - public static final File F_aspectjrt_jar = new File(S_aspectjrt_jar); + public static final Class[] MAIN_PARM_TYPES = + new Class[] { String[].class }; + public static final String S_testingclient_jar = + LIBDIR + "/testing-client.jar"; + public static final String S_aspectjrt_jar = LIBDIR + "/aspectjrt.jar"; + public static final File F_testingclient_jar = + new File(S_testingclient_jar); + public static final File F_aspectjrt_jar = new File(S_aspectjrt_jar); public static final boolean globalsValid; - + static { + J2SE13_RTJAR = + getRtJarFor(J2SE13_RTJAR_NAME, "c:/home/apps/jdk13"); + J2SE14_RTJAR = + getRtJarFor(J2SE14_RTJAR_NAME, "c:/home/apps/jdk14"); + + String forkSpec = getSystemProperty(FORK_NAME, null); + globalsValid = + (FileUtil.canReadFile(F_testingclient_jar) + && FileUtil.canReadFile(F_aspectjrt_jar) + && FileUtil.canReadFile(J2SE13_RTJAR) + && FileUtil.canReadFile(J2SE14_RTJAR)); + } + + private static File getRtJarFor( + String propertyName, + String defaultLocation) { File j2seJar = null; - String path = getSystemProperty(J2SE14_RTJAR_NAME, "c:/home/apps/jdk14"); - File j2seHome = new File(path); - if (j2seHome.exists() && j2seHome.isDirectory()) { - File rtjar = new File(j2seHome.getAbsolutePath() + "/jre/lib/rt.jar"); - if (rtjar.canRead() && rtjar.isFile()) { - j2seJar = rtjar; + try { + String path = getSystemProperty(propertyName, defaultLocation); + File file = new File(path); + if (file.exists()) { + File rtjar = null; + if (path.endsWith("rt.jar")) { + rtjar = file; + } else if (file.isDirectory()) { + path = file.getAbsolutePath() + "/jre/lib/rt.jar"; + rtjar = new File(path); + } + if (rtjar.canRead() && rtjar.isFile()) { + j2seJar = rtjar; + } + } + } catch (Throwable t) { // avoid at all costs during static init + try { + t.printStackTrace(System.err); + } catch (Throwable x) { + // unable to log, better to just fail... } } - J2SE14_RTJAR = j2seJar; - globalsValid = - (FileUtil.canReadFile(F_testingclient_jar) - && FileUtil.canReadFile(F_aspectjrt_jar) - && FileUtil.canReadFile(J2SE14_RTJAR) - ); + return j2seJar; } - /** - * - * @return null if not found, or + /** + * + * @return null if not found, or * String with class path for compiler to load J2SE 1.4 classes from. - */ - public static String get14Bootclasspath() { + */ + public static String get14Bootclasspath() { return null; - } + } - /** - * Get System property completely safely. - * @param propertyName the String name of the property to get - * @param defaultValue the String default value to return value is null or empty - * @return String value or defaultValue if not available. - */ - static String getSystemProperty( - String propertyName, - String defaultValue) { + /** + * Get System property completely safely. + * @param propertyName the String name of the property to get + * @param defaultValue the String default value to return value is null or empty + * @return String value or defaultValue if not available. + */ + static String getSystemProperty( + String propertyName, + String defaultValue) { String result = defaultValue; try { String value = System.getProperty(propertyName); if (!LangUtil.isEmpty(value)) { result = value; } - } catch (Throwable t) {} - return result; - } + } catch (Throwable t) { + } + return result; + } } diff --git a/testing/src/org/aspectj/testing/util/options/Option.java b/testing/src/org/aspectj/testing/util/options/Option.java new file mode 100644 index 000000000..85917922e --- /dev/null +++ b/testing/src/org/aspectj/testing/util/options/Option.java @@ -0,0 +1,707 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Wes Isberg initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util.options; + +import java.util.*; + +import org.aspectj.util.LangUtil; + +/** + * Immutable schema for an input (command-line) option. + * The schema contains the expected name/label, + * the family (for comparison purposes), + * and permitted prefixes. + * This has operations to accept input values and compare options. + * Options cannot be created directly; for that, use an + * Option.Factory, since it enforces uniqueness + * within the families and options created by the factory. + *

+ * Option is used with related nested classes to implement relations: + *

+ * The classes are nested as "friends" in order to hide private + * members (esp. constructors) that can be used within relations. + * Most of these classes are immutable. + */ +public class Option implements Comparable { + public static final Prefix ON = new Prefix("-", "on", true, false); + public static final Prefix NONE = new Prefix("", "none", true, false); + public static final Prefix FORCE_ON = + new Prefix("!", "force-on", true, true); + + public static final Prefix FORCE_OFF = + new Prefix("^", "force-off", false, true); + public static final Prefixes LITERAL_PREFIXES = + new Prefixes(new Prefix[] { NONE }); + public static final Prefixes STANDARD_PREFIXES = + new Prefixes(new Prefix[] { ON }); + public static final Prefixes FORCE_PREFIXES = + new Prefixes(new Prefix[] { ON, FORCE_ON, FORCE_OFF }); + + /** family is the key for comparing two options */ + private final Family family; + + /** label expected for the option */ + private final String name; + + /** unique identifier for option */ + private final String optionIdentifier; + + /** prefixes permitted for the option in input */ + private final Prefixes permittedPrefixes; + + /** if true, then match on input that has extra suffix beyond prefix and name */ + private final boolean acceptSuffixedInput; + + /** + * If true, no collision if there are multiple values + * that share the same family but not the same literal value + */ + private final boolean permitMultipleValues; + + /** int number of arguments expected after the option itself */ + private final int numArguments; + + /** + * If numArguments > 0, each element has a list of arguments + * permitted at that index from the initial matching value. + * Elements are not null. + */ + private final String[][] permittedArguments; + + private final int nameLength; + + /* + * Create a standard named boolean option, + * permitting force-on and force-off. + * @param name the String name of the option, e.g., "1.3" for "-1.3" + * @param family + * @param permittedPrefixes + * @param acceptSuffixedInput + * @param permittedArguments + */ + public Option( + String name, + Family family, + Prefixes permittedPrefixes, + boolean acceptSuffixedInput, + String[][] permittedArguments) { + LangUtil.throwIaxIfNull(name, "name"); + LangUtil.throwIaxIfNull(family, "family"); + LangUtil.throwIaxIfNull(permittedPrefixes, "permittedPrefixes"); + this.name = name; + this.nameLength = name.length(); + this.family = family; + this.permittedPrefixes = permittedPrefixes; + this.acceptSuffixedInput = acceptSuffixedInput; + this.permitMultipleValues = false; + if (LangUtil.isEmpty(permittedArguments)) { + permittedArguments = new String[][] { }; + // nominal, unused + } else { + String[][] temp = new String[permittedArguments.length][]; + for (int i = 0; i < temp.length; i++) { + String[] toCopy = permittedArguments[i]; + LangUtil.throwIaxIfNull(toCopy, "no permitted args"); + final int len = toCopy.length; + String[] variants = new String[len]; + System.arraycopy(toCopy, 0, variants, 0, len); + temp[i] = variants; + } + permittedArguments = temp; + } + this.permittedArguments = permittedArguments; + numArguments = permittedArguments.length; + optionIdentifier = family.familyName + "." + name; + } + + public int compareTo(Object other) { + Option option = (Option) other; + int diff = family.compareTo(option.family); + if (0 == diff) { + diff = name.compareTo(option.name); + } + return diff; + } + + public Family getFamily() { + return family; + } + + public boolean permitMultipleValues() { + return permitMultipleValues; + } + + /** + * @return int number of elements in this option, + * e.g., 0 for -g or 1 for -source 1.4 + */ + public int numArguments() { + return numArguments; + } + + /** + * If this value String represents a valid input for this option, + * then create and return the associated Value. + * + * @param value the Value created, or null if invalid + * @return Value if this value is permitted by this option + */ + public Value acceptValue(String value) { + Prefix prefix = hasPrefix(value); + if (null != prefix) { + if (value.startsWith(name, prefix.length())) { + value = value.substring(prefix.length()); + if (value.length() == nameLength) { + return new Value(value, prefix, this); + } else if (acceptSuffixedInput) { + return new Value(value, prefix, this); + } else { + return rejectingSuffixedInput(value); + } + } + } + return null; + } + + /** @return true if we have same option family */ + public boolean sameOptionFamily(Option other) { + return ((null != other) && other.family.equals(family)); + } + + /** @return true if we have same option family and name */ + public boolean sameOptionIdentifier(Option other) { + return (sameOptionFamily(other) && name.equals(other.name)); + } + + public String toString() { + return name; + } + + /** + * Called when ignoreSuffix is off but we got value with suffix. + */ + protected Value rejectingSuffixedInput(String value) { + return null; + } + + /** + * Verify that the input is permitted at this position. + * @param input the String input to check for validity + * @param position the int proposed position (0-based) + * for the input (position 0 is for first argument) + * @return null if this input is valid at this position, + * or a String error message otherwise. + */ + String validArgument(String input, int position) { + if (null == input) { + return "null input"; + } + // assert numArguments == permittedInput.length + if ((position < 0) || (position >= numArguments)) { + return "no input permitted at " + position; + } + String[] permitted = permittedArguments[position]; + for (int i = 0; i < permitted.length; i++) { + if (input.equals(permitted[i])) { + return null; + } + } + return input + " not permitted, expecting one of " + + Arrays.asList(permitted); + } + + String getName() { + return name; + } + Object getKey() { + return family; + } + + private String optionIdentifier() { + return optionIdentifier; + } + + private Prefix hasPrefix(String value) { + for (Iterator iter = permittedPrefixes.iterator(); + iter.hasNext(); + ) { + Prefix prefix = (Prefix) iter.next(); + if (-1 != prefix.prefixLength(value)) { + return prefix; + } + } + return null; + } + + /** + * An option family identifies a set of related options that + * might share no literal specification. + * E.g., the compiler family of options might include + * -ajc and -eclipse, and the debugInfo family of options + * might include -g and -g:vars. + * Option families may permit or avoid option collisions. + *

+ * For subclasses to permit some collisions and not others, + * they should set permitMultipleFamilyValues to false + * and implement doCollision(Option, Option). + *

+ * This relies on Factory to ensure that familyName is + * a unique identifier for the factory. + */ + public static class Family implements Comparable { + + /** unique String identifier for this family */ + private final String familyName; + + /** if true, then report no collisions */ + private final boolean permitMultipleFamilyValues; + + protected Family( + String familyName, + boolean permitMultipleFamilyValues) { + this.familyName = familyName; + this.permitMultipleFamilyValues = permitMultipleFamilyValues; + } + + public int compareTo(Object arg0) { + Family family = (Family) arg0; + return familyName.compareTo(family.familyName); + } + + public boolean sameFamily(Family family) { + return ( + (null != family) && familyName.equals(family.familyName)); + } + + boolean permitMultipleFamilyValues() { + return permitMultipleFamilyValues; + } + + /** + * Options collide if they share the same family + * but are not the same, + * and multiple values are not permitted by the family. + * @param lhs the Option to compare with rhs + * @param rhs the Option to compare with lhs + * @return true if the two options collide, false otherwise + * @throws IllegalArgumentException if the input differ + * and share the same family, but this isn't it. + */ + public final boolean collision(Option lhs, Option rhs) { + if ((lhs == rhs) || (null == lhs) || (null == rhs)) { + return false; + } + Family lhsFamily = lhs.getFamily(); + Family rhsFamily = rhs.getFamily(); + if (!(lhsFamily.sameFamily(rhsFamily))) { + return false; + } + if (lhs.sameOptionIdentifier(rhs)) { + return false; + } + if (this != lhsFamily) { + String s = + "expected family " + this +" got family " + lhsFamily; + throw new IllegalArgumentException(s); + } + return doCollision(lhs, rhs); + } + + /** + * Subclasses implement this to resolve collisions on + * a case-by-case basis. Input are guaranteed to be + * non-null, different, and to share this family. + * This implementation returns + * !permitMultipleFamilyValues. + * + * @param lhs the Option to compare + * @param rhs the other Option to compare + * @return true if there is a collision. + */ + protected boolean doCollision(Option lhs, Option rhs) { + return !permitMultipleFamilyValues; + } + } + + /** + * A factory enforces a namespace on options. + * All options produced from a given factory are unique, + * as are all families. + * Once an option or family is created, it cannot be changed. + * To have a family permit multiple values + * (i.e., ignore collisions), set up the family before any + * associated options are created using + * setupFamily(String, boolean). + */ + public static class Factory { + private final String factoryName; + + /** enforce uniqueness of family */ + private final Map familyNameToFamily = new TreeMap(); + + /** enforce uniqueness of options */ + private final ArrayList names = new ArrayList(); + + public Factory(String factoryName) { + this.factoryName = factoryName; + } + + /** + * Ensure that the family with this name has the + * specified permission. If the family does not exist, + * it is created. If it does, the permission is checked. + * If this returns false, there is no way to change the + * family permission. + * @param name the String identifier for the family + * @param permitMultipleValues the boolean permission whether to + * allow multiple values in this family + * @return true if family exists with this name and permission + */ + public boolean setupFamily( + String name, + boolean permitMultipleValues) { + LangUtil.throwIaxIfNull(name, "name"); + Family family; + synchronized (familyNameToFamily) { + family = (Family) familyNameToFamily.get(name); + if (null == family) { + family = new Family(name, permitMultipleValues); + familyNameToFamily.put(name, family); + } else if ( + permitMultipleValues + != family.permitMultipleFamilyValues) { + return false; + } + } + return true; + } + + /** + * Register a family with this factory. + * @return null if the family was successfully registered, + * or a String error otherwise + */ + public String registerFamily(Family family) { + if (null == family) { + return "null family"; + } + synchronized (familyNameToFamily) { + Family knownFamily = + (Family) familyNameToFamily.get(family.familyName); + if (null == knownFamily) { + familyNameToFamily.put(family.familyName, family); + } else if (!knownFamily.equals(family)) { + return "different family registered, have " + + knownFamily + + " registering " + + family; + } + } + + return null; + } + + public Option create(String name) { + return create(name, name, FORCE_PREFIXES, false); + } + + public Option create( + String name, + String family, + Prefixes permittedPrefixes, + boolean acceptSuffixedInput) { + return create( + name, + family, + permittedPrefixes, + acceptSuffixedInput, + (String[][]) null); + } + + public Option create( + String name, + String family, + Prefixes permittedPrefixes, + boolean acceptSuffixedInput, + String[][] permittedArguments) { + LangUtil.throwIaxIfNull(name, "name"); + LangUtil.throwIaxIfNull(family, "family"); + LangUtil.throwIaxIfNull( + permittedPrefixes, + "permittedPrefixes"); + Family resolvedFamily; + synchronized (familyNameToFamily) { + resolvedFamily = (Family) familyNameToFamily.get(family); + if (null == resolvedFamily) { + resolvedFamily = new Family(family, false); + familyNameToFamily.put(family, resolvedFamily); + } + } + Option result = + new Option( + name, + resolvedFamily, + permittedPrefixes, + acceptSuffixedInput, + permittedArguments); + synchronized (names) { + String optionIdentifier = result.optionIdentifier(); + if (names.contains(optionIdentifier)) { + String s = "not unique: " + result; + throw new IllegalArgumentException(s); + } else { + names.add(optionIdentifier); + } + } + return result; + } + + public String toString() { + return Factory.class.getName() + ": " + factoryName; + } + + private void checkUnique(Option result) { + String name = result.family + "." + result.name; + } + } + + /** + * The actual input value for an option. + * When an option takes arguments, all the arguments + * are absorbed/flattened into its value. + */ + public static class Value { + private static final String FLATTEN_DELIM = "_"; + + private static final int NOTARGUMENT = -1; + + private static String flatten(String prefix, String suffix) { + return prefix + FLATTEN_DELIM + suffix; + } + + private static String[] unflatten(Value value) { + if (value.argIndex == Value.NOTARGUMENT) { + return new String[] { value.value }; + } + StringTokenizer st = + new StringTokenizer(value.value, FLATTEN_DELIM); + String[] result = new String[st.countTokens()]; + // assert result.length == 1+inputIndex + for (int i = 0; i < result.length; i++) { + result[i] = st.nextToken(); + } + return result; + + } + + public final String value; + public final Prefix prefix; + public final Option option; + private final int argIndex; + + private Value( + String value, + Prefix prefix, + Option option) { + this(value, prefix, option, NOTARGUMENT); + } + private Value( + String value, + Prefix prefix, + Option option, + int argIndex) { + this.value = value; + this.prefix = prefix; + this.option = option; + this.argIndex = argIndex; + // asserts deferred - local clients only + // assert null != value + // assert null != prefix + // assert null != option + // assert 0 <= inputIndex + // assert inputIndex <= option.numArguments() + // assert {number of DELIM} == argIndex + } + + public String[] unflatten() { + return unflatten(this); + } + + /** + * Create new value same as this, but with new prefix. + * If the prefix is the same, return this. + * @param prefix the Prefix to convert to + * @return Value with new prefix - never null + */ + public Value convert(Prefix prefix) { + LangUtil.throwIaxIfNull(prefix, "prefix"); + if (this.prefix.equals(prefix)) { + return this; + } + return new Value( + this.value, + prefix, + this.option, + this.argIndex); + } + + /** + * + * @param other + * @return true if other == this for purposes of collisions + */ + public boolean sameValueIdentifier(Value other) { + return ( + (null != other) + && sameValueIdentifier(option, other.value)); + } + + public boolean sameValueIdentifier(Option option, String value) { + return ( + (null != option) + && this.option.sameOptionIdentifier(option) + && this.value.equals(value)); + } + + public boolean conflictsWith(Value other) { + return ( + (null != other) + && option.equals(other.option) + && (prefix.force == other.prefix.force) + && ((prefix.set != other.prefix.set) + || !value.equals(other.value))); + } + + public String toString() { + return option + "=" + prefix + value; + } + + final Value nextInput(String input) throws InvalidInputException { + final int index = argIndex + 1; + String err = option.validArgument(input, index); + if (null != err) { + throw new InvalidInputException(err, input, option); + } + return new Value(flatten(value, input), prefix, option, index); + } + } + + /** + * A bunch of prefixes. + */ + public static class Prefixes { + final List list; + private Prefixes(Prefix[] prefixes) { + if (LangUtil.isEmpty(prefixes)) { + list = Collections.EMPTY_LIST; + } else { + list = + Collections.unmodifiableList( + Arrays.asList( + LangUtil.safeCopy(prefixes, new Prefix[0]))); + } + } + public Iterator iterator() { + return list.iterator(); + } + } + + /** + * A permitted prefix for an option, mainly so that options + * "-verbose", "^verbose", and "!verbose" can be treated + * as variants (set, force-off, and force-on) of the + * same "verbose" option. + */ + public static class Prefix { + private final String prefix; + private final int prefixLength; + private final String name; + private final boolean set; + private final boolean force; + private Prefix( + String prefix, + String name, + boolean set, + boolean force) { + this.prefix = prefix; + this.name = name; + this.set = set; + this.force = force; + this.prefixLength = prefix.length(); + } + + /** + * Render a value for input if this is set. + * @param value the String to render as an input value + * @return null if value is null or option is not set, + * "-" + value otherwise + */ + public String render(String value) { + return ((!set || (null == value)) ? null : "-" + value); + } + + boolean forceOff() { + return force && !set; + } + boolean forceOn() { + return force && set; + } + public boolean isSet() { + return set; + } + private int length() { + return prefixLength; + } + private int prefixLength(String input) { + if ((null != input) && input.startsWith(prefix)) { + return length(); + } + return -1; + } + public String toString() { + return prefix; + } + } + + /** + * Thrown when an Option specifies required arguments, + * but the arguments are not available. + */ + public static class InvalidInputException extends Exception { + public final String err; + public final String input; + public final Option option; + InvalidInputException(String err, String input, Option option) { + super(err); + this.err = err; + this.input = input; + this.option = option; + } + public String getFullMessage() { + return "illegal input \"" + + input + + "\" for option " + + option + + ": " + + err; + } + } +} diff --git a/testing/src/org/aspectj/testing/util/options/Options.java b/testing/src/org/aspectj/testing/util/options/Options.java new file mode 100644 index 000000000..642ea0ca7 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/options/Options.java @@ -0,0 +1,152 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Wes Isberg initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util.options; + +import java.util.*; + +import org.aspectj.util.LangUtil; + +/** + * A bunch of options that handle search boilerplate. + * This enforces an initialization phase by permitting + * options to be added only until frozen, and + * permitting matching only after frozen. + */ +public class Options { + + /** if true, then perform extra checks to debug problems */ + private static final boolean verifying = false; + private static final boolean FROZEN = true; + + /** + * List input unmatched by options, if any. + * @param input the String[] used to generate the values + * @param values the Option.Value[] found from the input + * @return null if no values are null, String list of missed otherwise + */ + public static String missedMatchError( + String[] input, + Values values) { + int[] missed = values.indexMissedMatches(); + LangUtil.throwIaxIfNull(input, "input"); + LangUtil.throwIaxIfNull(values, "values"); + LangUtil.throwIaxIfFalse( + input.length == values.length(), + "input is not matched by values"); + if (0 == missed.length) { + return null; + } + StringBuffer sb = new StringBuffer(); + sb.append("missed values: ["); + for (int i = 0; i < missed.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(missed[i] + ": " + input[missed[i]]); + } + sb.append("]"); + return sb.toString(); + } + + private final ArrayList options = new ArrayList(); + private final boolean stopAtFirstMatch; + private boolean frozen = !FROZEN; + + public Options(boolean stopAtFirstMatch) { + this.stopAtFirstMatch = stopAtFirstMatch; + } + + public void freeze() { + if (frozen != FROZEN) { + frozen = FROZEN; + } + } + + public boolean isFrozen() { + return (frozen == FROZEN); + } + + public void addOption(Option option) { + checkFrozen("adding option", !FROZEN); + LangUtil.throwIaxIfNull(option, "option"); + options.add(option); + } + + /** + * Associate options matched, if any, with input by index. + * If an input element is not matched, the corresponding + * result element will be null. + * If there are multi-argument options matched, then + * only the initial element will be non-null, but it + * will contain the accumulated value of the arguments. + * @param input the String[] of input + * @return Option.Value[] corresponding to input + * @throws Option.InvalidInputException when encountering + * invalid arguments to a matched multi-argument option. + */ + public Values acceptInput(String[] input) + throws Option.InvalidInputException { + checkFrozen("matching options", FROZEN); + if ((null == input) || (0 == input.length)) { + return Values.EMPTY; + } + Option.Value[] results = new Option.Value[input.length]; + for (int i = 0; i < input.length; i++) { + Option.Value result = firstMatch(input[i]); + final int index = i; + if (null != result) { + for (int len = result.option.numArguments(); + len > 0; + len--) { + i++; + if (i >= input.length) { + throw new Option.InvalidInputException( + "not enough arguments", + null, + result.option); + } + result = result.nextInput(input[i]); + } + } + results[index] = result; + } + return Values.wrapValues(results); + } + + private void checkFrozen(String actionLabel, boolean expectFrozen) { + if (expectFrozen != isFrozen()) { + if (null == actionLabel) { + actionLabel = "use"; + } + if (expectFrozen) { + actionLabel = "must freeze before " + actionLabel; + } else { + actionLabel = "frozen before " + actionLabel; + } + throw new IllegalStateException(actionLabel); + } + } + + private Option.Value firstMatch(String value) { + LangUtil.throwIaxIfNull(value, "value"); + ArrayList list = new ArrayList(); + for (Iterator iter = options.iterator(); iter.hasNext();) { + Option option = (Option) iter.next(); + Option.Value result = option.acceptValue(value); + if (null != result) { + return result; + } + } + return null; + } +} diff --git a/testing/src/org/aspectj/testing/util/options/Values.java b/testing/src/org/aspectj/testing/util/options/Values.java new file mode 100644 index 000000000..21cd3b52f --- /dev/null +++ b/testing/src/org/aspectj/testing/util/options/Values.java @@ -0,0 +1,714 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Wes Isberg initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util.options; + +import java.util.*; + +import org.aspectj.testing.util.options.Option.Value; +import org.aspectj.util.LangUtil; + +/** + * Wrapper for Value[] that handles search boilerplate. + */ +public class Values { + public static final Values EMPTY; + /** used by methods taking Selector to halt processing early */ + private static final boolean VERIFYING = true; + private static final boolean FIND_ALL = true; + + private static final String NO_ERROR = "no error"; + + static { + EMPTY = new Values(new Value[0]); + } + + public static Values wrapValues(Value[] values) { + if ((null == values) || (0 == values.length)) { + return EMPTY; + } + return new Values(values); + } + + public static Values wrapValues(Values[] values) { + if ((null == values) || (0 == values.length)) { + return EMPTY; + } + Value[] input = null; + if (values.length == 1) { + input = values[0].asArray(); + LangUtil.throwIaxIfNull(input, "values"); + } else { + int length = 0; + for (int i = 0; i < values.length; i++) { + if (values[i] == null) { + LangUtil.throwIaxIfNull(null, "null value[" + i + "]"); + } + length += values[i].length(); + } + input = new Value[length]; + length = 0; + Value[] temp; + for (int i = 0; i < values.length; i++) { + temp = values[i].asArray(); + System.arraycopy(temp, 0, input, length, temp.length); + length += temp.length; + } + } + return new Values(input); + } + + static int[] invert(int[] missed, int length) { + final int MAX = length; + final int len = MAX - missed.length; + final int[] result = new int[len]; + int missedIndex = 0; + int resultIndex = 0; + for (int counter = 0; counter < MAX; counter++) { + // catch result up to missed + while (((missedIndex >= missed.length) + || (missed[missedIndex] > counter)) + && (counter < MAX)) { + result[resultIndex++] = counter++; + } + // absorb missed up to counter + while ((missedIndex < missed.length) + && (missed[missedIndex] <= counter) + && (counter < MAX)) { + missedIndex++; + } + } + return result; + } + + private static Option.Value[] toArray(ArrayList list) { + return (Option.Value[]) list.toArray(new Option.Value[0]); + } + + /** + * Resolve input to remove any values matching the same options, + * where option conflicts are handled by option forcing. + * First, for any force-off value, all matching set-on and + * the force-off itself are removed. At this time, if there + * is a matching force-on, then this will return an error. + * Next, for any force-on value, it is converted to set-on, + * and any other matching set-on value is removed. + * Finally, this signals a collision if two values share + * the same option family and the family reports that this is + * a collision. + * In all cases, only the first error detected is reported. + * @param input the Option.Value[] matched from the input, + * (forced/duplicate options will be set to null) + * @return String error during resolution, or null if no error + */ + private static String resolve(Option.Value[] input) { + String err = null; + if (LangUtil.isEmpty(input)) { + return null; + } + + Map familyToMatches = new TreeMap(); + for (int i = 0;(null == err) && (i < input.length); i++) { + if (null != input[i]) { + Option.Family family = input[i].option.getFamily(); + int[] matches = (int[]) familyToMatches.get(family); + if (null == matches) { + matches = match(input, i); + familyToMatches.put(family, matches); + } + } + } + + familyToMatches = Collections.unmodifiableMap(familyToMatches); + for (Iterator iter = familyToMatches.entrySet().iterator(); + (null == err) && iter.hasNext(); + ) { + Map.Entry entry = (Map.Entry) iter.next(); + int[] matches = (int[]) entry.getValue(); + err = resolve(input, matches); + } + return err; + } + + /** + * Resolve all related options into one + * by nullifying or modifying the values. + * + * First, for any force-off value, + * remove all identical set-on + * and the force-off itself, + * and alert on any identical force-on. + * + * Next, for any force-on value, + * convert to set-on, + * throw Error on any same-family force-off value, + * remove any identical force-on or set-on value, + * alert on any other non-identical same-family force-on value, + * remove any same-family set-on value, + * and alert on any same-family set-off value. + * + * Finally, alert if any two remaining values share + * the same option family, unless the option is marked + * as permitting multiple values. + * + * @param input the Option.Value[] matching the input + * @param matches the int[] list of indexes into input for + * values for related by option + * (all such values must have option matched by family) + * @return String error, if any, or null if no error + * @see #match(Option.Value[], int) + */ + private static String resolve(Option.Value[] input, int[] matches) { + String err = null; + // seek force-off + Option.Value forceOff = null; + Option option = null; + // find and remove any force-off + for (int i = 0;(null == err) && (i < matches.length); i++) { + Option.Value value = input[matches[i]]; + if (null != value) { + // verify that matches are in the same family + if (VERIFYING) { + if (null == option) { + option = value.option; + } else if (!(option.sameOptionFamily(value.option))) { + String s = + value.option + + " has different family from " + + option; + throw new IllegalArgumentException(s); + } + } + if (value.prefix.forceOff()) { + err = removeForceOff(input, value, matches); + } + } + } + // find and set any force-on, removing others + for (int i = 0;(null == err) && (i < matches.length); i++) { + Option.Value value = input[matches[i]]; + if (null != value) { + if (value.prefix.forceOn()) { + err = convertForceOn(input, i, matches); + } + } + } + // remove any exact duplicates + for (int i = 0;(null == err) && (i < matches.length); i++) { + Option.Value value = input[matches[i]]; + if (null != value) { + for (int j = i + 1; j < matches.length; j++) { + if (value.sameValueIdentifier(input[matches[j]])) { + input[matches[j]] = null; + } + } + } + } + // signal error if two left unless permitMultipleFamilyValues + Option.Value first = null; + for (int i = 0;(null == err) && (i < matches.length); i++) { + Option.Value value = input[matches[i]]; + if (null != value) { + if (null == first) { + first = value; + if (first + .option + .getFamily() + .permitMultipleFamilyValues()) { + break; + } + } else { + err = "collision between " + first + " and " + value; + } + } + } + + return err; + } + + /** + * For any force-off value, + * remove all set-on or force-off with same value + * (including the force-off itself), + * and alert on any identical force-on. + * @param input the Option.Value[] matching the input + * @param value the force-off Option.Value to remove + * @param matches the int[] list of indexes into input for + * values for related by option + * (all such values must have matching option) + * @return String error if any + */ + private static String removeForceOff( + Option.Value[] input, + Option.Value value, + int[] matches) { + if (!value.prefix.forceOff()) { + throw new IllegalArgumentException( + "expecting force-off: " + value); + } + for (int i = 0; i < matches.length; i++) { + Option.Value match = input[matches[i]]; + if ((null != match) && value.sameValueIdentifier(match)) { + if (match.prefix.forceOn()) { + return "force conflict between " + + value + + " and " + + match; + } else { + input[matches[i]] = null; // unset matches[i]? + } + } + } + return null; + } + + /** + * For this force-on value, convert to set-on, + * throw Error on any same-family force-off value, + * remove any identical force-on or set-on value, + * alert on any other non-identical same-family force-on value, + * remove any same-family set-on value, + * and alert on any same-family set-off value. + * This must be called after removeForceOff(..). + * @param input the Option.Value[] to modify + * @param valueIndex the int index in matches to find the force-on + * and to start after + * @param matches the int[] map into input entries with matching options + * @return + * @throw Error if any matching force-off found + */ + private static String convertForceOn( + Option.Value[] input, + int valueIndex, + int[] matches) { + Option.Value value = input[matches[valueIndex]]; + if (!value.prefix.forceOn()) { + throw new IllegalArgumentException( + "expecting force-on: " + value); + } + input[matches[valueIndex]] = value.convert(Option.ON); + for (int i = 0; i < matches.length; i++) { + if (i == valueIndex) { + continue; + } + Option.Value match = input[matches[i]]; + if (null != match) { + // assert match.sameOptionFamily(value); + if (match.prefix.forceOff()) { + throw new Error( + "unexpected force-off:" + + match + + " when processing " + + value); + } + if (value.option.sameOptionIdentifier(match.option)) { + input[matches[i]] = null; + // remove any identical force-on or set + } else if (match.prefix.forceOn()) { + return "conflict between " + match + " and " + value; + } else if (match.prefix.isSet()) { + input[matches[i]] = null; + // remove any same-value set-on value + } else { // same family, force-off + return "collision between " + match + " and " + value; + } + } + } + return null; + } + + /** + * Get a list of input matching the option in the initial value, + * rendered as indexes into the input array. + * @param input the Option.Value[] to seek in + * @param start the int index of the starting position + * @return int[] of indexes into input with the same option + * as index[start] - never null, but can be empty + */ + private static int[] match(Option.Value[] input, int start) { + IntList result = new IntList(); + Option.Family key = null; + Option.Family nextKey = null; + for (int i = start; i < input.length; i++) { + if (null != input[i]) { + nextKey = input[i].option.getFamily(); + if (null == key) { + key = nextKey; + result.add(i); + } else if (key.equals(nextKey)) { + result.add(i); + } + } + } + return result.getList(); + } + + static int nullify(Option.Value[] values, Selector selector) { + LangUtil.throwIaxIfNull(selector, "selector"); + int changed = 0; + for (int i = 0; i < values.length; i++) { + final boolean accepted; + try { + accepted = selector.accept(values[i]); + } catch (Error e) { + if (e != Selector.STOP) { + throw e; + } + break; + } + if (accepted) { + if (null != values[i]) { + values[i] = null; + changed++; + } + } + } + return changed; + } + + /** + * Render set values as String using associated prefix. + * @param values the Value[] to render + * @return String[] of values rendered for output + * (never null or longer than values, but might be shorter) + */ + private static String[] render(Value[] values) { + ArrayList list = new ArrayList(); + for (int i = 0; i < values.length; i++) { + if (null != values[i]) { + String[] output = values[i].unflatten(); + if (LangUtil.isEmpty(output)) { + throw new Error("no output for " + values[i]); + } + + String s = values[i].prefix.render(output[0]); + if (null != s) { // this means the prefix is set + list.add(s); + for (int j = 1; j < output.length; j++) { + list.add(output[j]); + } + } + } + } + return (String[]) list.toArray(new String[list.size()]); + } + + private final Option.Value[] values; + private Option.Value[] valuesNotNull; + private String resolveError; + + private Values(Value[] values) { + this.values = new Value[values.length]; + System.arraycopy(values, 0, this.values, 0, values.length); + } + + public int length() { + return values.length; + } + + public Option.Value[] asArray() { + Option.Value[] result = new Option.Value[values.length]; + System.arraycopy(values, 0, result, 0, result.length); + return result; + } + + /** + * Emit as String[] the non-null values. + * @return String[] of matched entries (never null, elements not null) + */ + public String[] render() { + return Values.render(valuesNotNull()); + } + + public String toString() { + return Arrays.asList(values).toString(); + } + + /** + * Create index into values of those that were matched, + * including the options and their arguments. + * @return int[] of elements in values that are not null (options) + * or that represent option arguments + */ + public int[] indexMatches() { + // must be in order, low to high + final int[] missed = indexMissedMatches(); + return invert(missed, length()); + } + + /** + * Create index into values of missed input, + * taking into account that matched arguments are + * represented as null. + * @return int[] of elements in values that are null + * or optionally represent option arguments + */ + public int[] indexMissedMatches() { + MissedSelector selector = new MissedSelector(); + find(selector, FIND_ALL); + String errors = selector.getErrors(); + if (null != errors) { + throw new Error(errors); + } + return selector.getResult(); + } + + public Value firstInFamily(Option.Family family) { + return findFirst(new ValueSelector(family)); + } + + public Value[] allInFamily(Option.Family family) { + return find(new ValueSelector(family), FIND_ALL); + } + + public Value firstOption(Option option) { + return findFirst(new ValueSelector(option)); + } + + public Value[] allOption(Option option) { + return find(new ValueSelector(option), FIND_ALL); + } + + public Value firstValue(Option option, String value) { + LangUtil.throwIaxIfNull(value, "value"); + return findFirst(new ValueSelector(option, value)); + } + + public Value[] allValues(Option option, String value) { + LangUtil.throwIaxIfNull(value, "value"); + return find(new ValueSelector(option, value), FIND_ALL); + } + + public boolean isResolved() { + return ((this != EMPTY) && (null != resolveError)); + } + + /** + * + * @param selector the Selector to pick out entries to nullify + * (should throw STOP to halt processing) + * @return Values resulting from nullifying entries, + * or this if none were changed + */ + public Values nullify(Selector selector) { + if (null == selector) { + return this; + } + Value[] temp = asArray(); + int changed = nullify(temp, selector); + if (0 == changed) { + return this; + } + return new Values(temp); + } + + /** + * Resolve options, removing duplicates by force if necessary. + * If any error is returned, then the values are left unchanged. + * @return String error, if any + * @throws IllegalStateException if isResolved() + */ + public String resolve() { + if (isResolved()) { + throw new IllegalStateException("already resolved"); + } + Option.Value[] temp = asArray(); + resolveError = resolve(temp); + if (null == resolveError) { + System.arraycopy(temp, 0, values, 0, temp.length); + valuesNotNull = null; + resolveError = NO_ERROR; + return null; + } + return resolveError; + } + + protected Option.Value findFirst(Selector filter) { + Option.Value[] result = find(filter, !FIND_ALL); + return (0 == result.length ? null : result[0]); + } + + protected Option.Value[] find(Selector filter, boolean findAll) { + LangUtil.throwIaxIfNull(filter, "filter"); + ArrayList result = new ArrayList(); + for (int i = 0; i < values.length; i++) { + final boolean accepted; + try { + accepted = filter.accept(values[i]); + } catch (Error e) { + if (Selector.STOP != e) { + throw e; + } + break; + } + if (accepted) { + result.add(values[i]); + if (findAll != FIND_ALL) { + break; + } + } + } + return toArray(result); + } + + private Option.Value[] valuesNotNull() { + if (null == valuesNotNull) { + ArrayList list = new ArrayList(); + for (int i = 0; i < this.values.length; i++) { + if (null != this.values[i]) { + list.add(this.values[i]); + } + } + valuesNotNull = toArray(list); + } + return valuesNotNull; + } + + public static class Selector { + public static final Error STOP = new Error("stop invoking Selector"); + protected Selector() { + } + protected boolean accept(Value value) { + return false; + } + } + protected static class ValueSelector extends Selector { + + private final Option option; + private final Option.Family family; + private final String value; + ValueSelector(Option.Family family) { + LangUtil.throwIaxIfNull(family, "family"); + this.family = family; + option = null; + value = null; + } + ValueSelector(Option option) { + this(option, (String) null); + } + ValueSelector(Option option, String value) { + LangUtil.throwIaxIfNull(option, "option"); + this.option = option; + family = null; + this.value = value; + } + protected boolean accept(Value value) { + if (null == value) { + return false; + } + if (null != family) { + return family.sameFamily(value.option.getFamily()); + } else if (!option.sameOptionIdentifier(value.option)) { + return false; + } else { + return ((null == this.value) + || (this.value.equals(value.value))); + } + } + } + + /** pick all null entries (except for args), return as int[] */ + protected static class MissedSelector extends Selector { + public static final String DELIM = "; "; + final IntList result = new IntList(); + int index; + final StringBuffer errors = new StringBuffer(); + int argsExpected; + Option argsExpectedFor; + MissedSelector() { + } + + int[] getResult() { + return result.getList(); + } + + /** + * add index if value is null + * unless skipArguments + */ + protected boolean accept(Value value) { + index++; + if (null != value) { + if (0 < argsExpected) { // expected more (null) args + missedArgsFor(argsExpectedFor, argsExpected); + } + argsExpected = value.option.numArguments(); + argsExpectedFor = value.option; + } else if (0 < argsExpected) { // ignore null in arg position + argsExpected--; + if (0 == argsExpected) { + argsExpectedFor = null; + } + } else { // null, not expecting arg, so missing + result.add(index - 1); + return true; + } + return false; + } + + private void missedArgsFor(Option option, int numArgs) { + errors.append("missed "); + errors.append(numArgs + " args for "); + errors.append(option + DELIM); + } + + String getErrors() { + if (0 < argsExpected) { + } + if (0 == errors.length()) { + return null; + } + return errors.toString(); + } + } + + static class IntList { + // not synchronized - used only in one thread + static String render(int[] input) { + if (null == input) { + return "null"; + } + StringBuffer sb = new StringBuffer(); + sb.append("["); + for (int i = 0; i < input.length; i++) { + if (i > 0) { + sb.append(", " + input[i]); + } else { + sb.append("" + input[i]); + } + } + sb.append("]"); + return sb.toString(); + } + + private int[] input = new int[256]; + private int insert; + private void add(int i) { + if (insert >= input.length) { + int[] temp = new int[insert + 256]; + for (int j = 0; j < input.length; j++) { + temp[j] = input[j]; + } + input = temp; + } + input[insert++] = i; + } + + private int[] getList() { + int[] result = new int[insert]; + for (int i = 0; i < result.length; i++) { + result[i] = input[i]; + } + return result; + } + } +} diff --git a/testing/testsrc/TestingModuleTests.java b/testing/testsrc/TestingModuleTests.java index 452d2e0b1..4c904ac90 100644 --- a/testing/testsrc/TestingModuleTests.java +++ b/testing/testsrc/TestingModuleTests.java @@ -25,6 +25,7 @@ public class TestingModuleTests extends TestCase { suite.addTest(org.aspectj.testing.harness.bridge.TestingBridgeTests.suite()); suite.addTest(org.aspectj.testing.taskdefs.TaskdefTests.suite()); suite.addTest(org.aspectj.testing.util.UtilTests.suite()); + suite.addTest(org.aspectj.testing.util.options.OptionsTests.suite()); suite.addTest(org.aspectj.testing.xml.TestingXmlTests.suite()); return suite; } diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java b/testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java index a876db105..8d05160b4 100644 --- a/testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java @@ -1,6 +1,6 @@ /* ******************************************************************* - * Copyright (c) 1999-2001 Xerox Corporation, - * 2002 Palo Alto Research Center, Incorporated (PARC). + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC), + * 2003 Contributors. * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Common Public License v1.0 @@ -9,14 +9,22 @@ * * Contributors: * Xerox/PARC initial implementation + * Wes Isberg 2003 modifications * ******************************************************************/ package org.aspectj.testing.harness.bridge; +import java.util.*; +import java.util.Arrays; + import junit.framework.TestCase; import org.aspectj.bridge.MessageHandler; import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.harness.bridge.CompilerRun.Spec.CRSOptions; +import org.aspectj.testing.util.options.*; +import org.aspectj.testing.util.options.Options; +import org.aspectj.util.LangUtil; /** * @@ -24,147 +32,515 @@ import org.aspectj.bridge.MessageUtil; public class CompilerRunSpecTest extends TestCase { private static boolean PRINTING = true; - - /** - * Constructor for CompilerRunSpecTest. - * @param name - */ - public CompilerRunSpecTest(String name) { - super(name); - } + private static final boolean SETUP_JAVA13 = + haveProperty(Globals.J2SE13_RTJAR_NAME); + private static final boolean SETUP_JAVA14 = + haveProperty(Globals.J2SE14_RTJAR_NAME); + static { + if (!SETUP_JAVA14) { + System.err.println( + "warning: set -D" + + Globals.J2SE14_RTJAR_NAME + + "=... in order to run all tests"); + } + if (!SETUP_JAVA13) { + System.err.println( + "warning: set -D" + + Globals.J2SE13_RTJAR_NAME + + "=... in order to run all tests"); + } + } + private static boolean haveProperty(String key) { + try { + return (null != System.getProperty(key)); + } catch (Throwable t) { + // ignore + } + return false; + } + + /** + * Constructor for CompilerRunSpecTest. + * @param name + */ + public CompilerRunSpecTest(String name) { + super(name); + } public void testSetupArgs() { - checkSetupArgs("verbose", false); - // XXX skipping since eclipse is default - // checkSetupArgs("lenient", false); - // checkSetupArgs("strict", false); - // checkSetupArgs("ajc", true); // XXX need to predict/test compiler selection - // eclipse-only checkSetupArgs("eclipse", true); + checkSetupArgs("verbose", false); + } + + public void testCompliance() { + // 1.3 should work + String specOptions = "-1.3"; + String[] globalOptions = new String[0]; + boolean expectAdopted = true; + String resultContains = "1.3"; + String messagesContain = null; + MessageHandler handler = + runTest( + specOptions, + globalOptions, + expectAdopted, + resultContains, + messagesContain); + checkMessages(handler, null); + + // 1.3 should work with collision? + globalOptions = new String[] { "-1.3" }; + resultContains = "1.3"; + handler = + runTest( + specOptions, + globalOptions, + expectAdopted, + resultContains, + messagesContain); + checkMessages(handler, null); + + // 1.4 should work + globalOptions = new String[0]; + specOptions = "-1.4"; + resultContains = "1.4"; + handler = + runTest( + specOptions, + globalOptions, + expectAdopted, + resultContains, + messagesContain); + checkMessages(handler, null); + + // compliance not checked for valid numbers, so -1.2 would pass + } + + private void checkMessages(MessageHandler handler, String contains) { + if (null == contains) { + if (0 != handler.numMessages(null, true)) { + assertTrue("" + handler, false); + } + } else { + String messages = "" + handler; + if (-1 == messages.indexOf(contains)) { + assertTrue(messages, false); + } + } + } + + /** @testcase check -target converts for 1.1 and 1.2, not others */ + public void testTarget() { + final boolean PASS = true; + final boolean FAIL = false; + checkSourceTargetVersionConversion("target", 1, PASS, null); + checkSourceTargetVersionConversion("target", 2, PASS, null); + checkSourceTargetVersionConversion( + "target", + 3, + FAIL, + "illegal input"); + } + + /** @testcase check -source converts for 1.3 and 1.4, not others */ + public void testSource() { + final boolean PASS = true; + final boolean FAIL = false; + if (SETUP_JAVA13) { + checkSourceTargetVersionConversion("source", 3, PASS, null); + } + if (SETUP_JAVA14) { + checkSourceTargetVersionConversion("source", 4, PASS, null); + } + + checkSourceTargetVersionConversion( + "source", + 2, + FAIL, + "not permitted"); + checkSourceTargetVersionConversion( + "source", + 6, + FAIL, + "not permitted"); + } + + public void testSourceOverride() { + if (SETUP_JAVA13 && SETUP_JAVA14) { + checkSourceTargetOverride("source", 3, 4); + checkSourceTargetOverride("source", 4, 3); + checkSourceTargetOverride("source", 3, 3); + checkSourceTargetOverride("source", 4, 4); + } + } + + public void testTargetOverride() { + checkSourceTargetOverride("target", 1, 2); + checkSourceTargetOverride("target", 2, 1); + checkSourceTargetOverride("target", 1, 1); + checkSourceTargetOverride("target", 2, 2); } - void checkSetupArgs(String arg, boolean isTestArg) { + public void testCompilerOptions() { + checkCompilerOption(null, CompilerRun.Spec.DEFAULT_COMPILER); + CRSOptions crsOptions = CompilerRun.Spec.testAccessToCRSOptions(); + Set options = crsOptions.compilerOptions(); + assertTrue(null != options); + StringBuffer notLoaded = new StringBuffer(); + for (Iterator iter = options.iterator(); iter.hasNext();) { + Option compilerOption = (Option) iter.next(); + if (!(crsOptions.compilerIsLoadable(compilerOption))) { + notLoaded.append(" " + compilerOption); + } else { + String className = crsOptions.compilerClassName(compilerOption); + String argValue = compilerOption.toString(); // XXX snoop + String arg = Option.ON.render(argValue); + checkCompilerOption(arg, className); + } + } + if (0 < notLoaded.length()) { + System.err.println( + getClass().getName() + + ".testCompilerOptions()" + + " warning: compiler options not tested because not loadable: " + + notLoaded); + } + } + + void checkCompilerOption(String arg, String className) { + MessageHandler handler = new MessageHandler(); + try { + CompilerRun.Spec spec = null; + try { + spec = new CompilerRun.Spec(); + } catch (Throwable t) { + t.printStackTrace(System.err); + } + assertTrue(spec != null); + AbstractRunSpec.RT parentRuntime = new AbstractRunSpec.RT(); + String result; + String expResult; + + if (null != arg) { + parentRuntime.setOptions(new String[] { arg }); + } + if (!spec.adoptParentValues(parentRuntime, handler)) { + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } else { + assertTrue("adopt failed, but no messages", false); + } + } + result = "" + spec.testSetup.commandOptions; + assertEquals("[]", result); + assertEquals(className, spec.testSetup.compilerName); + } finally { + } + } + + public void testSpecOptions() { + Options options = CompilerRun.Spec.testAccessToOptions(); + OptionChecker optionChecker = new OptionChecker(options); + // known failures: extdirs, aspectpath, Xlintfile + // progress, time, noExit, repeat , + // help, + String[][] input = + new String[][] { + new String[] { "-verbose" }, + new String[] { "-incremental" }, + new String[] { "-emacssym" }, + new String[] { "-Xlint" }, + new String[] { "-Xlint:error" }, + new String[] { "-1.3" }, + new String[] { "-1.4" }, + new String[] { "-source", "1.3" }, + new String[] { "-source", "1.4" }, + new String[] { "-target", "1.1" }, + new String[] { "-target", "1.2" }, + new String[] { "-preserveAllLocals" }, + new String[] { "-referenceInfo" }, + new String[] { "-deprecation" }, + new String[] { "-noImportError" }, + new String[] { "-proceedOnError" } + }; + String[][] literalInput = + new String[][] { + new String[] { "-nowarn" }, + new String[] { "-warn:deprecated" }, + new String[] { "-warn:deprecated,unverified" }, + new String[] { "-warn:deprecated", "-warn:unusedLocals" }, + new String[] { "-g" }, + new String[] { "-g", "-g:none" }, + new String[] { "-g:vars,source" }, + new String[] { "-verbose", "-g:vars,source" }, + }; + // normal + for (int i = 0; i < input.length; i++) { + optionChecker.checkOptions(input[i], input[i]); + } + for (int i = 0; i < literalInput.length; i++) { + optionChecker.checkOptions(literalInput[i], literalInput[i]); + } + // force-on + String[][] forceInput = duplicate(input, "!"); + for (int i = 0; i < input.length; i++) { + optionChecker.checkOptions(forceInput[i], input[i]); + } + // force-off + forceInput = duplicate(input, "^"); + String[] none = new String[0]; + for (int i = 0; i < input.length; i++) { + optionChecker.checkOptions(forceInput[i], none); + } + } + + String[][] duplicate(String[][] input, String prefix) { + String[][] output = new String[input.length][]; + final int prefixLength = (null == prefix ? 0 : prefix.length()); + for (int i = 0; i < output.length; i++) { + int length = input[i].length; + output[i] = new String[length]; + if ((length > 0) && (prefixLength > 0)) { + System.arraycopy(input[i], 0, output[i], 0, length); + output[i][0] = + prefix + output[i][0].substring(prefixLength); + } + } + return output; + } + + public void checkSourceTargetOverride(String name, int from, int to) { + final String specOptions = "-" + name + ", 1." + from; + String[] globalOptions = new String[] { "!" + name, "1." + to }; + boolean expectAdopted = true; + String resultContains = "[-" + name + ", 1." + to; + String messagesContain = null; + MessageHandler handler = + runTest( + specOptions, + globalOptions, + expectAdopted, + resultContains, + messagesContain); + checkMessages(handler, null); + } + + void checkSourceTargetVersionConversion( + String name, + int i, + boolean expectPass, + String expectedErr) { + final String specOptions = "-" + name + ", 1." + i; + String[] globalOptions = new String[0]; + boolean expectAdopted = expectPass; + String resultContains = + !expectPass ? null : "[-" + name + ", 1." + i; + String messagesContain = expectedErr; + MessageHandler handler = + runTest( + specOptions, + globalOptions, + expectAdopted, + resultContains, + messagesContain); + } + + MessageHandler runTest( + String specOptions, + String[] globalOptions, + boolean expectAdopted, + String resultContains, + String messagesContain) { MessageHandler handler = new MessageHandler(); try { CompilerRun.Spec spec = new CompilerRun.Spec(); AbstractRunSpec.RT parentRuntime = new AbstractRunSpec.RT(); + + if (!LangUtil.isEmpty(specOptions)) { + spec.setOptions(specOptions); + } + if (!LangUtil.isEmpty(globalOptions)) { + parentRuntime.setOptions(globalOptions); + } + boolean adopted = + spec.adoptParentValues(parentRuntime, handler); + if (adopted != expectAdopted) { + String s = + (expectAdopted ? "not " : "") + + "adopted spec=" + + specOptions + + " globals=" + + (LangUtil.isEmpty(globalOptions) + ? "[]" + : Arrays.asList(globalOptions).toString()) + + " -- " + + handler; + assertTrue(s, false); + } + if (null != resultContains) { + String result = "" + spec.testSetup.commandOptions; + if (-1 == result.indexOf(resultContains)) { + assertTrue( + "expected " + resultContains + " got " + result, + false); + } + } + if (null != messagesContain) { + boolean haveMessages = + (0 != handler.numMessages(null, true)); + if (!haveMessages) { + assertTrue("expected " + messagesContain, false); + } else { + String messages = handler.toString(); + if (-1 == messages.indexOf(messagesContain)) { + assertTrue( + "expected " + + messagesContain + + " got " + + messages, + false); + } + } + } + return handler; + } finally { + } + } + + void checkSetupArgs(String arg, final boolean isTestArg) { + MessageHandler handler = new MessageHandler(); + try { + CompilerRun.Spec spec = null; + try { + spec = new CompilerRun.Spec(); + } catch (Throwable t) { + t.printStackTrace(System.err); + } + assertTrue(spec != null); + AbstractRunSpec.RT parentRuntime = new AbstractRunSpec.RT(); String result; String expResult; - + // -------- local set + // global - (set) does not change local-set + parentRuntime.setOptions(new String[] { "-" + arg }); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = "" + spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + // global ^ (force-off) to disable spec.setOptions("-" + arg); - parentRuntime.setOptions(new String[] {"^" + arg}); + parentRuntime.setOptions(new String[] { "^" + arg }); assertTrue(spec.adoptParentValues(parentRuntime, handler)); if (0 != handler.numMessages(null, true)) { assertTrue(handler.toString(), false); } - result = ""+spec.testSetup.commandOptions; + result = "" + spec.testSetup.commandOptions; assertTrue(result, "[]".equals(result)); - + // global ! (force-on) does not change local-set - parentRuntime.setOptions(new String[] {"!" + arg}); + parentRuntime.setOptions(new String[] { "!" + arg }); assertTrue(spec.adoptParentValues(parentRuntime, handler)); if (0 != handler.numMessages(null, true)) { assertTrue(handler.toString(), false); } - result = ""+spec.testSetup.commandOptions; + result = "" + spec.testSetup.commandOptions; expResult = (isTestArg ? "[]" : "[-" + arg + "]"); - assertTrue(result, expResult.equals(result)); - - // global - (set) does not change local-set - parentRuntime.setOptions(new String[] {"-" + arg}); - assertTrue(spec.adoptParentValues(parentRuntime, handler)); - if (0 != handler.numMessages(null, true)) { - assertTrue(handler.toString(), false); + if (!expResult.equals(result)) { + assertTrue( + "expected " + expResult + " got " + result, + false); } - result = ""+spec.testSetup.commandOptions; - expResult = (isTestArg ? "[]" : "[-" + arg + "]"); - assertTrue(result, expResult.equals(result)); - + // global (unset) does not change local-set - parentRuntime.setOptions(new String[] {""}); + parentRuntime.setOptions(new String[] { "" }); assertTrue(spec.adoptParentValues(parentRuntime, handler)); if (0 != handler.numMessages(null, true)) { assertTrue(handler.toString(), false); } - result = ""+spec.testSetup.commandOptions; + result = "" + spec.testSetup.commandOptions; expResult = (isTestArg ? "[]" : "[-" + arg + "]"); assertTrue(result, expResult.equals(result)); - + // -------- local force-on // global ^ (force-off) conflicts with local force-on spec.setOptions("!" + arg); - parentRuntime.setOptions(new String[] {"^" + arg}); + parentRuntime.setOptions(new String[] { "^" + arg }); assertTrue(!spec.adoptParentValues(parentRuntime, handler)); assertTrue(0 != handler.numMessages(null, true)); handler.init(); - + // global ! (force-on) does not change local force-on - parentRuntime.setOptions(new String[] {"!" + arg}); + parentRuntime.setOptions(new String[] { "!" + arg }); assertTrue(spec.adoptParentValues(parentRuntime, handler)); if (0 != handler.numMessages(null, true)) { assertTrue(handler.toString(), false); } - result = ""+spec.testSetup.commandOptions; + result = "" + spec.testSetup.commandOptions; expResult = (isTestArg ? "[]" : "[-" + arg + "]"); assertTrue(result, expResult.equals(result)); - + // global - (set) does not change local force-on - parentRuntime.setOptions(new String[] {"-" + arg}); + parentRuntime.setOptions(new String[] { "-" + arg }); assertTrue(spec.adoptParentValues(parentRuntime, handler)); if (0 != handler.numMessages(null, true)) { assertTrue(handler.toString(), false); } - result = ""+spec.testSetup.commandOptions; + result = "" + spec.testSetup.commandOptions; expResult = (isTestArg ? "[]" : "[-" + arg + "]"); assertTrue(result, expResult.equals(result)); - + // global (unset) does not change local force-on - parentRuntime.setOptions(new String[] {""}); + parentRuntime.setOptions(new String[] { "" }); assertTrue(spec.adoptParentValues(parentRuntime, handler)); if (0 != handler.numMessages(null, true)) { assertTrue(handler.toString(), false); } - result = ""+spec.testSetup.commandOptions; + result = "" + spec.testSetup.commandOptions; expResult = (isTestArg ? "[]" : "[-" + arg + "]"); assertTrue(result, expResult.equals(result)); - - + // -------- local force-off // global ^ (force-off) does not change local force-off spec.setOptions("^" + arg); - parentRuntime.setOptions(new String[] {"^" + arg}); + parentRuntime.setOptions(new String[] { "^" + arg }); assertTrue(spec.adoptParentValues(parentRuntime, handler)); if (0 != handler.numMessages(null, true)) { assertTrue(handler.toString(), false); } - result = ""+spec.testSetup.commandOptions; + result = "" + spec.testSetup.commandOptions; assertTrue(result, ("[]").equals(result)); - + // global ! (force-on) conflicts with local force-off - parentRuntime.setOptions(new String[] {"!" + arg}); + parentRuntime.setOptions(new String[] { "!" + arg }); assertTrue(!spec.adoptParentValues(parentRuntime, handler)); assertTrue(0 != handler.numMessages(null, true)); handler.init(); - - // global - (set) overridden by local force-off // XXX?? - parentRuntime.setOptions(new String[] {"-" + arg}); + + // global - (set) overridden by local force-off + parentRuntime.setOptions(new String[] { "-" + arg }); assertTrue(spec.adoptParentValues(parentRuntime, handler)); if (0 != handler.numMessages(null, true)) { assertTrue(handler.toString(), false); } - result = ""+spec.testSetup.commandOptions; + result = "" + spec.testSetup.commandOptions; assertTrue(result, ("[]").equals(result)); - + // global (unset) does not change local force-off - parentRuntime.setOptions(new String[] {""}); + parentRuntime.setOptions(new String[] { "" }); assertTrue(spec.adoptParentValues(parentRuntime, handler)); if (0 != handler.numMessages(null, true)) { assertTrue(handler.toString(), false); } - result = ""+spec.testSetup.commandOptions; + result = "" + spec.testSetup.commandOptions; assertTrue(result, ("[]").equals(result)); + + // undefined whether global set overrides local set + // for different sibling options } finally { if (PRINTING && (0 < handler.numMessages(null, true))) { MessageUtil.print(System.err, handler, "checkSetupArgs: "); diff --git a/testing/testsrc/org/aspectj/testing/util/options/OptionChecker.java b/testing/testsrc/org/aspectj/testing/util/options/OptionChecker.java new file mode 100644 index 000000000..f2ae8acad --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/options/OptionChecker.java @@ -0,0 +1,195 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Wes Isberg initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util.options; + +import java.util.Arrays; + +import junit.framework.Assert; + +import org.aspectj.testing.util.LangUtil; + +/** + * Checks that throw AssertionFailedError on failure. + * Subclasses reimplement assertionFailed(String) + * to handle failures differently. + */ +public class OptionChecker { + private final Options options; + + public OptionChecker(Options options) { + this.options = options; + LangUtil.throwIaxIfNull(options, "options"); + } + + /** + * Subclasses override this to throw different exceptions + * on assertion failures. + * This implementation delegates to + * Assert.assertTrue(label, false). + * @param label the String message for the assertion + * + */ + public void assertionFailed(String label) { + Assert.assertTrue(label, false); + } + + public void checkAssertion(String label, boolean test) { + if (!test) { + assertionFailed(label); + } + } + + public void checkOptions(String[] input, String[] expected) { + checkOptions(input, expected, true); + } + + public void checkOptions( + String[] input, + String[] expected, + boolean resolve) { + Values values = getValues(input); + if (resolve) { + String err = values.resolve(); + checkAssertion("error: \"" + err + "\"", null == err); + } + String[] actual = values.render(); // Value.render(values); + checkEqual(expected, actual); + } + + public void checkOptionsNegative( + String[] input, + String expectedMissedMatchErr, + String expectedResolveErr) { + checkOptionsNegative( + input, + null, + expectedMissedMatchErr, + expectedResolveErr); + } + + public void checkOptionsNegative( + String[] input, + String expectedInValuesException, + String expectedMissedMatchErr, + String expectedResolveErr) { + Values values = + getValuesNegative(input, expectedInValuesException); + if (null == expectedInValuesException) { + String err = Options.missedMatchError(input, values); + checkContains(expectedMissedMatchErr, err); + err = values.resolve(); + checkContains(expectedResolveErr, err); + } + } + + private Values getValuesNegative( + String[] input, + String expectedInExceptionMessage, + Options options) { + try { + return options.acceptInput(input); + } catch (Option.InvalidInputException e) { + String m = e.getFullMessage(); + boolean ok = + (null != expectedInExceptionMessage) + && (-1 != m.indexOf(expectedInExceptionMessage)); + if (!ok) { + e.printStackTrace(System.err); + if (null != expectedInExceptionMessage) { + m = + "expected \"" + + expectedInExceptionMessage + + "\" in " + + m; + } + assertionFailed(m); + } + return null; // actually never executed + } + } + + private Values getValuesNegative( + String[] input, + String expectedInExceptionMessage) { + return getValuesNegative( + input, + expectedInExceptionMessage, + options); + } + + private Values getValues(String[] input, Options options) { + return getValuesNegative(input, null, options); + } + + private Values getValues(String[] input) { + return getValuesNegative(input, null); + } + + private void checkContains(String expected, String expectedIn) { + if (null == expected) { + if (null != expectedIn) { + assertionFailed("did not expect \"" + expectedIn + "\""); + } + } else { + if ((null == expectedIn) + || (-1 == expectedIn.indexOf(expected))) { + assertionFailed( + "expected \"" + + expected + + "\" in \"" + + expectedIn + + "\""); + } + } + } + + private String safeString(String[] ra) { + return (null == ra ? "null" : Arrays.asList(ra).toString()); + } + + private void checkEqual(String[] expected, String[] actual) { + if (!isEqual(expected, actual)) { + assertionFailed( + "expected \"" + + safeString(expected) + + "\" got \"" + + safeString(actual) + + "\""); + } + } + + private boolean isEqual(String[] expected, String[] actual) { + if (null == expected) { + return (null == actual ? true : false); + } else if (null == actual) { + return false; + } else if (expected.length != actual.length) { + return false; + } + for (int i = 0; i < actual.length; i++) { + String e = expected[i]; + String a = actual[i]; + if (null == e) { + if (null != a) { + return false; + } + } else if (null == a) { + return false; + } else if (!(e.equals(a))) { + return false; + } + } + return true; + } + +} diff --git a/testing/testsrc/org/aspectj/testing/util/options/OptionsTest.java b/testing/testsrc/org/aspectj/testing/util/options/OptionsTest.java new file mode 100644 index 000000000..29048aa07 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/options/OptionsTest.java @@ -0,0 +1,486 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Wes Isberg initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util.options; + +import junit.framework.*; + +import org.aspectj.testing.util.options.Option.InvalidInputException; + +/** + */ +public class OptionsTest extends TestCase { + + private static Options OPTIONS; + + private static Options getOptions() { + if (null == OPTIONS) { + OPTIONS = new Options(true); + Option.Factory factory = new Option.Factory("OptionsTest"); + OPTIONS.addOption(factory.create("verbose")); + OPTIONS.addOption(factory.create("quiet")); + OPTIONS.addOption(factory.create("debug")); + OPTIONS.addOption( + factory.create("ajc", "compiler", Option.FORCE_PREFIXES, false)); + OPTIONS.addOption( + factory.create( + "eclipse", + "compiler", + Option.FORCE_PREFIXES, + false)); + OPTIONS.addOption( + factory.create( + "ajdeCompiler", + "compiler", + Option.FORCE_PREFIXES, + false)); + OPTIONS.addOption( + factory.create("1.3", "compliance", Option.FORCE_PREFIXES, false)); + OPTIONS.addOption( + factory.create("1.4", "compliance", Option.FORCE_PREFIXES, false)); + + // treating multi-arg as single - extrinsic flatten/unflatten + OPTIONS.addOption( + factory.create("target11", "target", Option.FORCE_PREFIXES, false)); + OPTIONS.addOption( + factory.create("target12", "target", Option.FORCE_PREFIXES, false)); + OPTIONS.addOption( + factory.create("source13", "source", Option.FORCE_PREFIXES, false)); + OPTIONS.addOption( + factory.create("source14", "source", Option.FORCE_PREFIXES, false)); + + // suffix options (a) -warn:... (b) -g, -g:... + Assert.assertTrue(factory.setupFamily("warning", true)); + OPTIONS.addOption( + factory.create("warn", "warning", Option.STANDARD_PREFIXES, true)); + + Assert.assertTrue(factory.setupFamily("debugSymbols", true)); + OPTIONS.addOption( + factory.create( + "g", + "debugSymbols", + Option.STANDARD_PREFIXES, + false)); + OPTIONS.addOption( + factory.create( + "g:", + "debugSymbols", + Option.STANDARD_PREFIXES, + true)); + + // treating multi-arg as single - intrinsic flatten/unflatten + OPTIONS + .addOption( + factory + .create( + "target", + "target", + Option.FORCE_PREFIXES, + false, + new String[][] { + new String[] { "1.1", "1.2" } + })); + OPTIONS + .addOption( + factory + .create( + "source", + "source", + Option.FORCE_PREFIXES, + false, + new String[][] { + new String[] { "1.3", "1.4" } + })); + OPTIONS.freeze(); + } + return OPTIONS; + } + + private boolean verbose; + private OptionChecker localOptionChecker; + + public void testDebugCase() { + OptionChecker optionChecker = getOptionChecker(); + String[] input = new String[] { + }; + String[] expected = new String[0]; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-target", "1.1", "^target", "1.2" }; + expected = new String[] { "-target", "1.1" }; + + optionChecker.checkOptions(input, expected); + } + public void testNegDebugCase() { + OptionChecker optionChecker = getOptionChecker(); + String[] input = new String[] { "-target" }; + String expectedInValuesException = "not enough arguments"; + String expectedMissedMatchErr = null; + String expectedResolveErr = null; + + optionChecker.checkOptionsNegative( + input, + expectedInValuesException, + expectedMissedMatchErr, + expectedResolveErr); + } + + public void testOptionsPositive() { + OptionChecker optionChecker = getOptionChecker(); + String[] input = new String[] { + }; + String[] expected = new String[0]; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-verbose" }; + expected = new String[] { "-verbose" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!verbose" }; + expected = new String[] { "-verbose" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!verbose", "-verbose" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-verbose", "!verbose" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "^verbose" }; + expected = new String[] { + }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "^verbose", "-verbose" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-verbose", "^verbose" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-verbose", "-debug" }; + expected = new String[] { "-verbose", "-debug" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!verbose", "!debug" }; + expected = new String[] { "-verbose", "-debug" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-verbose", "^verbose", "!debug" }; + expected = new String[] { "-debug" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "^verbose", "-verbose", "!debug" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!debug", "^verbose", "-verbose" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-ajc" }; + expected = new String[] { "-ajc" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-eclipse" }; + expected = new String[] { "-eclipse" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-eclipse", "-ajc", "!ajc" }; + expected = new String[] { "-ajc" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-eclipse", "!ajc" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-ajdeCompiler", "^ajc" }; + expected = new String[] { "-ajdeCompiler" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!ajdeCompiler", "^ajc" }; + optionChecker.checkOptions(input, expected); + + input = + new String[] { + "-verbose", + "^verbose", + "!quiet", + "-quiet", + "-debug", + "^debug" }; + expected = new String[] { "-quiet" }; + optionChecker.checkOptions(input, expected); + + input = + new String[] { + "-verbose", + "^debug", + "!quiet", + "!quiet", + "-debug", + "^verbose" }; + expected = new String[] { "-quiet" }; + optionChecker.checkOptions(input, expected); + } + + public void testOptionsNegative() { + OptionChecker optionChecker = getOptionChecker(); + String[] input = new String[] { "-unknown" }; + String expectedMissedMatchErr = "-unknown"; + String expectedResolveErr = null; + optionChecker.checkOptionsNegative( + input, + expectedMissedMatchErr, + expectedResolveErr); + + input = new String[] { "!verbose", "^verbose" }; + expectedMissedMatchErr = null; + expectedResolveErr = "conflict"; + optionChecker.checkOptionsNegative( + input, + expectedMissedMatchErr, + expectedResolveErr); + + input = new String[] { "!ajc", "!eclipse" }; + optionChecker.checkOptionsNegative( + input, + expectedMissedMatchErr, + expectedResolveErr); + + input = new String[] { "-ajc", "-eclipse" }; + expectedResolveErr = "collision"; + optionChecker.checkOptionsNegative( + input, + expectedMissedMatchErr, + expectedResolveErr); + + input = new String[] { "-verbose", "-verbose" }; + expectedResolveErr = null; // duplicates redundant, not colliding + optionChecker.checkOptionsNegative( + input, + expectedMissedMatchErr, + expectedResolveErr); + } + + public void testMissedMatches() throws InvalidInputException { + checkMissedMatches(new int[0], Values.EMPTY); + checkMissedMatches(new int[] { 0 }, + Values.wrapValues(new Option.Value[1])); // null in [0] + checkMissedMatches( + new int[] { 0, 1, 2 }, + Values.wrapValues(new Option.Value[] { null, null, null })); + + Option.Factory factory = new Option.Factory("testMissedMatches"); + Option single = factory.create("verbose"); + Option multiple = + factory + .create( + "source", + "source", + Option.STANDARD_PREFIXES, + false, + new String[][] { new String[] { "1.3", "1.4" } + }); + + Options options = new Options(false); + options.addOption(single); + options.addOption(multiple); + options.freeze(); + int[] expectNone = new int[0]; + String[] input = new String[] { "-verbose" }; + Values result = options.acceptInput(input); + checkMissedMatches(expectNone, result); + + input = new String[] { "-verbose", "-verbose" }; + result = options.acceptInput(input); + checkMissedMatches(expectNone, result); + + input = new String[] { "-source", "1.3" }; + result = options.acceptInput(input); + checkMissedMatches(expectNone, result); + + input = new String[] { "-source", "1.4" }; + result = options.acceptInput(input); + checkMissedMatches(expectNone, result); + + input = new String[] { "-verbose", "-missed" }; + result = options.acceptInput(input); + checkMissedMatches(new int[] { 1 }, result); + + input = new String[] { "-source", "1.4", "-missed" }; + result = options.acceptInput(input); + checkMissedMatches(new int[] { 2 }, result); + + input = new String[] { "-source", "1.4", "-missed", "-verbose" }; + result = options.acceptInput(input); + checkMissedMatches(new int[] { 2 }, result); + + } + + void checkMissedMatches(int[] expected, Values actual) { + int[] result = actual.indexMissedMatches(); + boolean failed = (result.length != expected.length); + + for (int i = 0; !failed && (i < result.length); i++) { + failed = (result[i] != expected[i]); + } + if (failed) { + assertTrue( + "expected " + + Values.IntList.render(expected) + + " got " + + Values.IntList.render(result) + + " for " + + actual, + false); + } + } + + public void testComplexOptionsPositive() { + OptionChecker optionChecker = getOptionChecker(); + String[] input = new String[] { + }; + String[] expected = new String[0]; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-target11" }; + expected = new String[] { "-target11" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!target12" }; + expected = new String[] { "-target12" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!target12", "-target11" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!target12", "^target11" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "^target12", "^target11" }; + expected = new String[] { + }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!1.3" }; + expected = new String[] { "-1.3" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!1.3", "-1.4" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!1.3", "^1.4" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "^1.3", "^1.4" }; + expected = new String[] { + }; + optionChecker.checkOptions(input, expected); + + } + + public void testMultiArgOptionsPositive() { + OptionChecker optionChecker = getOptionChecker(); + String[] input = new String[] { + }; + String[] expected = new String[0]; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-target", "1.1" }; + expected = new String[] { "-target", "1.1" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!target", "1.1" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!target", "1.1", "-target", "1.2" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-target", "1.1", "^target", "1.2" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-source", "1.3" }; + expected = new String[] { "-source", "1.3" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "!source", "1.3", "-source", "1.4" }; + optionChecker.checkOptions(input, expected); + + input = new String[] { "-source", "1.3", "^source", "1.4" }; + input = + new String[] { + "-source", + "1.3", + "^source", + "1.4", + "!source", + "1.3", + "-source", + "1.4" }; + optionChecker.checkOptions(input, expected); + } + + public void testMultiArgOptionsNegative() { + OptionChecker optionChecker = getOptionChecker(); + String[] input = new String[] { "-target" }; + String expectedException = "not enough arguments"; + + optionChecker.checkOptionsNegative( + input, + expectedException, + null, + null); + + input = new String[] { "-source" }; + optionChecker.checkOptionsNegative( + input, + expectedException, + null, + null); + + input = new String[] { "-source", "1.1" }; + expectedException = "not permitted"; + optionChecker.checkOptionsNegative( + input, + expectedException, + null, + null); + + input = new String[] { "-target", "1.3" }; + optionChecker.checkOptionsNegative( + input, + expectedException, + null, + null); + } + + public void testMultipleInput() { + OptionChecker optionChecker = getOptionChecker(); + String[][] input = + new String[][] { + new String[] { "-warn:deprecated" }, + new String[] { "-warn:deprecated,unverified" }, + new String[] { "-warn:deprecated", "-warn:unusedLocals" }, + new String[] { "-g" }, + new String[] { "-g", "-g:none" }, + new String[] { "-g:vars,source" }, + new String[] { "-verbose", "-g:vars,source" }, + }; + for (int i = 0; i < input.length; i++) { + optionChecker.checkOptions(input[i], input[i]); + } + } + + private OptionChecker getOptionChecker() { + if (null == localOptionChecker) { + localOptionChecker = new OptionChecker(getOptions()); + } + return localOptionChecker; + } +} diff --git a/testing/testsrc/org/aspectj/testing/util/options/OptionsTests.java b/testing/testsrc/org/aspectj/testing/util/options/OptionsTests.java new file mode 100644 index 000000000..2cc937935 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/options/OptionsTests.java @@ -0,0 +1,30 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Wes Isberg initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util.options; + +import junit.framework.*; + +public class OptionsTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(OptionsTests.class.getName()); + //$JUnit-BEGIN$ + suite.addTestSuite(OptionsTest.class); + //$JUnit-END$ + return suite; + } + + public OptionsTests(String name) { super(name); } + +} diff --git a/testing/testsrc/org/aspectj/testing/util/options/ValuesTest.java b/testing/testsrc/org/aspectj/testing/util/options/ValuesTest.java new file mode 100644 index 000000000..cc6623ffc --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/options/ValuesTest.java @@ -0,0 +1,69 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Common Public License v1.0 + * which accompanies this distribution and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * Wes Isberg initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util.options; + +import junit.framework.TestCase; + +/** + */ +public class ValuesTest extends TestCase { + + public ValuesTest(String s) { + super(s); + } + public void testInvert() { + checkInvert(new int[0], 0, new int[0]); // no input or missed => none found + checkInvert(new int[0], 1, new int[] {0}); // no missed, input 1 => 1 found + checkInvert(new int[] {0}, 1, new int[] {}); // 1 (all) missed, input 1 => none found + checkInvert(new int[] {}, 1, new int[] {0}); // 0 (none) missed, input 1 => 1 found + checkInvert(new int[] {1,2}, 3, new int[] {0}); // 2 missed, input 3 => 1 (first) found + checkInvert(new int[] {0,2}, 3, new int[] {1}); // 2 missed, input 3 => 1 (middle) found + checkInvert(new int[] {0,1}, 3, new int[] {2}); // 2 missed, input 3 => 1 (last) found + checkInvert(new int[] {1,3}, 4, new int[] {0,2}); // 2 missed, input 4 => 2 found + checkInvert(new int[] {5,6,7}, 8, new int[] {0,1,2,3,4}); // starting run + checkInvert(new int[] {0,1,2,3,4}, 8, new int[] {5,6,7}); // ending run + checkInvert(new int[] {0,5,6,7}, 8, new int[] {1,2,3,4}); // middle run + checkInvert(new int[] {0,5,6,9},10, new int[] {1,2,3,4,7,8}); // two middle run + checkInvert(new int[] {1,2,5,6,9},10, new int[] {0,3,4,7,8}); // start, 2 middle run + checkInvert(new int[] {0,1,2,5,6},10, new int[] {3,4,7,8,9}); // middle, end run + } + + void checkInvert(int[] missed, int length, int[] expected) { + int[] actual = Values.invert(missed, length); + assertTrue(null != actual); + assertTrue(actual.length == expected.length); + for (int i = 0; i < actual.length; i++) { + if (expected[i] != actual[i]) { + assertTrue("failed at " + i + render(expected, actual), false); + } + } + } + static String render(int[] expected, int[] actual) { + StringBuffer sb = new StringBuffer(); + sb.append(" expected "); + render(expected, sb); + sb.append(" actual "); + render(actual, sb); + return sb.toString(); + } + static void render(int[] ra, StringBuffer sb) { + sb.append("["); + for (int i = 0; i < ra.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append("" + ra[i]); + } + sb.append("]"); + } +} -- 2.39.5