/* *******************************************************************
* Copyright (c) 1999-2001 Xerox Corporation,
* 2002 Palo Alto Research Center, Incorporated (PARC).
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v1.0
* which accompanies this distribution and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Xerox/PARC initial implementation
* ******************************************************************/
package org.aspectj.internal.tools.build;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Properties;
import java.util.StringTokenizer;
import org.aspectj.internal.tools.build.Result.Kind;
import org.aspectj.internal.tools.build.Util.OSGIBundle;
import org.aspectj.internal.tools.build.Util.OSGIBundle.RequiredBundle;
/**
* This represents an (eclipse) build module/unit used by a Builder to compile
* classes and/or assemble zip file of classes, optionally with all antecedants.
* This implementation infers attributes from two files in the module directory:
*
* - an Eclipse project
.classpath
file containing required
* libraries and modules (collectively, "antecedants")
* - a file
{moduleName}.mf.txt
is taken as the manifest of
* any .jar file produced, after filtering.
*
*
* @see Builder
* @see Modules#getModule(String)
*/
public class Module {
private static final String[] ATTS = new String[] { "exported", "kind",
"path", "sourcepath" };
// private static final int getATTSIndex(String key) {
// for (int i = 0; i < ATTS.length; i++) {
// if (ATTS[i].equals(key))
// return i;
// }
// return -1;
// }
/**
* @return true if file is null or cannot be read or was last modified after
* time
*/
private static boolean outOfDate(long time, File file) {
return ((null == file) || !file.canRead() || (file.lastModified() > time));
}
/** @return all source files under srcDir */
private static Iterator sourceFiles(File srcDir) {
ArrayList result = new ArrayList<>();
sourceFiles(srcDir, result);
return result.iterator();
}
private static void sourceFiles(File srcDir, List result) {
if ((null == srcDir) || !srcDir.canRead() || !srcDir.isDirectory()) {
return;
}
File[] files = srcDir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
sourceFiles(file, result);
} else if (isSourceFile(file)) {
result.add(file);
}
}
}
private static void addIfNew(List source, List sink) {
for (File item: source) {
if (!sink.contains(item)) {
sink.add(item);
}
}
}
/**
* Recursively find antecedant jars.
*
* @see findKnownJarAntecedants()
*/
static void doFindJarRequirements(Result result, List known) {
Util.iaxIfNull(result, "result");
Util.iaxIfNull(known, "known");
addIfNew(result.getLibJars(), known);
addIfNew(result.getExportedLibJars(), known);
Result[] reqs = result.getRequired();
for (Result requiredResult : reqs) {
File requiredJar = requiredResult.getOutputFile();
if (!known.contains(requiredJar)) {
known.add(requiredJar);
doFindJarRequirements(requiredResult, known);
}
}
}
/** @return true if this is a source file */
private static boolean isSourceFile(File file) {
String path = file.getPath();
return (path.endsWith(".java") || path.endsWith(".aj")); // XXXFileLiteral
}
// /** @return List of File of any module or library jar ending with suffix */
// private static ArrayList findJarsBySuffix(String suffix, Kind kind,
// List libJars, List required) {
// ArrayList result = new ArrayList();
// if (null != suffix) {
// // library jars
// for (Iterator iter = libJars.iterator(); iter.hasNext();) {
// File file = (File) iter.next();
// if (file.getPath().endsWith(suffix)) {
// result.add(file);
// }
// }
// // module jars
// for (Iterator iter = required.iterator(); iter.hasNext();) {
// Module module = (Module) iter.next();
// Result moduleResult = module.getResult(kind);
// File file = moduleResult.getOutputFile();
// if (file.getPath().endsWith(suffix)) {
// result.add(file);
// }
// }
// }
// return result;
// }
public final boolean valid;
public final File moduleDir;
public final String name;
/** reference back to collection for creating required modules */
private final Modules modules;
private final Result release;
private final Result test;
private final Result testAll;
private final Result releaseAll;
/** path to output jar - may not exist */
private final File moduleJar;
/** File list of library jars */
private final List libJars;
/** List of classpath variables */
private final List classpathVariables;
/**
* List of library jars exported to clients (duplicates some libJars
* entries)
*/
private final List exportedLibJars;
/** File list of source directories */
private final List srcDirs;
/** properties from the modules {name}.properties file */
private final Properties properties;
/** List of required modules */
private final List requiredModules;
/** logger */
private final Messager messager;
Module(File moduleDir, File jarDir, String name, Modules modules,
Messager messager) {
Util.iaxIfNotCanReadDir(moduleDir, "moduleDir");
Util.iaxIfNotCanReadDir(jarDir, "jarDir");
Util.iaxIfNull(name, "name");
Util.iaxIfNull(modules, "modules");
this.moduleDir = moduleDir;
this.libJars = new ArrayList<>();
this.exportedLibJars = new ArrayList<>();
this.requiredModules = new ArrayList<>();
this.srcDirs = new ArrayList<>();
this.classpathVariables = new ArrayList<>();
this.properties = new Properties();
this.name = name;
this.modules = modules;
this.messager = messager;
this.moduleJar = new File(jarDir, name + ".jar");
this.release = new Result(Result.RELEASE, this, jarDir);
this.releaseAll = new Result(Result.RELEASE_ALL, this, jarDir);
this.test = new Result(Result.TEST, this, jarDir);
this.testAll = new Result(Result.TEST_ALL, this, jarDir);
valid = init();
}
/** @return Modules registry of known modules, including this one */
public Modules getModules() {
return modules;
}
/**
* @param kind
* the Kind of the result to recalculate
* @param recalculate
* if true, then force recalculation
* @return true if the target jar for this module is older than any source
* files in a source directory or any required modules or any
* libraries or if any libraries or required modules are missing
*/
public static boolean outOfDate(Result result) {
File outputFile = result.getOutputFile();
if (!(outputFile.exists() && outputFile.canRead())) {
return true;
}
final long time = outputFile.lastModified();
File file;
for (File srcDir : result.getSrcDirs()) {
for (Iterator srcFiles = sourceFiles(srcDir); srcFiles.hasNext(); ) {
file = srcFiles.next();
if (outOfDate(time, file)) {
return true;
}
}
}
// required modules
Result[] reqs = result.getRequired();
for (Result requiredResult : reqs) {
file = requiredResult.getOutputFile();
if (outOfDate(time, file)) {
return true;
}
}
// libraries
for (File value : result.getLibJars()) {
file = value;
if (outOfDate(time, file)) {
return true;
}
}
return false;
}
public String toString() {
return name;
}
public String toLongString() {
return "Module [name=" + name + ", srcDirs=" + srcDirs + ", required="
+ requiredModules + ", moduleJar=" + moduleJar + ", libJars="
+ libJars + "]";
}
public Result getResult(Kind kind) {
return kind.assemble ? (kind.normal ? releaseAll : testAll)
: (kind.normal ? release : test);
}
List srcDirs(Result result) {
myResult(result);
return srcDirs;
}
List libJars(Result result) {
myResult(result);
return libJars;
}
List classpathVariables(Result result) {
myResult(result);
return classpathVariables;
}
List exportedLibJars(Result result) {
myResult(result);
return exportedLibJars;
}
List requiredModules(Result result) {
myResult(result);
return requiredModules;
}
private void myResult(Result result) {
if ((null == result) || this != result.getModule()) {
throw new IllegalArgumentException("not my result: " + result + ": " + this);
}
}
private boolean init() {
boolean cp = initClasspath();
boolean mf = initManifest();
if (!cp && !mf) {
return false;
}
return initProperties() && reviewInit() && initResults();
}
/** read OSGI manifest.mf file XXX hacked */
private boolean initManifest() {
File metaInf = new File(moduleDir, "META-INF");
if (!metaInf.canRead() || !metaInf.isDirectory()) {
return false;
}
File file = new File(metaInf, "MANIFEST.MF"); // XXXFileLiteral
if (!file.exists()) {
return false; // ok, not OSGI
}
InputStream fin = null;
OSGIBundle bundle = null;
try {
fin = new FileInputStream(file);
bundle = new OSGIBundle(fin);
} catch (IOException e) {
messager.logException("IOException reading " + file, e);
return false;
} finally {
Util.closeSilently(fin);
}
RequiredBundle[] bundles = bundle.getRequiredBundles();
for (RequiredBundle required : bundles) {
update("src", "/" + required.name, required.text, false);
}
String[] libs = bundle.getClasspath();
for (String lib : libs) {
update("lib", lib, lib, false);
}
return true;
}
/** read eclipse .classpath file XXX line-oriented hack */
private boolean initClasspath() {
// meaning testsrc directory, junit library, etc.
File file = new File(moduleDir, ".classpath"); // XXXFileLiteral
if (!file.exists()) {
return false; // OSGI???
}
FileReader fin = null;
try {
fin = new FileReader(file);
BufferedReader reader = new BufferedReader(fin);
String line;
XMLItem item = new XMLItem("classpathentry", new ICB());
while (null != (line = reader.readLine())) {
line = line.trim();
// dumb - only handle comment-only lines
if (!line.startsWith(" iter = srcDirs.listIterator(); iter.hasNext();) {
File srcDir = iter.next();
String lcname = srcDir.getName().toLowerCase();
if (!Util.JAVA5_VM
&& (Util.Constants.JAVA5_SRC.equals(lcname) || Util.Constants.JAVA5_TESTSRC
.equals(lcname))) {
// assume optional for pre-1.5 builds
iter.remove();
}
}
} catch (UnsupportedOperationException e) {
return false; // failed XXX log also if verbose
}
return true;
}
/**
* After reviewInit, setup four kinds of results.
*/
protected boolean initResults() {
return true; // results initialized lazily
}
/** resolve path absolutely, assuming / means base of modules dir */
public String getFullPath(String path) {
String fullPath;
if (path.startsWith("/")) {
fullPath = modules.baseDir.getAbsolutePath() + path;
} else {
fullPath = moduleDir.getAbsolutePath() + "/" + path;
}
// check for absolute paths (untested - none in our modules so far)
File testFile = new File(fullPath);
// System.out.println("Module.getFullPath: " + fullPath + " - " +
// testFile.getAbsolutePath());
if (!testFile.exists()) {
testFile = new File(path);
if (testFile.exists() && testFile.isAbsolute()) {
fullPath = path;
}
}
return fullPath;
}
class ICB implements XMLItem.ICallback {
public void end(Properties attributes) {
String kind = attributes.getProperty("kind");
String path = attributes.getProperty("path");
String exp = attributes.getProperty("exported");
boolean exported = ("true".equals(exp));
ByteArrayOutputStream bout = new ByteArrayOutputStream();
attributes.list(new PrintStream(bout));
update(kind, path, bout.toString(), exported);
}
}
public static class XMLItem {
public interface ICallback {
void end(Properties attributes);
}
static final String START_NAME = "classpathentry";
static final String ATT_STARTED = "STARTED";
final ICallback callback;
final StringBuffer input = new StringBuffer();
final String[] attributes = new String[ATTS.length];
final String targetEntity;
String entityName;
String attributeName;
XMLItem(String targetEntity, ICallback callback) {
this.callback = callback;
this.targetEntity = targetEntity;
reset();
}
private void reset() {
input.setLength(0);
for (int i = 0; i < attributes.length; i++) {
attributes[i] = null;
}
entityName = null;
attributeName = null;
}
String[] tokenize(String line) {
final String DELIM = " \n\t\\<>\"=";
StringTokenizer st = new StringTokenizer(line, DELIM, true);
ArrayList result = new ArrayList<>();
StringBuffer quote = new StringBuffer();
boolean inQuote = false;
while (st.hasMoreTokens()) {
String s = st.nextToken();
if ((1 == s.length()) && (DELIM.contains(s))) {
if ("\"".equals(s)) { // end quote (or escaped)
if (inQuote) {
inQuote = false;
quote.append("\"");
result.add(quote.toString());
quote.setLength(0);
} else {
quote.append("\"");
inQuote = true;
}
} else {
result.add(s);
}
} else { // not a delimiter
if (inQuote) {
quote.append(s);
} else {
result.add(s);
}
}
}
return result.toArray(new String[0]);
}
public void acceptLine(String line) {
String[] tokens = tokenize(line);
for (String token : tokens) {
next(token);
}
}
private Properties attributesToProperties() {
Properties result = new Properties();
for (int i = 0; i < attributes.length; i++) {
String a = attributes[i];
if (null != a) {
result.setProperty(ATTS[i], a);
}
}
return result;
}
void errorIfNotNull(String name, String value) {
if (null != value) {
error("Did not expect " + name + ": " + value);
}
}
void errorIfNull(String name, String value) {
if (null == value) {
error("expected value for " + name);
}
}
boolean activeEntity() {
return targetEntity.equals(entityName);
}
/**
* Assumes that comments and "".equals(s)) {
errorIfNull("entityName", entityName);
if ("/".equals(attributeName)) {
attributeName = null;
} else {
errorIfNotNull("attributeName", attributeName);
}
if (activeEntity()) {
callback.end(attributesToProperties());
}
entityName = null;
} else if ("=".equals(s)) {
errorIfNull("entityName", entityName);
errorIfNull("attributeName", attributeName);
} else if (s.startsWith("\"")) {
errorIfNull("entityName", entityName);
errorIfNull("attributeName", attributeName);
writeAttribute(attributeName, s);
attributeName = null;
} else {
if (null == entityName) {
reset();
entityName = s;
} else if (null == attributeName) {
attributeName = s;
} else {
System.out
.println("unknown state - not value, attribute, or entity: "
+ s);
}
}
}
void readAttribute(String s) {
for (int i = 0; i < ATTS.length; i++) {
if (s.equals(ATTS[i])) {
attributes[i] = ATT_STARTED;
break;
}
}
}
void writeAttribute(String name, String value) {
for (int i = 0; i < ATTS.length; i++) {
if (name.equals(ATTS[i])) {
if (!value.startsWith("\"") || !value.endsWith("\"")) {
error("bad attribute value: " + value);
}
value = value.substring(1, value.length() - 1);
attributes[i] = value;
return;
}
}
}
void error(String s) {
throw new Error(s + " at input " + input);
}
}
}