blob: 044545fafeb68d47bee85b583f8db1e0e86f639a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tools.ant.module.bridge.impl;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildListener;
import org.apache.tools.ant.Location;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.RuntimeConfigurable;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.module.bridge.AntBridge;
import org.apache.tools.ant.module.run.Hyperlink;
import org.apache.tools.ant.module.run.LoggerTrampoline;
import org.apache.tools.ant.module.run.StandardLogger;
import org.apache.tools.ant.module.spi.AntEvent;
import org.apache.tools.ant.module.spi.AntLogger;
import org.apache.tools.ant.module.spi.AntSession;
import org.apache.tools.ant.module.spi.TaskStructure;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.modules.project.indexingbridge.IndexingBridge;
import org.openide.awt.StatusDisplayer;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbCollections;
import org.openide.util.Parameters;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakSet;
import org.openide.windows.InputOutput;
import org.openide.windows.OutputListener;
import org.openide.windows.OutputWriter;
/**
* NetBeans-sensitive build logger.
* Just delegates all events to the registered SPI loggers
* through an abstraction layer.
* Synchronization: all callbacks are synchronized, both to protect access to logger
* caches, and to prevent AntBridge.suspend/resumeDelegation from being called with
* dynamic overlaps.
* @author Jesse Glick
*/
final class NbBuildLogger implements BuildListener, LoggerTrampoline.AntSessionImpl {
private static final Logger LOG = Logger.getLogger(NbBuildLogger.class.getName());
final AntSession thisSession;
private final File origScript;
private String[] targets = null;
final OutputWriter out;
final OutputWriter err;
final InputOutput io;
private final int verbosity;
private final Map<String,String> properties;
private final Set<? extends String> concealedProperties;
private final String displayName;
private final Runnable interestingOutputCallback;
private final ProgressHandle handle;
private boolean insideRunTask = false; // #95201
private IndexingBridge.Lock protectedMode; // #211005
private final Object protectedModeLock = new Object();
private final RequestProcessor.Task sleepTask = new RequestProcessor(NbBuildLogger.class.getName(), 1, false, false).create(new Runnable() {
public @Override void run() {
handle.suspend(insideRunTask ? NbBundle.getMessage(NbBuildLogger.class, "MSG_sleep_running") : "");
exitProtectedMode();
}
});
private static final int SLEEP_DELAY = 5000;
private final Map<AntLogger,Object> customData = new HashMap<AntLogger,Object>();
private List<AntLogger> interestedLoggers = null;
private Map<File/*|null*/,Collection<AntLogger>> interestedLoggersByScript = new HashMap<File,Collection<AntLogger>>();
private Map<String/*|null*/,Collection<AntLogger>> interestedLoggersByTarget = new HashMap<String,Collection<AntLogger>>();
private Map<String/*|null*/,Collection<AntLogger>> interestedLoggersByTask = new HashMap<String,Collection<AntLogger>>();
private Map<Integer,Collection<AntLogger>> interestedLoggersByLevel = new HashMap<Integer,Collection<AntLogger>>();
private final Set<Project> projectsWithProperties = Collections.synchronizedSet(new WeakSet<Project>());
private final Set<Throwable> consumedExceptions = new WeakSet<Throwable>();
/** whether this process should be halted at the next safe point */
private boolean stop = false;
/** whether this process is thought to be still running */
private boolean running = true;
private static final ThreadLocal<Boolean> insideToString = new ThreadLocal<Boolean>() {
protected @Override Boolean initialValue() {
return false;
}
};
/**
* Map from master build scripts to maps from imported target names to imported locations.
* Hack for lack of Target.getLocation() in Ant 1.6.2 and earlier.
* Unused if targetGetLocation is not null.
*/
private final Map<String,Map<String,String>> knownImportedTargets = Collections.synchronizedMap(new HashMap<String,Map<String,String>>());
/**
* Main script known to be being parsed at the moment.
* Unused if targetGetLocation is not null.
*/
private String currentlyParsedMainScript = null;
/**
* Imported script known to be being parsed at the moment.
* Unused if targetGetLocation is not null.
*/
private String currentlyParsedImportedScript = null;
/**
* Last task which was known to be running. Heuristic. Cf. #49464.
*/
private Task lastTask = null;
private synchronized Task getLastTask() {
return lastTask;
}
private synchronized void setLastTask(Task lastTask) {
this.lastTask = lastTask;
}
@SuppressWarnings("LeakingThisInConstructor")
NbBuildLogger(File origScript, OutputWriter out, OutputWriter err, int verbosity, String displayName, Map<String,String> properties,
Set<? extends String> concealedProperties, Runnable interestingOutputCallback, ProgressHandle handle, InputOutput io) {
thisSession = LoggerTrampoline.ANT_SESSION_CREATOR.makeAntSession(this);
this.origScript = origScript;
this.out = out;
this.err = err;
this.io = io;
this.verbosity = verbosity;
this.properties = properties;
this.concealedProperties = concealedProperties;
this.displayName = displayName;
this.interestingOutputCallback = interestingOutputCallback;
this.handle = handle;
LOG.log(Level.FINE, "---- Initializing build of {0} \"{1}\" at verbosity {2} ----", new Object[] {origScript, displayName, verbosity});
enterProtectedMode(isCompileOnSave(properties));
}
//where
private static boolean isCompileOnSave(final Map<String,String> properties) {
return "true".equals(properties.get("nb.wait.for.caches")); //NOI18N
}
private void enterProtectedMode(final boolean waitForScan) {
synchronized (protectedModeLock) {
if (protectedMode == null) {
protectedMode = IndexingBridge.getDefault().protectedMode(waitForScan);
}
}
}
private void exitProtectedMode() {
synchronized (protectedModeLock) {
if (protectedMode != null) {
protectedMode.release();
protectedMode = null;
}
}
}
/** Try to stop running at the next safe point. */
public void stop() {
stop = true;
}
/** Stop the build now if requested. Also restarts sleep timer. */
private void checkForStop() {
if (stop) {
StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(NbBuildLogger.class, "MSG_stopped", displayName));
throw new ThreadDeath();
}
if (running) {
handle.switchToIndeterminate();
enterProtectedMode(false);
sleepTask.schedule(SLEEP_DELAY);
}
}
/**
* Notify this process that it has been shut down.
* Refuse any further queries on AntEvent etc.
* @see "#71266"
*/
public void shutdown() {
running = false;
out.close();
err.close();
handle.finish();
sleepTask.cancel();
exitProtectedMode();
}
private void verifyRunning() {
if (!running && !insideToString.get()) {
throw new ThreadDeath(); // AntSession/AntEvent/TaskStructure method called after completion of Ant process
}
}
/**
* Compute a list of loggers to use for this session.
* Do not do it in the constructor since the actual targets will not have been
* set and some loggers may care about the targets. However if buildInitializationFailed
* is called before then, initialize them anyway.
*/
private synchronized void initInterestedLoggers() {
if (interestedLoggers == null) {
interestedLoggers = new ArrayList<AntLogger>();
for (AntLogger l : Lookup.getDefault().lookupAll(AntLogger.class)) {
if (l.interestedInSession(thisSession)) {
interestedLoggers.add(l);
}
}
LOG.log(Level.FINEST, "getInterestedLoggers: loggers={0}", interestedLoggers);
}
}
private synchronized Collection<AntLogger> getInterestedLoggers() {
initInterestedLoggers();
return new ArrayList<AntLogger>(interestedLoggers);
}
@SuppressWarnings({"unchecked", "rawtypes"}) // could use List<Collection<AntLogger>> but too slow?
private final Collection<AntLogger>[] interestedLoggersByVariousCriteria = new Collection[4];
private static final Comparator<Collection<AntLogger>> INTERESTED_LOGGERS_SORTER = new Comparator<Collection<AntLogger>>() {
public @Override int compare(Collection<AntLogger> c1, Collection<AntLogger> c2) {
int x = c1.size() - c2.size(); // reverse sort by size
if (x != 0) {
return x;
} else {
return System.identityHashCode(c1) - System.identityHashCode(c2);
}
}
};
/**
* Get those loggers interested in a given event.
*/
private Collection<AntLogger> getInterestedLoggersByEvent(AntEvent e) {
File scriptLocation = e.getScriptLocation();
String targetName = e.getTargetName();
String taskName = e.getTaskName();
int logLevel = e.getLogLevel();
synchronized (this) { // #132945: <parallel> can deadlock if you block on event info here
initInterestedLoggers();
// Start with the smallest one and go down.
interestedLoggersByVariousCriteria[0] = getInterestedLoggersByScript(scriptLocation);
interestedLoggersByVariousCriteria[1] = getInterestedLoggersByTarget(targetName);
interestedLoggersByVariousCriteria[2] = getInterestedLoggersByTask(taskName);
interestedLoggersByVariousCriteria[3] = getInterestedLoggersByLevel(logLevel);
Arrays.sort(interestedLoggersByVariousCriteria, INTERESTED_LOGGERS_SORTER);
LOG.log(Level.FINEST, "getInterestedLoggersByVariousCriteria: event={0} loggers={1}", new Object[] {e, Arrays.asList(interestedLoggersByVariousCriteria)});
// XXX could probably be even a bit more efficient by iterating on the fly...
// and by skipping the sorting which is probably overkill for a small number of a loggers (or hardcode the sort)
List<AntLogger> loggers = new LinkedList<AntLogger>(interestedLoggersByVariousCriteria[0]);
for (int i = 1; i < 4; i++) {
loggers.retainAll(interestedLoggersByVariousCriteria[i]);
}
LOG.log(Level.FINEST, "getInterestedLoggersByEvent: event={0} loggers={1}", new Object[] {e, loggers});
return loggers;
}
}
private synchronized Collection<AntLogger> getInterestedLoggersByScript(File script) {
initInterestedLoggers();
Collection<AntLogger> c = interestedLoggersByScript.get(script);
if (c == null) {
c = new LinkedHashSet<AntLogger>(interestedLoggers.size());
interestedLoggersByScript.put(script, c);
for (AntLogger l : interestedLoggers) {
if (l.interestedInAllScripts(thisSession) || (script != null && l.interestedInScript(script, thisSession))) {
c.add(l);
}
}
LOG.log(Level.FINEST, "getInterestedLoggersByScript: script={0} loggers={1}", new Object[] {script, c});
}
return c;
}
private synchronized Collection<AntLogger> getInterestedLoggersByTarget(String target) {
Collection<AntLogger> c = interestedLoggersByTarget.get(target);
if (c == null) {
c = new LinkedHashSet<AntLogger>(interestedLoggers.size());
interestedLoggersByTarget.put(target, c);
for (AntLogger l : interestedLoggers) {
String[] interestingTargets = l.interestedInTargets(thisSession);
if (interestingTargets == AntLogger.ALL_TARGETS ||
(target != null && Arrays.asList(interestingTargets).contains(target)) ||
(target == null && interestingTargets == AntLogger.NO_TARGETS)) {
c.add(l);
}
}
LOG.log(Level.FINEST, "getInterestedLoggersByTarget: target={0} loggers={1}", new Object[] {target, c});
}
return c;
}
private synchronized Collection<AntLogger> getInterestedLoggersByTask(String task) {
Collection<AntLogger> c = interestedLoggersByTask.get(task);
if (c == null) {
c = new LinkedHashSet<AntLogger>(interestedLoggers.size());
interestedLoggersByTask.put(task, c);
for (AntLogger l : interestedLoggers) {
String[] tasks = l.interestedInTasks(thisSession);
if (tasks == AntLogger.ALL_TASKS ||
(task != null && Arrays.asList(tasks).contains(task)) ||
(task == null && tasks == AntLogger.NO_TASKS)) {
c.add(l);
}
}
LOG.log(Level.FINEST, "getInterestedLoggersByTask: task={0} loggers={1}", new Object[] {task, c});
}
return c;
}
private synchronized Collection<AntLogger> getInterestedLoggersByLevel(int level) {
Collection<AntLogger> c = interestedLoggersByLevel.get(level);
if (c == null) {
c = new LinkedHashSet<AntLogger>(interestedLoggers.size());
interestedLoggersByLevel.put(level, c);
for (AntLogger l : interestedLoggers) {
if (level == -1) {
c.add(l);
} else {
int[] levels = l.interestedInLogLevels(thisSession);
for (int _level : levels) {
if (_level == level) {
c.add(l);
break;
}
}
}
}
LOG.log(Level.FINEST, "getInterestedLoggersByLevel: level={0} loggers={1}", new Object[] {level, c});
}
return c;
}
synchronized void setActualTargets(String[] targets) {
this.targets = targets;
}
void buildInitializationFailed(BuildException be) {
AntEvent ev = LoggerTrampoline.ANT_EVENT_CREATOR.makeAntEvent(new Event(be));
LOG.log(Level.FINE, "buildInitializationFailed: {0}", ev);
for (AntLogger l : getInterestedLoggersByScript(null)) {
l.buildInitializationFailed(ev);
}
interestingOutputCallback.run();
}
public @Override void buildStarted(BuildEvent ev) {
AntBridge.suspendDelegation();
try {
checkForStop();
AntEvent e = LoggerTrampoline.ANT_EVENT_CREATOR.makeAntEvent(new Event(ev, false));
LOG.log(Level.FINE, "buildStarted: {0}", e);
for (AntLogger l : getInterestedLoggers()) {
try {
l.buildStarted(e);
} catch (RuntimeException x) {
LOG.log(Level.WARNING, null, x);
}
}
} finally {
AntBridge.resumeDelegation();
}
}
public @Override void buildFinished(BuildEvent ev) {
AntBridge.suspendDelegation();
try {
// #82160: do not call checkForStop() here
stop = false; // do not throw ThreadDeath on messageLogged from BridgeImpl cleanup code
setLastTask(null);
AntEvent e = LoggerTrampoline.ANT_EVENT_CREATOR.makeAntEvent(new Event(ev, false));
LOG.log(Level.FINE, "buildFinished: {0}", e);
if (e.getException() != null) {
LOG.log(Level.FINE, null, e.getException());
}
for (AntLogger l : getInterestedLoggers()) {
try {
l.buildFinished(e);
} catch (RuntimeException x) {
LOG.log(Level.WARNING, null, x);
} catch (Error x) {
LOG.log(Level.WARNING, null, x);
}
}
if (e.getException() != null) {
interestingOutputCallback.run();
}
} finally {
AntBridge.resumeDelegation();
}
}
public @Override void targetStarted(BuildEvent ev) {
AntBridge.suspendDelegation();
try {
checkForStop();
setLastTask(null);
AntEvent e = LoggerTrampoline.ANT_EVENT_CREATOR.makeAntEvent(new Event(ev, false));
LOG.log(Level.FINE, "targetStarted: {0}", e);
for (AntLogger l : getInterestedLoggersByEvent(e)) {
try {
l.targetStarted(e);
} catch (RuntimeException x) {
LOG.log(Level.WARNING, null, x);
}
}
// Update progress handle label so user can see what is being run.
Project p = ev.getProject();
String projectName = null;
if (p != null) {
projectName = p.getName();
}
String targetName = e.getTargetName();
if (targetName != null) {
String message;
if (projectName != null) {
message = NbBundle.getMessage(NbBuildLogger.class, "MSG_progress_target", projectName, targetName);
} else {
message = targetName;
}
/*
if (message.equals(displayName)) {
// Redundant in this case.
message = "";
}
*/
handle.progress(message);
}
} finally {
AntBridge.resumeDelegation();
}
}
public @Override void targetFinished(BuildEvent ev) {
AntBridge.suspendDelegation();
try {
checkForStop();
setLastTask(null);
AntEvent e = LoggerTrampoline.ANT_EVENT_CREATOR.makeAntEvent(new Event(ev, false));
LOG.log(Level.FINE, "targetFinished: {0}", e);
for (AntLogger l : getInterestedLoggersByEvent(e)) {
try {
l.targetFinished(e);
} catch (RuntimeException x) {
LOG.log(Level.WARNING, null, x);
}
}
} finally {
AntBridge.resumeDelegation();
}
}
public @Override void taskStarted(BuildEvent ev) {
AntBridge.suspendDelegation();
try {
checkForStop();
setLastTask(ev.getTask());
AntEvent e = LoggerTrampoline.ANT_EVENT_CREATOR.makeAntEvent(new Event(ev, false));
LOG.log(Level.FINE, "taskStarted: {0}", e);
for (AntLogger l : getInterestedLoggersByEvent(e)) {
try {
l.taskStarted(e);
} catch (RuntimeException x) {
LOG.log(Level.WARNING, null, x);
}
}
if ("input".equals(e.getTaskName())) { // #81139; NOI18N
TaskStructure s = e.getTaskStructure();
if (s != null) {
String def = s.getAttribute("defaultvalue"); // NOI18N
if (def != null) {
NbInputHandler.setDefaultValue(e.evaluate(def));
}
}
}
if (isRunTask(e)) {
insideRunTask = true;
}
} finally {
AntBridge.resumeDelegation();
}
}
private boolean isRunTask(AntEvent event) { // #95201
String taskName = event.getTaskName();
return "java".equals(taskName) || "exec".equals(taskName); // NOI18N
}
public @Override void taskFinished(BuildEvent ev) {
AntBridge.suspendDelegation();
try {
checkForStop();
setLastTask(null);
AntEvent e = LoggerTrampoline.ANT_EVENT_CREATOR.makeAntEvent(new Event(ev, false));
LOG.log(Level.FINE, "taskFinished: {0}", e);
for (AntLogger l : getInterestedLoggersByEvent(e)) {
try {
l.taskFinished(e);
} catch (RuntimeException x) {
LOG.log(Level.WARNING, null, x);
}
}
NbInputHandler.setDefaultValue(null);
if (isRunTask(e)) {
insideRunTask = false;
}
} finally {
AntBridge.resumeDelegation();
}
}
/**
* Pattern matching an Ant message logged when it is parsing a build script.
* Hack for lack of Target.getLocation() in Ant 1.6.2 and earlier.
* Captured groups:
* <ol>
* <li>absolute path of build script
* </ol>
*/
private static final Pattern PARSING_BUILDFILE_MESSAGE =
Pattern.compile("parsing buildfile (.+) with URI = (?:.+)"); // NOI18N
/**
* Pattern matching an Ant message logged when it is importing a build script.
* Hack for lack of Target.getLocation() in Ant 1.6.2 and earlier.
* Captured groups:
* <ol>
* <li>absolute path of build script which is doing the importing
* </ol>
*/
private static final Pattern IMPORTING_FILE_MESSAGE =
Pattern.compile("Importing file (?:.+) from (.+)"); // NOI18N
/**
* Pattern matching an Ant message logged when it has encountered a target in some build script.
* Hack for lack of Target.getLocation() in Ant 1.6.2 and earlier.
* Captured groups:
* <ol>
* <li>target name
* </ol>
*/
private static final Pattern PARSED_TARGET_MESSAGE =
Pattern.compile(" \\+Target: (.+)"); // NOI18N
public @Override void messageLogged(BuildEvent ev) {
if (!running) { // #145722
return;
}
AntBridge.suspendDelegation();
try {
checkForStop();
AntEvent e = LoggerTrampoline.ANT_EVENT_CREATOR.makeAntEvent(new Event(ev, true));
LOG.log(Level.FINER, "messageLogged: {0}", e);
for (AntLogger l : getInterestedLoggersByEvent(e)) {
try {
l.messageLogged(e);
} catch (RuntimeException x) {
LOG.log(Level.WARNING, null, x);
}
}
// Let the hacks begin!
String msg = ev.getMessage();
if (msg.contains("ant.PropertyHelper") || /* #71816 */ msg.contains("ant.projectHelper")) { // NOI18N
// Only after this has been defined can we get any properties.
// Even trying earlier will give a recursion error since this pseudoprop
// is set lazily, which produces a new message logged event.
projectsWithProperties.add(ev.getProject());
}
if (targetGetLocation == null) {
// Try to figure out which imported targets belong to which actual scripts.
// XXX consider keeping a singleton Matcher for each pattern and reusing it
// or just doing string comparisons
Matcher matcher;
if ((matcher = PARSING_BUILDFILE_MESSAGE.matcher(msg)).matches()) {
if (currentlyParsedMainScript != null) {
currentlyParsedImportedScript = matcher.group(1);
}
LOG.log(Level.FINE, "Got PARSING_BUILDFILE_MESSAGE: {0}", currentlyParsedImportedScript);
setLastTask(null);
} else if ((matcher = IMPORTING_FILE_MESSAGE.matcher(msg)).matches()) {
currentlyParsedMainScript = matcher.group(1);
currentlyParsedImportedScript = null;
LOG.log(Level.FINE, "Got IMPORTING_FILE_MESSAGE: {0}", currentlyParsedMainScript);
setLastTask(null);
} else if ((matcher = PARSED_TARGET_MESSAGE.matcher(msg)).matches()) {
if (currentlyParsedMainScript != null && currentlyParsedImportedScript != null) {
Map<String,String> targetLocations = knownImportedTargets.get(currentlyParsedMainScript);
if (targetLocations == null) {
targetLocations = new HashMap<String,String>();
knownImportedTargets.put(currentlyParsedMainScript, targetLocations);
}
targetLocations.put(matcher.group(1), currentlyParsedImportedScript);
}
LOG.log(Level.FINE, "Got PARSED_TARGET_MESSAGE: {0}", matcher.group(1));
setLastTask(null);
}
}
} finally {
AntBridge.resumeDelegation();
}
}
public @Override File getOriginatingScript() {
verifyRunning();
return origScript;
}
public @Override String[] getOriginatingTargets() {
verifyRunning();
return targets != null ? targets : new String[0];
}
public @Override synchronized Object getCustomData(AntLogger logger) {
verifyRunning();
return customData.get(logger);
}
public @Override synchronized void putCustomData(AntLogger logger, Object data) {
verifyRunning();
customData.put(logger, data);
}
public @Override void println(String message, boolean error, OutputListener listener) {
verifyRunning();
LOG.log(Level.FINEST, "println: error={0} listener={1} message={2}", new Object[] {error, listener, message});
OutputWriter ow = error ? err : out;
try {
if (listener != null) {
// Loggers wishing for more control can use getIO and do it themselves.
boolean important = StandardLogger.isImportant(message);
ow.println(message, listener, important);
interestingOutputCallback.run();
} else {
ow.println(message);
}
} catch (IOException e) {
LOG.log(Level.WARNING, null, e);
}
}
public @Override void deliverMessageLogged(AntEvent originalEvent, String message, int level) {
verifyRunning();
if (originalEvent == null) {
throw new IllegalArgumentException("Must pass an original event to deliverMessageLogged"); // NOI18N
}
if (message == null) {
throw new IllegalArgumentException("Must pass a message to deliverMessageLogged"); // NOI18N
}
if (level < AntEvent.LOG_ERR || level > AntEvent.LOG_DEBUG) {
throw new IllegalArgumentException("Unknown log level for reposted log event: " + level); // NOI18N
}
LOG.log(Level.FINEST, "deliverMessageLogged: level={0} message={1}", new Object[] {level, message});
AntEvent newEvent = LoggerTrampoline.ANT_EVENT_CREATOR.makeAntEvent(new RepostedEvent(originalEvent, message, level));
for (AntLogger l : getInterestedLoggersByEvent(newEvent)) {
try {
l.messageLogged(newEvent);
} catch (RuntimeException x) {
LOG.log(Level.WARNING, null, x);
}
}
}
public @Override synchronized void consumeException(Throwable t) throws IllegalStateException {
verifyRunning();
if (isExceptionConsumed(t)) {
throw new IllegalStateException("Already consumed " + t); // NOI18N
}
consumedExceptions.add(t);
}
public @Override synchronized boolean isExceptionConsumed(Throwable t) {
verifyRunning();
if (consumedExceptions.contains(t)) {
return true;
}
// Check for nested exceptions too.
Throwable nested = t.getCause();
if (nested != null && isExceptionConsumed(nested)) {
// cache that
consumedExceptions.add(t);
return true;
}
return false;
}
public @Override int getVerbosity() {
verifyRunning();
return verbosity;
}
@Override public Map<String, String> getProperties() {
return Collections.unmodifiableMap(properties);
}
@Override
public boolean isConcealed(@NonNull final String propertyName) {
Parameters.notNull("propertyName", propertyName); //NOI18N
return concealedProperties.contains(propertyName);
}
String getDisplayNameNoLock() {
return displayName;
}
public @Override String getDisplayName() {
verifyRunning();
return displayName;
}
public @Override OutputListener createStandardHyperlink(URL file, String message, int line1, int column1, int line2, int column2) {
verifyRunning();
return new Hyperlink(file, message, line1, column1, line2, column2);
}
public @Override InputOutput getIO() {
return io;
}
// Accessors for stuff which is specific to particular versions of Ant.
private static final Method targetGetLocation; // 1.6.2+
private static final Method locationGetFileName; // 1.6+
private static final Method locationGetLineNumber; // 1.6+
private static final Method runtimeConfigurableGetAttributeMap; // 1.6+
private static final Method runtimeConfigurableGetChildren; // 1.6+
private static final Method runtimeConfigurableGetText; // 1.6+
static {
Method _targetGetLocation = null;
try {
_targetGetLocation = Target.class.getMethod("getLocation"); // NOI18N
if (AntBridge.getInterface().getAntVersion().indexOf("1.6.2") != -1) { // NOI18N
// Unfortunately in 1.6.2 the method exists but it doesn't work (Ant #28599):
_targetGetLocation = null;
}
} catch (NoSuchMethodException e) {
// OK
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
targetGetLocation = _targetGetLocation;
Method _locationGetFileName = null;
try {
_locationGetFileName = Location.class.getMethod("getFileName"); // NOI18N
} catch (NoSuchMethodException e) {
// OK
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
locationGetFileName = _locationGetFileName;
Method _locationGetLineNumber = null;
try {
_locationGetLineNumber = Location.class.getMethod("getLineNumber"); // NOI18N
} catch (NoSuchMethodException e) {
// OK
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
locationGetLineNumber = _locationGetLineNumber;
Method _runtimeConfigurableGetAttributeMap = null;
try {
_runtimeConfigurableGetAttributeMap = RuntimeConfigurable.class.getMethod("getAttributeMap"); // NOI18N
} catch (NoSuchMethodException e) {
// OK
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
runtimeConfigurableGetAttributeMap = _runtimeConfigurableGetAttributeMap;
Method _runtimeConfigurableGetChildren = null;
try {
_runtimeConfigurableGetChildren = RuntimeConfigurable.class.getMethod("getChildren"); // NOI18N
} catch (NoSuchMethodException e) {
// OK
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
runtimeConfigurableGetChildren = _runtimeConfigurableGetChildren;
Method _runtimeConfigurableGetText = null;
try {
_runtimeConfigurableGetText = RuntimeConfigurable.class.getMethod("getText"); // NOI18N
} catch (NoSuchMethodException e) {
// OK
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
runtimeConfigurableGetText = _runtimeConfigurableGetText;
}
/**
* Try to find the location of an Ant target.
* @param project if not null, the main project from which this target might have been imported
*/
private Location getLocationOfTarget(Target target, Project project) {
if (targetGetLocation != null) {
try {
return (Location) targetGetLocation.invoke(target);
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
}
// For Ant 1.6.2 and earlier, hope we got the right info from the hacks above.
LOG.log(Level.FINEST, "knownImportedTargets: {0}", knownImportedTargets);
if (project != null) {
String file = project.getProperty("ant.file"); // NOI18N
if (file != null) {
Map<String,String> targetLocations = knownImportedTargets.get(file);
if (targetLocations != null) {
String importedFile = targetLocations.get(target.getName());
if (importedFile != null) {
// Have no line number, note.
return new Location(importedFile);
}
}
}
}
// Dunno.
return null;
}
private static String getFileNameOfLocation(Location loc) {
if (locationGetFileName != null) {
try {
return (String) locationGetFileName.invoke(loc);
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
}
// OK, using Ant 1.5.x.
String locs = loc.toString();
// Format: "$file:$line: " or "$file: " or ""
int x = locs.indexOf(':');
if (x != -1) {
return locs.substring(0, x);
} else {
return null;
}
}
private static int getLineNumberOfLocation(Location loc) {
if (locationGetLineNumber != null) {
try {
return (Integer) locationGetLineNumber.invoke(loc);
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
}
// OK, using Ant 1.5.x.
String locs = loc.toString();
// Format: "$file:$line: " or "$file: " or ""
int x = locs.indexOf(':');
if (x != -1) {
int x2 = locs.indexOf(':', x + 1);
if (x2 != -1) {
String line = locs.substring(x + 1, x2);
try {
return Integer.parseInt(line);
} catch (NumberFormatException e) {
// ignore?
}
}
}
return 0;
}
private static Map<String,String> getAttributeMapOfRuntimeConfigurable(RuntimeConfigurable rc) {
Map<String, String> m = new HashMap<String, String>();
if (runtimeConfigurableGetAttributeMap != null) {
try {
for (Map.Entry<?,?> entry : ((Map<?,?>) runtimeConfigurableGetAttributeMap.invoke(rc)).entrySet()) {
m.put(((String) entry.getKey()).toLowerCase(Locale.ENGLISH), (String) entry.getValue());
}
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
}
return m;
}
@SuppressWarnings("unchecked")
private static Enumeration<RuntimeConfigurable> getChildrenOfRuntimeConfigurable(RuntimeConfigurable rc) {
if (runtimeConfigurableGetChildren != null) {
try {
return (Enumeration<RuntimeConfigurable>) runtimeConfigurableGetChildren.invoke(rc);
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
}
return Collections.enumeration(Collections.<RuntimeConfigurable>emptySet());
}
private static String getTextOfRuntimeConfigurable(RuntimeConfigurable rc) {
if (runtimeConfigurableGetText != null) {
try {
return ((StringBuffer) runtimeConfigurableGetText.invoke(rc)).toString();
} catch (Exception e) {
LOG.log(Level.WARNING, null, e);
}
}
return "";
}
/**
* Standard event implementation, delegating to the Ant BuildEvent and Project.
*/
private final class Event implements LoggerTrampoline.AntEventImpl {
private boolean consumed = false;
private final BuildEvent e;
private final Throwable exception;
private final int level;
private File scriptLocation;
/**
* Create a new regular event.
* @param e the Ant build event
* @param msgLogged true for logged events
*/
public Event(BuildEvent e, boolean msgLogged) {
this.e = e;
exception = e.getException();
if (msgLogged) {
level = e.getPriority();
} else {
level = -1;
}
}
/**
* Create a new event for buildInitializationFailed.
* @param exception the problem
*/
public Event(Throwable exception) {
e = null;
this.exception = exception;
level = -1;
}
public @Override AntSession getSession() {
verifyRunning();
return thisSession;
}
public @Override void consume() throws IllegalStateException {
verifyRunning();
if (consumed) {
throw new IllegalStateException("Event already consumed"); // NOI18N
}
consumed = true;
}
public @Override boolean isConsumed() {
verifyRunning();
return consumed;
}
public @Override File getScriptLocation() {
verifyRunning();
if (scriptLocation != null) {
return scriptLocation;
}
if (e == null) {
return null;
}
Task task = e.getTask();
if (task != null) {
Location l = task.getLocation();
if (l != null) {
String file = getFileNameOfLocation(l);
if (file != null) {
return scriptLocation = new File(file);
}
}
}
Target target = e.getTarget();
Project project = getProjectIfPropertiesDefined();
if (target != null) {
Location l = getLocationOfTarget(target, project);
if (l != null) {
String file = getFileNameOfLocation(l);
if (file != null) {
return scriptLocation = new File(file);
}
}
}
// #49464: guess at task.
Task lastTask = getLastTask();
if (lastTask != null) {
Location l = lastTask.getLocation();
if (l != null) {
String file = getFileNameOfLocation(l);
if (file != null) {
return scriptLocation = new File(file);
}
}
}
// #104103: lastTask is more likely to be accurate.
// Consider a call to Project.log from within a task run in an imported script.
if (project != null) {
String file = project.getProperty("ant.file"); // NOI18N
if (file != null) {
return scriptLocation = new File(file);
}
}
// #57153 suggests using SubBuildListener, but is it really necessary?
return null;
}
private Project getProjectIfPropertiesDefined() {
Project project = e.getProject();
if (project != null && projectsWithProperties.contains(project)) {
return project;
} else {
return null;
}
}
public @Override int getLine() {
verifyRunning();
if (e == null) {
return -1;
}
Task task = e.getTask();
if (task != null) {
Location l = task.getLocation();
if (l != null) {
int line = getLineNumberOfLocation(l);
if (line > 0) {
return line;
}
}
}
Target target = e.getTarget();
if (target != null) {
Location l = getLocationOfTarget(target, getProjectIfPropertiesDefined());
if (l != null) {
int line = getLineNumberOfLocation(l);
if (line > 0) {
return line;
}
}
}
// #49464: guess at task.
Task lastTask = getLastTask();
if (lastTask != null) {
Location l = lastTask.getLocation();
if (l != null) {
int line = getLineNumberOfLocation(l);
if (line > 0) {
return line;
}
}
}
return -1;
}
public @Override String getTargetName() {
verifyRunning();
if (e == null) {
return null;
}
Target target = e.getTarget();
if (target != null) {
String name = target.getName();
if (name != null && name.length() > 0) {
return name;
}
}
// #49464: guess at task.
Task lastTask = getLastTask();
if (lastTask != null) {
target = lastTask.getOwningTarget();
if (target != null) {
String name = target.getName();
if (name != null && name.length() > 0) {
return name;
}
}
}
return null;
}
public @Override String getTaskName() {
verifyRunning();
if (e == null) {
return null;
}
Task task = e.getTask();
if (task != null) {
return task.getRuntimeConfigurableWrapper().getElementTag();
}
// #49464: guess at task.
Task lastTask = getLastTask();
if (lastTask != null) {
return lastTask.getRuntimeConfigurableWrapper().getElementTag();
}
return null;
}
public @Override TaskStructure getTaskStructure() {
verifyRunning();
Task task = e.getTask();
if (task != null) {
return LoggerTrampoline.TASK_STRUCTURE_CREATOR.makeTaskStructure(new TaskStructureImpl(task.getRuntimeConfigurableWrapper()));
}
// #49464: guess at task.
Task lastTask = getLastTask();
if (lastTask != null) {
return LoggerTrampoline.TASK_STRUCTURE_CREATOR.makeTaskStructure(new TaskStructureImpl(lastTask.getRuntimeConfigurableWrapper()));
}
return null;
}
public @Override String getMessage() {
verifyRunning();
if (e == null) {
return null;
}
return e.getMessage();
}
public @Override int getLogLevel() {
verifyRunning();
return level;
}
public @Override Throwable getException() {
verifyRunning();
return exception;
}
public @Override String getProperty(String name) {
verifyRunning();
Project project = getProjectIfPropertiesDefined();
if (project != null) {
String v = project.getProperty(name);
if (v != null) {
return v;
} else {
Object o = project.getReference(name);
if (o != null) {
return o.toString();
} else {
return null;
}
}
} else {
return null;
}
}
public @Override Set<String> getPropertyNames() {
verifyRunning();
Project project = getProjectIfPropertiesDefined();
if (project != null) {
Set<String> s = new HashSet<String>();
s.addAll(NbCollections.checkedSetByFilter(project.getProperties().keySet(), String.class, true));
s.addAll(NbCollections.checkedSetByFilter(project.getReferences().keySet(), String.class, true));
return s;
} else {
return Collections.emptySet();
}
}
public @Override String evaluate(String text) {
verifyRunning();
Project project = getProjectIfPropertiesDefined();
if (project != null) {
return project.replaceProperties(text);
} else {
return text;
}
}
@Override
public String toString() {
assert !insideToString.get();
insideToString.set(true);
try {
StringBuilder b = new StringBuilder("Event"); // NOI18N
String s = getTargetName();
if (s != null) {
b.append(";targ=").append(s); // NOI18N
}
s = getTaskName();
if (s != null) {
b.append(";task=").append(s); // NOI18N
}
if (exception != null) {
b.append(";exc=").append(exception); // NOI18N
}
if (level != -1) {
b.append(";lvl=").append(LEVEL_NAMES[level]); // NOI18N
}
if (consumed) {
b.append(";consumed"); // NOI18N
}
s = getMessage();
if (s != null) {
b.append(";msg=").append(s); // NOI18N
}
File f = getScriptLocation();
if (f != null) {
b.append(";scrLoc=").append(f); // NOI18N
}
return b.toString();
} finally {
insideToString.set(false);
}
}
}
private static final String[] LEVEL_NAMES = {"ERR", "WARN", "INFO", "VERBOSE", "DEBUG"}; // NOI18N
/**
* Reposted event delegating to an original one except for message and level.
* @see #deliverMessageLogged
*/
private final class RepostedEvent implements LoggerTrampoline.AntEventImpl {
private final AntEvent originalEvent;
private final String message;
private final int level;
private boolean consumed = false;
public RepostedEvent(AntEvent originalEvent, String message, int level) {
this.originalEvent = originalEvent;
this.message = message;
this.level = level;
}
public @Override void consume() throws IllegalStateException {
verifyRunning();
if (consumed) {
throw new IllegalStateException("Event already consumed"); // NOI18N
}
consumed = true;
}
public @Override boolean isConsumed() {
verifyRunning();
return consumed;
}
public @Override AntSession getSession() {
return originalEvent.getSession();
}
public @Override File getScriptLocation() {
return originalEvent.getScriptLocation();
}
public @Override int getLine() {
return originalEvent.getLine();
}
public @Override String getTargetName() {
return originalEvent.getTargetName();
}
public @Override String getTaskName() {
return originalEvent.getTaskName();
}
public @Override TaskStructure getTaskStructure() {
return originalEvent.getTaskStructure();
}
public @Override String getMessage() {
verifyRunning();
return message;
}
public @Override int getLogLevel() {
verifyRunning();
return level;
}
public @Override Throwable getException() {
verifyRunning();
return null;
}
public @Override String getProperty(String name) {
return originalEvent.getProperty(name);
}
public @Override Set<String> getPropertyNames() {
return originalEvent.getPropertyNames();
}
public @Override String evaluate(String text) {
return originalEvent.evaluate(text);
}
@Override
public String toString() {
return "RepostedEvent[consumed=" + consumed + ",level=" + level + ",message=" + message + /*",orig=" + originalEvent +*/ "]"; // NOI18N
}
}
/**
* Implementation of TaskStructure based on an Ant Task.
* @see Event#getTaskStructure
*/
private final class TaskStructureImpl implements LoggerTrampoline.TaskStructureImpl {
private final RuntimeConfigurable rc;
public TaskStructureImpl(RuntimeConfigurable rc) {
this.rc = rc;
}
public @Override String getName() {
verifyRunning();
String name = rc.getElementTag();
if (name != null) {
return name;
} else {
// What does this mean?
return "";
}
}
public @Override String getAttribute(String name) {
verifyRunning();
return getAttributeMapOfRuntimeConfigurable(rc).get(name.toLowerCase(Locale.ENGLISH));
}
public @Override Set<String> getAttributeNames() {
verifyRunning();
return getAttributeMapOfRuntimeConfigurable(rc).keySet();
}
public @Override String getText() {
verifyRunning();
String s = getTextOfRuntimeConfigurable(rc);
if (s.length() > 0) {
// XXX is it appropriate to trim() this? probably not
return s;
} else {
return null;
}
}
public @Override TaskStructure[] getChildren() {
verifyRunning();
List<TaskStructure> structures = new ArrayList<TaskStructure>();
for (RuntimeConfigurable subrc : NbCollections.iterable(getChildrenOfRuntimeConfigurable(rc))) {
structures.add(LoggerTrampoline.TASK_STRUCTURE_CREATOR.makeTaskStructure(new TaskStructureImpl(subrc)));
}
return structures.toArray(new TaskStructure[structures.size()]);
}
}
}