/* ******************************************************************* * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). * 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: * Xerox/PARC initial implementation * ******************************************************************/ package org.aspectj.testing.harness.bridge; import java.io.File; import java.io.FileFilter; import java.util.ArrayList; import java.util.Arrays; import org.aspectj.bridge.ICommand; import org.aspectj.bridge.IMessage; import org.aspectj.bridge.IMessageHandler; import org.aspectj.testing.taskdefs.AjcTaskCompileCommand; import org.aspectj.testing.util.Diffs; import org.aspectj.util.FileUtil; import org.aspectj.util.LangUtil; /** * A sandbox holds state shared by AjcTest sub-runs, * mostly directories relevant to testing. * It permits a limited amount of coordination and * setup/cleanup operations (todo XXX). *

* AjcTest creates the Sandbox and initializes the final fields. * To coordinate with each other, run components may set and get values, * with the sources running first and the sinks second. * To make the interactions clear * (and to avoid accidentally violating these semantics), * setters/getters for a coordinated property are constrained two ways: *

  • Both have an extra (typed) "caller" parameter which must not * be null, authenticating that the caller is known & valid.
  • *
  • A getter throws IllegalStateException if called before the setter
  • *
  • A setter throws IllegalStateException if called after the getter
  • * XXX subclass more general sandbox? */ public class Sandbox { /** classes directory token for DirChanges.Spec */ public static final String RUN_DIR = "run"; /** run directory token for DirChanges.Spec */ public static final String CLASSES_DIR = "classes"; private static boolean canRead(File dir) { return ((null != dir) && dir.isDirectory() && dir.canRead()); } private static boolean canWrite(File dir) { return ((null != dir) && dir.isDirectory() && dir.canWrite()); } private static void iaxWrite(File dir, String label) { if (!canWrite(dir)) { throw new IllegalArgumentException(label + " - " + dir); } } private static void iaxRead(File dir, String label) { if (!canRead(dir)) { throw new IllegalArgumentException(label + " - " + dir); } } /** @throws IllegalStateException(message) if test */ private static void assertState(boolean test, String message) { if (!test) { throw new IllegalStateException(message); } } /** * The (read-only) base of the test sources (which may or may not * be the base of the java sources) */ public final File testBaseDir; /** the parent of a temporary workspace, probably includes some others */ public final File sandboxDir; /** a shared working dir */ public final File workingDir; /** a shared classes dir */ public final File classesDir; /** a run dir (which will be ignored in non-forking runs) */ public final File runDir; /** staging directory for IAjcRun requiring files be copied, deleted, etc. */ public final File stagingDir; /** * This manages creation and deletion of temporary directories. * We hold a reference so that our clients can signal whether * this should be deleted. */ private final Validator validator; // XXX required after completing tests? /** original base of the original java sources, set by CompileRun.setup(..) */ private File testBaseSrcDir; /** directories and libraries on the classpath, set by CompileRun.setup(..) */ private File[] compileClasspath; private String bootClasspath; /** aspectpath entries, set by CompileRun.setup(..) */ private File[] aspectpath; /** track whether classpath getter ran */ private boolean gotClasspath; /** command shared between runs using sandbox - i.e., compiler */ private ICommand command; /** track whether command getter ran */ private boolean gotCommand; /** cache results of rendering final fields */ private transient String toStringLeader; private transient boolean compilerRunInit; /** @throws IllegalArgumentException unless validator validates * testBaseDir as readable */ public Sandbox(File testBaseDir, Validator validator) { LangUtil.throwIaxIfNull(validator, "validator"); this.validator = validator; Sandbox.iaxRead(testBaseDir, "testBaseDir"); this.testBaseDir = testBaseDir; { File baseDir = FileUtil.getTempDir("Sandbox"); if (!baseDir.isAbsolute()) { baseDir = baseDir.getAbsoluteFile(); } sandboxDir = baseDir; } Sandbox.iaxWrite(sandboxDir, "sandboxDir"); // XXX not really iax workingDir = FileUtil.makeNewChildDir(sandboxDir, "workingDir"); Sandbox.iaxWrite(workingDir, "workingDir"); classesDir = FileUtil.makeNewChildDir(sandboxDir, "classes"); Sandbox.iaxWrite(classesDir, "classesDir"); runDir = FileUtil.makeNewChildDir(sandboxDir, "run"); Sandbox.iaxWrite(runDir, "runDir"); stagingDir = FileUtil.makeNewChildDir(sandboxDir, "staging"); Sandbox.iaxWrite(stagingDir, "stagingDir"); validator.registerSandbox(this); } private String getToStringLeader() { if (null == toStringLeader) { toStringLeader = "Sandbox(" + sandboxDir.getName() + ", " + testBaseSrcDir.getName(); } return toStringLeader; } /** @return "Sandbox(sandbox, src, classes)" with names only */ public String toString() { return getToStringLeader() + ", " + classesDir.getName() + ")"; } /** @return "Sandbox(sandbox, src, classes)" with paths */ public String toLongString() { return getToStringLeader() + ", " + classesDir.getPath() + (null == command ? ", (null command)" : ", " + command) + ")"; } void setCommand(ICommand command, CompilerRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); LangUtil.throwIaxIfNull(command, "command"); LangUtil.throwIaxIfFalse(!gotCommand, "no command"); this.command = command; } /** When test is completed, clear the compiler to avoid memory leaks */ void clearCommand(AjcTest caller) { LangUtil.throwIaxIfNull(caller, "caller"); if (null != command) { // need to add ICommand.quit() if (command instanceof AjcTaskCompileCommand) { // XXX urk! ((AjcTaskCompileCommand) command).quit(); } command = null; } // also try to clear sandbox/filesystem. // If locked by suite, we can't. if (null != validator) { validator.deleteTempFiles(true); } } // /** // * Populate the staging directory by copying any files in the // * source directory ending with fromSuffix // * to the staging directory, after renaming them with toSuffix. // * If the source file name starts with "delete", then the // * corresponding file in the staging directory is deleting. // * @return a String[] of the files copied or deleted // * (path after suffix changes and relative to staging dir) // * @throws Error if no File using fromSuffix are found // */ // String[] populateStagingDir(String fromSuffix, String toSuffix, IAjcRun caller) { // LangUtil.throwIaxIfNull(fromSuffix, "fromSuffix"); // LangUtil.throwIaxIfNull(toSuffix, "toSuffix"); // LangUtil.throwIaxIfNull(caller, "caller"); // // ArrayList result = new ArrayList(); // FileUtil.copyDir( // srcBase, // targetSrc, // fromSuffix, // toSuffix, // collector); // // final String canonicalFrom = srcBase.getCanonicalPath(); // final Definition[] defs = getDefinitions(srcBase); // if ((null == defs) || (defs.length < 9)) { // throw new Error("did not get definitions"); // } // MessageHandler compilerMessages = new MessageHandler(); // StringBuffer commandLine = new StringBuffer(); // for (int i = 1; result && (i < 10); i++) { // String fromSuffix = "." + i + "0.java"; // // copy files, collecting as we go... // files.clear(); // if (0 == files.size()) { // XXX detect incomplete? // break; // } // // // return (String[]) result.toArray(new String[0]); // } // XXX move to more general in FileUtil void reportClassDiffs( final IMessageHandler handler, IncCompilerRun caller, long classesDirStartTime, String[] expectedSources) { LangUtil.throwIaxIfFalse(0 < classesDirStartTime, "0 >= " + classesDirStartTime); boolean acceptPrefixes = true; Diffs diffs = org.aspectj.testing.util.FileUtil.dirDiffs( "classes", classesDir, classesDirStartTime, ".class", expectedSources, acceptPrefixes); diffs.report(handler, IMessage.ERROR); } // // XXX replace with IMessage-based implementation // // XXX move to more general in FileUtil // void reportClassesDirDiffs(final IMessageHandler handler, IncCompilerRun caller, // String[] expectedSources) { // // normalize sources to ignore // final ArrayList sources = new ArrayList(); // if (!LangUtil.isEmpty(expectedSources)) { // for (int i = 0; i < expectedSources.length; i++) { // String srcPath = expectedSources[i]; // int clip = FileUtil.sourceSuffixLength(srcPath); // if (0 != clip) { // srcPath = srcPath.substring(0, srcPath.length() - clip); // sources.add(FileUtil.weakNormalize(srcPath)); // } else if (srcPath.endsWith(".class")) { // srcPath = srcPath.substring(0, srcPath.length() - 6); // sources.add(FileUtil.weakNormalize(srcPath)); // } else { // MessageUtil.info(handler, "not source file: " + srcPath); // } // } // } // // // gather, normalize paths changed // final ArrayList changed = new ArrayList(); // FileFilter touchedCollector = new FileFilter() { // public boolean accept(File file) { // if (file.lastModified() > classesDirTime) { // String path = file.getPath(); // if (!path.endsWith(".class")) { // MessageUtil.info(handler, "changed file not a class: " + file); // } else { // String classPath = path.substring(0, path.length() - 6); // classPath = FileUtil.weakNormalize(classPath); // if (sources.contains(classPath)) { // sources.remove(classPath); // } else { // changed.add(classPath); // } // } // } // return false; // } // }; // classesDir.listFiles(touchedCollector); // // // report any unexpected changes // Diffs diffs = new Diffs("classes", sources, changed, String.CASE_INSENSITIVE_ORDER); // diffs.report(handler, IMessage.ERROR); // } ICommand getCommand(CompilerRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); assertState(null != command, "command never set"); return command; } ICommand getCommand(IncCompilerRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); assertState(null != command, "command never set"); return command; } File getTestBaseSrcDir(IncCompilerRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); return testBaseSrcDir; } /** * Get the files with names (case-sensitive) * under the staging or test base directories. * @param names * @return */ File[] findFiles(final String[] names) { ArrayList result = new ArrayList(); NamesFilter filter = new NamesFilter(names); File[] bases = { testBaseDir, sandboxDir }; for (int i = 0; i < bases.length; i++) { File base = bases[i]; if ((null == base) || !base.canRead()) { continue; } result.addAll(Arrays.asList(FileUtil.listFiles(base, filter))); } return (File[]) result.toArray(new File[0]); } File getTestBaseSrcDir(JavaRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); return testBaseSrcDir; } void defaultTestBaseSrcDir(JavaRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); if (null != testBaseSrcDir) { throw new IllegalStateException("testBaseSrcDir not null"); } testBaseSrcDir = testBaseDir; } static boolean readableDir(File dir) { return ((null != dir) && dir.isDirectory() && dir.canRead()); } void compilerRunInit(CompilerRun caller, File testBaseSrcDir, File[] aspectPath, boolean aspectpathReadable, File[] classPath, boolean classpathReadable, String bootclassPath ) { if (null != testBaseSrcDir) { setTestBaseSrcDir(testBaseSrcDir, caller); } if ((null != aspectPath) && (0 < aspectPath.length)) { setAspectpath(aspectPath, aspectpathReadable, caller); } if ((null != classPath) && (0 < classPath.length)) { setClasspath(classPath, classpathReadable, caller); } setBootclasspath(bootclassPath, caller); compilerRunInit = true; } void javaRunInit(JavaRun caller) { if (!compilerRunInit) { testBaseSrcDir = testBaseDir; // default to aspectjrt.jar? compileClasspath = new File[0]; } } /** @throws IllegalArgumentException unless a readable directory */ private void setTestBaseSrcDir(File dir, CompilerRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); if ((null == dir) || !dir.isDirectory() || !dir.canRead()) { throw new IllegalArgumentException("bad test base src dir: " + dir); } testBaseSrcDir = dir; } /** * Set aspectpath. * @param readable if true, then throw IllegalArgumentException if not readable */ private void setAspectpath(File[] files, boolean readable, CompilerRun caller) { LangUtil.throwIaxIfNull(files, "files"); LangUtil.throwIaxIfNull(caller, "caller"); assertState(null == aspectpath, "aspectpath already written"); aspectpath = new File[files.length]; for (int i = 0; i < files.length; i++) { LangUtil.throwIaxIfNull(files[i], "files[i]"); if (readable && !files[i].canRead()) { throw new IllegalArgumentException("bad aspectpath entry: " + files[i]); } aspectpath[i] = files[i]; } } /** * Set bootclasspath, presumed to be delimited by * File.pathSeparator and have valid entries. * @param bootClasspath * @param caller */ private void setBootclasspath(String bootClasspath, CompilerRun caller) { this.bootClasspath = bootClasspath; } /** * Set compile classpath. * @param readable if true, then throw IllegalArgumentException if not readable */ private void setClasspath(File[] files, boolean readable, CompilerRun caller) { LangUtil.throwIaxIfNull(files, "files"); LangUtil.throwIaxIfNull(caller, "caller"); assertState(!gotClasspath, "classpath already read"); compileClasspath = new File[files.length]; for (int i = 0; i < files.length; i++) { LangUtil.throwIaxIfNull(files[i], "files[i]"); if (readable && !files[i].canRead()) { throw new IllegalArgumentException("bad classpath entry: " + files[i]); } compileClasspath[i] = files[i]; } } // /** // * Get run classpath // * @param caller unused except to restrict usage to non-null JavaRun. // * @throws IllegalStateException if compileClasspath was not set. // * @throws IllegalArgumentException if caller is null // */ // File[] getRunClasspath(JavaRun caller) { // LangUtil.throwIaxIfNull(caller, "caller"); // assertState(null != compileClasspath, "classpath not set"); // int compilePathLength = compileClasspath.length; // int aspectPathLength = (null == aspectpath ? 0 : aspectpath.length); // File[] result = new File[aspectPathLength + compilePathLength]; // System.arraycopy(compileClasspath, 0, result, 0, compilePathLength); // if (0 < aspectPathLength) { // System.arraycopy(aspectpath, 0, result, compilePathLength, aspectPathLength); // } // return result; // } /** * Get directories for the run classpath by selecting them * from the compile classpath. * This ignores aspectpath since it may contain only jar files. * @param readable if true, omit non-readable directories */ File[] getClasspathDirectories( boolean readable, JavaRun caller, boolean includeOutput) { LangUtil.throwIaxIfNull(caller, "caller"); assertState(null != compileClasspath, "classpath not set"); ArrayList result = new ArrayList(); File[] src = compileClasspath; for (int i = 0; i < src.length; i++) { File f = src[i]; if ((null != f) && (f.isDirectory()) && (!readable || f.canRead())) { result.add(f); } } if (includeOutput && (null != classesDir) && (!readable || classesDir.canRead())) { result.add(classesDir); } return (File[]) result.toArray(new File[0]); } /** * Get the jars belonging on the run classpath, including classpath * and aspectpath entries. * @param readable if true, omit non-readable directories */ File[] getClasspathJars(boolean readable, JavaRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); assertState(null != compileClasspath, "classpath not set"); ArrayList result = new ArrayList(); File[][] src = new File[][] { compileClasspath, aspectpath }; for (int i = 0; i < src.length; i++) { File[] paths = src[i]; int len = (null == paths ? 0 : paths.length); for (int j = 0; j < len; j++) { File f = paths[j]; if (FileUtil.isZipFile(f) && (!readable || f.canRead())) { result.add(f); } } } return (File[]) result.toArray(new File[0]); } /** * Get the list of aspect jars as a String. * @return String of classpath entries delimited internally by File.pathSeparator */ String aspectpathToString(CompilerRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); return FileUtil.flatten(aspectpath, File.pathSeparator); } /** * Get the compile classpath as a String. * @return String of classpath entries delimited internally by File.pathSeparator */ String classpathToString(CompilerRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); return FileUtil.flatten(compileClasspath, File.pathSeparator); } /** * Get the bootClasspath as a String. * @return String of bootclasspath entries delimited internally by File.pathSeparator */ String getBootclasspath(CompilerRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); return bootClasspath; } /** * Get the bootClasspath as a String. * @return String of bootclasspath entries delimited internally by File.pathSeparator */ String getBootclasspath(JavaRun caller) { LangUtil.throwIaxIfNull(caller, "caller"); return bootClasspath; } private static class NamesFilter implements FileFilter { private final String[] names; private NamesFilter(String[] names) { this.names = names; } public boolean accept(File file) { if (null != file) { String name = file.getName(); if ((null != name) && (null != names)) { for (int i = 0; i < names.length; i++) { if (name.equals(names[i])) { return true; } } } } return false; } } }