AjcTaskCompileCommand.java
/* *******************************************************************
* Copyright (c) 2003 Contributors.
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v 2.0
* which accompanies this distribution and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
*
* Contributors:
* Wes Isberg initial implementation
* ******************************************************************/
package org.aspectj.testing.taskdefs;
//import java.awt.Frame;
import java.io.File;
import java.io.IOException;
//import java.lang.reflect.*;
//import java.util.List;
import java.util.ArrayList;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.aspectj.bridge.ICommand;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.bridge.IMessageHolder;
import org.aspectj.bridge.MessageHandler;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.tools.ant.taskdefs.AjcTask;
import org.aspectj.util.FileUtil;
import org.aspectj.util.LangUtil;
/**
* Drive tests using the Ant taskdef.
* The non-incremental case is quite easy to implement,
* but incremental compiles require running the compiler
* in another thread using an incremental tag file.
* This is imprecise because it assumes
* incremental compiles are complete
* when messages stop coming from the compiler.
* Also, the client should call quit() when done compiling
* to halt the process.
* XXX build up ICommand with quit (and done-with-last-invocation?)
* to avoid the kludge workarounds
*/
public class AjcTaskCompileCommand implements ICommand {
/**
* 20 seconds of quiet in message holder
* before we assume incremental compile is done
*/
public static int COMPILE_SECONDS_WITHOUT_MESSAGES = 20;
/** 5 minutes maximum time to wait for a compile to complete */
public static int COMPILE_MAX_SECONDS = 300;
/**
* Wait until this holder has not changed the number of messages
* in secs seconds, as a weak form of determining when the
* compile has completed.
* XXX implement "compile-complete" message instead.
* @param holder the IMessageHolder to wait for
* @param seconds the number of seconds that the messages should be the same
* @param timeoutSeconds the int number of seconds after which to time out
* @return true if the messages quiesced before the timeout
* false if parameters are 0 or negative or if
* seconds => timeoutSeconds
*/
static boolean waitUntilMessagesQuiet(
IMessageHolder holder,
int seconds,
int timeoutSeconds) {
LangUtil.throwIaxIfNull(holder, "holder");
if ((0 >= timeoutSeconds) || (0 >= seconds)
|| (timeoutSeconds <= seconds)) {
return false;
}
long curTime = System.currentTimeMillis();
final long timeout = curTime + (timeoutSeconds*1000);
// final Thread thread = Thread.currentThread();
final long targetQuietTime = 1000 * seconds;
int numMessages = holder.numMessages(null, true);
long numMessagesTime = curTime;
// check for new messages every .5 to 3 seconds
final long checkInterval;
{
long interval = seconds / 10;
if (interval > 3000) {
interval = 3000;
} else if (interval < 200) {
interval = 500;
}
checkInterval = interval;
}
while ((curTime < timeout)
&& (curTime < (numMessagesTime + targetQuietTime))) {
// delay until next check
long nextCheck = curTime + checkInterval;
while (nextCheck > curTime) {
try {
Thread.sleep(nextCheck - curTime);
} catch (InterruptedException e) {
// ignore
}
curTime = System.currentTimeMillis();
}
int newNumMessages = holder.numMessages(null, true);
if (newNumMessages != numMessages) {
numMessages = newNumMessages;
numMessagesTime = curTime;
}
}
return (curTime >= (numMessagesTime + targetQuietTime));
}
// single-threaded driver
MessageHandler messages = new MessageHandler();
AjcTask ajcTask;
File incrementalFile;
Thread incrementalCompileThread;
/**
* Stop waiting for any further incremental compiles.
* Safe to call in non-incremental modes.
*/
public void quit() { // XXX requires downcast from ICommand, need validator,IMessageHandler interface
AjcTask task = ajcTask;
if (null != task) {
task.quit(); // signal task to quit, thread to die
ajcTask = null; // XXX need for cleanup?
}
updateIncrementalFile(false, true);
Thread thread = incrementalCompileThread;
if (null != thread) {
if (thread.isAlive()) {
try {
thread.join(3000);
} catch (InterruptedException e) {
// ignore
}
if (thread.isAlive()) {
String s = "WARNING: abandoning undying thread ";
System.err.println(s + thread.getName());
}
}
incrementalCompileThread = null;
}
}
// --------- ICommand interface
public boolean runCommand(String[] args, IMessageHandler handler) {
return (makeAjcTask(args, handler)
&& doCommand(handler, false));
}
/**
* Fails if called before runCommand or if
* not in incremental mode or if the command
* included an incremental file entry.
* @return
*/
public boolean repeatCommand(IMessageHandler handler) {
return doCommand(handler, true);
}
protected boolean doCommand(IMessageHandler handler, boolean repeat) {
messages.clearMessages();
if (null == ajcTask) {
MessageUtil.fail(messages, "ajcTask null - repeat without do");
}
try {
// normal, non-incremental case
if (!repeat && (null == incrementalFile)) {
ajcTask.execute();
// rest is for incremental mode
} else if (null == incrementalFile) {
MessageUtil.fail(messages, "incremental mode not specified");
} else if (!updateIncrementalFile(false, false)) {
MessageUtil.fail(messages, "can't update incremental file");
} else if (!repeat) { // first incremental compile
incrementalCompileThread = new Thread(
new Runnable() {
public void run() {
ajcTask.execute();
}
}, "AjcTaskCompileCommand-incremental");
incrementalCompileThread.start();
waitUntilMessagesQuiet(messages, 10, 180);
} else {
Thread thread = incrementalCompileThread;
if (null == thread) {
MessageUtil.fail(messages, "incremental process stopped");
} else if (!thread.isAlive()) {
MessageUtil.fail(messages, "incremental process dead");
} else {
waitUntilMessagesQuiet(messages, 10, 180);
}
}
} catch (BuildException e) {
Throwable t = e.getCause();
while (t instanceof BuildException) {
t = ((BuildException) t).getCause();
}
if (null == t) {
t = e;
}
MessageUtil.abort(messages, "BuildException " + e.getMessage(), t);
} finally {
MessageUtil.handleAll(handler, messages, false);
}
return (0 == messages.numMessages(IMessage.ERROR, true));
}
protected boolean makeAjcTask(String[] args, IMessageHandler handler) {
ajcTask = null;
incrementalFile = null;
AjcTask result = null;
try {
result = new AjcTask();
Project project = new Project();
project.setName("AjcTaskCompileCommand");
result.setProject(project);
result.setMessageHolder(messages);
// XXX argh - have to strip out "-incremental"
// because tools.ajc.Main privileges it over tagfile
ArrayList newArgs = new ArrayList();
boolean incremental = false;
for (int i = 0; i < args.length; i++) {
if ("-incremental".equals(args[i])) {
incremental = true;
} else if ("-XincrementalFile".equals(args[i])) {
// CommandController.TAG_FILE_OPTION = "-XincrementalFile";
incremental = true;
i++;
} else {
newArgs.add(args[i]);
}
}
if (incremental) {
args = (String[]) newArgs.toArray(new String[0]);
}
result.readArguments(args);
if (incremental || result.isInIncrementalMode()) {
// these should be impossible...
if (result.isInIncrementalFileMode()) {
String m = "incremental file set in command";
MessageUtil.fail(handler, m);
return false;
} else if (null != incrementalFile) {
String m = "incremental file set already";
MessageUtil.fail(handler, m);
return false;
}
String prefix = "AjcTaskCompileCommand_makeAjcTask";
File tmpDir = FileUtil.getTempDir(prefix);
incrementalFile = new File(tmpDir, "tagfile.tmp");
boolean create = true;
boolean delete = false;
updateIncrementalFile(create, delete);
result.setTagFile(incrementalFile);
}
// do not throw BuildException on error
result.setFailonerror(false);
ajcTask = result;
} catch (BuildException e) {
MessageUtil.abort(handler,"setting up AjcTask", e);
}
return (null != ajcTask);
}
protected boolean updateIncrementalFile(boolean create, boolean delete) {
File file = incrementalFile;
if (delete) {
try {
return (null == file)
|| !file.exists()
|| file.delete();
} finally {
incrementalFile = null;
}
}
if (null == file) {
return false;
}
if (file.exists()) {
return file.setLastModified(System.currentTimeMillis());
} else {
try {
return create && file.createNewFile();
} catch (IOException e) { // XXX warn in verbose mode?
return false;
}
}
}
}