| /* |
| * 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.beans.Introspector; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintStream; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.net.URL; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Vector; |
| import java.util.WeakHashMap; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DemuxOutputStream; |
| import org.apache.tools.ant.IntrospectionHelper; |
| import org.apache.tools.ant.Main; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.ProjectHelper; |
| import org.apache.tools.ant.module.AntModule; |
| import org.apache.tools.ant.module.AntSettings; |
| import org.apache.tools.ant.module.api.IntrospectedInfo; |
| import org.apache.tools.ant.module.bridge.AntBridge; |
| import org.apache.tools.ant.module.bridge.BridgeInterface; |
| import org.apache.tools.ant.module.bridge.IntrospectionHelperProxy; |
| import org.apache.tools.ant.types.EnumeratedAttribute; |
| import org.apache.tools.ant.types.Path; |
| import org.netbeans.api.progress.ProgressHandle; |
| import org.openide.ErrorManager; |
| import org.openide.awt.StatusDisplayer; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| import org.openide.util.NbCollections; |
| import org.openide.util.RequestProcessor; |
| import org.openide.windows.InputOutput; |
| import org.openide.windows.OutputWriter; |
| |
| /** |
| * Implements the BridgeInterface using the current version of Ant. |
| * @author Jesse Glick |
| */ |
| public class BridgeImpl implements BridgeInterface { |
| |
| private static final Logger LOG = Logger.getLogger(BridgeImpl.class.getName()); |
| |
| private static final RequestProcessor RP = new RequestProcessor(BridgeImpl.class); |
| |
| /** Number of milliseconds to wait before forcibly halting a runaway process. */ |
| private static final int STOP_TIMEOUT = 10000; |
| |
| private final AtomicBoolean classpathInitialized = new AtomicBoolean(); |
| |
| /** |
| * Index of loggers by active thread. |
| * @see #stop |
| */ |
| private static final Map<Thread,NbBuildLogger> loggersByThread = new WeakHashMap<Thread,NbBuildLogger>(); |
| |
| public BridgeImpl() { |
| } |
| |
| public String getAntVersion() { |
| try { |
| return Main.getAntVersion(); |
| } catch (BuildException be) { |
| AntModule.err.notify(ErrorManager.INFORMATIONAL, be); |
| return NbBundle.getMessage(BridgeImpl.class, "LBL_ant_version_unknown"); |
| } |
| } |
| |
| public boolean isAnt16() { |
| try { |
| Class.forName("org.apache.tools.ant.taskdefs.Antlib"); // NOI18N |
| return true; |
| } catch (ClassNotFoundException e) { |
| // Fine, 1.5 |
| return false; |
| } |
| } |
| |
| public IntrospectionHelperProxy getIntrospectionHelper(Class<?> clazz) { |
| return new IntrospectionHelperImpl(clazz); |
| } |
| |
| public boolean toBoolean(String val) { |
| return Project.toBoolean(val); |
| } |
| |
| public String[] getEnumeratedValues(Class<?> c) { |
| if (EnumeratedAttribute.class.isAssignableFrom(c)) { |
| try { |
| return ((EnumeratedAttribute)c.newInstance()).getValues(); |
| } catch (Exception e) { |
| AntModule.err.notify(ErrorManager.INFORMATIONAL, e); |
| } |
| } else if (Enum.class.isAssignableFrom(c)) { // Ant 1.7.0 (#41058) |
| try { |
| Enum<?>[] vals = (Enum<?>[]) c.getMethod("values").invoke(null); |
| String[] names = new String[vals.length]; |
| for (int i = 0; i < vals.length; i++) { |
| names[i] = vals[i].name(); |
| } |
| return names; |
| } catch (Exception x) { |
| Exceptions.printStackTrace(x); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean run(File buildFile, List<String> targets, InputStream in, OutputWriter out, OutputWriter err, Map<String,String> properties, |
| Set<? extends String> concealedProperties, int verbosity, String displayName, Runnable interestingOutputCallback, ProgressHandle handle, InputOutput io) { |
| if (classpathInitialized.compareAndSet(false, true)) { |
| // #46171: Ant expects this path to have itself and whatever else you loaded with it, |
| // or AntClassLoader.getResources will not be able to find anything in the Ant loader. |
| //Proabably not needed anymore: https://bz.apache.org/bugzilla/show_bug.cgi?id=30161 |
| Path.systemClasspath = new Path(null, AntBridge.getMainClassPath()); |
| } |
| |
| boolean ok = false; |
| |
| // Important for various other stuff. |
| final boolean ant16 = isAnt16(); |
| |
| // Make sure "main Ant loader" is used as context loader for duration of the |
| // run. Otherwise some code, e.g. JAXP, will accidentally pick up NB classes, |
| // which can cause various undesirable effects. |
| ClassLoader oldCCL = Thread.currentThread().getContextClassLoader(); |
| ClassLoader newCCL = Project.class.getClassLoader(); |
| LOG.log(Level.FINER, "Fixing CCL: {0} -> {1}", new Object[] {oldCCL, newCCL}); |
| Thread.currentThread().setContextClassLoader(newCCL); |
| try { |
| |
| final Project project; |
| |
| // first use the ProjectHelper to create the project object |
| // from the given build file. |
| final NbBuildLogger logger = new NbBuildLogger(buildFile, out, err, verbosity, displayName, properties, concealedProperties, interestingOutputCallback, handle, io); |
| Vector<String> targs; |
| try { |
| project = new Project(); |
| project.addBuildListener(logger); |
| project.init(); |
| project.addTaskDefinition("java", ForkedJavaOverride.class); // #56341 |
| project.addTaskDefinition("input", InputOverride.class); // #155056 |
| try { |
| addCustomDefs(project); |
| } catch (IOException e) { |
| throw new BuildException(e); |
| } |
| project.setUserProperty("ant.file", buildFile.getAbsolutePath()); // NOI18N |
| // #14993: |
| project.setUserProperty("ant.version", Main.getAntVersion()); // NOI18N |
| File antHome = AntSettings.getAntHome(); |
| if (antHome != null) { |
| project.setUserProperty("ant.home", antHome.getAbsolutePath()); // NOI18N |
| } |
| String ENABLE_TESTLISTENER_EVENTS = "ant.junit.enabletestlistenerevents"; // NOI18N; since 1.8.2 in JUnitTask |
| project.setProperty(ENABLE_TESTLISTENER_EVENTS, "true"); // NOI18N |
| for (Map.Entry<String,String> entry : properties.entrySet()) { |
| project.setUserProperty(entry.getKey(), entry.getValue()); |
| } |
| if (in != null && ant16) { |
| try { |
| Method m = Project.class.getMethod("setDefaultInputStream", InputStream.class); // NOI18N |
| m.invoke(project, in); |
| } catch (Exception e) { |
| AntModule.err.notify(ErrorManager.INFORMATIONAL, e); |
| } |
| } |
| LOG.log(Level.FINER, "CCL when configureProject is called: {0}", Thread.currentThread().getContextClassLoader()); |
| ProjectHelper projhelper = ProjectHelper.getProjectHelper(); |
| // Cf. Ant #32668 & #32216; ProjectHelper.configureProject undeprecated in 1.7 |
| project.addReference("ant.projectHelper", projhelper); // NOI18N |
| projhelper.parse(project, buildFile); |
| |
| project.setInputHandler(new NbInputHandler(interestingOutputCallback)); |
| |
| if (targets != null) { |
| targs = new Vector<String>(targets); |
| } else { |
| targs = new Vector<String>(1); |
| targs.add(project.getDefaultTarget()); |
| } |
| logger.setActualTargets(targets != null ? targets.toArray(new String[targets.size()]) : null); |
| } |
| catch (BuildException be) { |
| logger.buildInitializationFailed(be); |
| logger.shutdown(); |
| if (in != null) { |
| try { |
| in.close(); |
| } catch (IOException e) { |
| AntModule.err.notify(e); |
| } |
| } |
| return false; |
| } |
| |
| project.fireBuildStarted(); |
| |
| // Save & restore system output streams. |
| InputStream is = System.in; |
| if (in != null && ant16) { |
| try { |
| Class<? extends InputStream> dis = Class.forName("org.apache.tools.ant.DemuxInputStream").asSubclass(InputStream.class); // NOI18N |
| Constructor<? extends InputStream> c = dis.getConstructor(Project.class); |
| is = c.newInstance(project); |
| } catch (Exception e) { |
| AntModule.err.notify(ErrorManager.INFORMATIONAL, e); |
| } |
| } |
| AntBridge.pushSystemInOutErr(is, |
| new PrintStream(new DemuxOutputStream(project, false)), |
| new PrintStream(new DemuxOutputStream(project, true))); |
| |
| Thread currentThread = Thread.currentThread(); |
| synchronized (loggersByThread) { |
| assert !loggersByThread.containsKey(currentThread); |
| loggersByThread.put(currentThread, logger); |
| } |
| try { |
| if (Thread.interrupted()) { |
| logger.shutdown(); |
| return false; |
| } |
| // Execute the configured project |
| //writer.println("#4"); // NOI18N |
| project.executeTargets(targs); |
| //writer.println("#5"); // NOI18N |
| project.fireBuildFinished(null); |
| ok = true; |
| } catch (Throwable t) { |
| // Really need to catch everything, else AntClassLoader.cleanup may |
| // not be called, resulting in a memory leak and/or locked JARs (#42431). |
| project.fireBuildFinished(t); |
| } finally { |
| AntBridge.restoreSystemInOutErr(); |
| if (in != null) { |
| try { |
| in.close(); |
| } catch (IOException e) { |
| AntModule.err.notify(e); |
| } |
| } |
| synchronized (loggersByThread) { |
| loggersByThread.remove(currentThread); |
| } |
| } |
| |
| // Now check to see if the Project defined any cool new custom tasks. |
| RP.post(new PostRun(project, logger)); |
| |
| } finally { |
| LOG.log(Level.FINER, "Restoring CCL: {0}", oldCCL); |
| Thread.currentThread().setContextClassLoader(oldCCL); |
| } |
| |
| return ok; |
| } |
| |
| private static final RequestProcessor.Task refreshFilesystemsTask = RP.create(new Runnable() { |
| public @Override void run() { |
| LOG.log(Level.FINE, "Refreshing filesystems"); |
| FileUtil.refreshAll(); |
| } |
| }); |
| |
| public void stop(final Thread process) { |
| NbBuildLogger logger; |
| synchronized (loggersByThread) { |
| logger = loggersByThread.get(process); |
| } |
| if (logger != null) { |
| // Try stopping at a safe point. |
| StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(BridgeImpl.class, "MSG_stopping", logger.getDisplayNameNoLock())); |
| logger.stop(); |
| } |
| process.interrupt(); |
| // But if that doesn't do it, double-check later... |
| // Yes Thread.stop() is deprecated; that is why we try to avoid using it. |
| RP.create(new StopProcess(process)).schedule(STOP_TIMEOUT); |
| } |
| private static class StopProcess implements Runnable { |
| private final Thread process; |
| StopProcess(Thread process) { |
| this.process = process; |
| } |
| public @Override void run() { |
| if (process.isAlive()) { |
| StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(BridgeImpl.class, "MSG_halting")); |
| stopThread(); |
| } |
| } |
| @SuppressWarnings("deprecation") |
| private void stopThread() { |
| process.stop(); |
| } |
| } |
| |
| private static void addCustomDefs(Project project) throws BuildException, IOException { |
| long start = System.currentTimeMillis(); |
| if (AntBridge.getInterface().isAnt16()) { |
| Map<String,ClassLoader> antlibLoaders = AntBridge.getCustomDefClassLoaders(); |
| for (Map.Entry<String,ClassLoader> entry : antlibLoaders.entrySet()) { |
| String cnb = entry.getKey(); |
| ClassLoader l = entry.getValue(); |
| String resource = cnb.replace('.', '/') + "/antlib.xml"; // NOI18N |
| URL antlib = l.getResource(resource); |
| if (antlib == null) { |
| throw new IOException("Could not find " + resource + " in ant/nblib/" + cnb.replace('.', '-') + ".jar"); // NOI18N |
| } |
| // Once with no namespaces. |
| NbAntlib.process(project, antlib, null, l); |
| // Once with. |
| String antlibUri = "antlib:" + cnb; // NOI18N |
| NbAntlib.process(project, antlib, antlibUri, l); |
| } |
| } else { |
| // For Ant 1.5, just dump in old-style defs in the simplest manner. |
| Map<String,Map<String,Class>> customDefs = AntBridge.getCustomDefsNoNamespace(); |
| for (Map.Entry<String,Class> entry : customDefs.get("task").entrySet()) { // NOI18N |
| project.addTaskDefinition(entry.getKey(), entry.getValue()); |
| } |
| for (Map.Entry<String,Class> entry : customDefs.get("type").entrySet()) { // NOI18N |
| project.addDataTypeDefinition(entry.getKey(), entry.getValue()); |
| } |
| } |
| if (AntModule.err.isLoggable(ErrorManager.INFORMATIONAL)) { |
| AntModule.err.log("addCustomDefs took " + (System.currentTimeMillis() - start) + "msec"); |
| } |
| } |
| |
| private static boolean doGutProject = !Boolean.getBoolean("org.apache.tools.ant.module.bridge.impl.BridgeImpl.doNotGutProject"); |
| /** |
| * Try to break up as many references in a project as possible. |
| * Helpful to mitigate the effects of unsolved memory leaks: at |
| * least one project will not hold onto all subprojects, and a |
| * taskdef will not hold onto its siblings, etc. |
| */ |
| private static void gutProject(Project p) { |
| if (!doGutProject) { |
| return; |
| } |
| // XXX should ideally try to wait for all other threads in this thread group |
| // to finish - see e.g. #51962 for example of what can happen otherwise. |
| try { |
| String s = p.getName(); |
| AntModule.err.log("Gutting extra references in project \"" + s + "\""); |
| for (Field f : Project.class.getDeclaredFields()) { |
| if (Modifier.isStatic(f.getModifiers())) { |
| continue; |
| } |
| if (f.getType().isPrimitive()) { |
| continue; |
| } |
| f.setAccessible(true); |
| Object o = f.get(p); |
| if (o == null) { |
| continue; |
| } |
| try { |
| if (o instanceof Collection<?>) { |
| ((Collection<?>) o).clear(); |
| // #69727: do not null out the field (e.g. Project.listeners) in this case. |
| continue; |
| } else if (o instanceof Map<?,?>) { |
| ((Map<?,?>) o).clear(); |
| continue; |
| } |
| } catch (UnsupportedOperationException e) { |
| // ignore |
| } |
| if (Modifier.isFinal(f.getModifiers())) { |
| continue; |
| } |
| if (o.getClass().isArray()) { |
| f.set(p, Array.newInstance(o.getClass().getComponentType(), 0)); |
| continue; |
| } |
| f.set(p, null); |
| } |
| // #43113: IntrospectionHelper can hold strong refs to dynamically loaded classes |
| Field helpersF; |
| try { |
| helpersF = IntrospectionHelper.class.getDeclaredField("helpers"); |
| } catch (NoSuchFieldException x) { // Ant 1.7.0 |
| helpersF = IntrospectionHelper.class.getDeclaredField("HELPERS"); |
| } |
| helpersF.setAccessible(true); |
| Object helpersO = helpersF.get(null); |
| Map<?,?> helpersM = (Map<?,?>) helpersO; |
| helpersM.clear(); |
| // #46532: java.beans.Introspector caches not cleared well in all cases. |
| Introspector.flushCaches(); |
| } catch (Exception e) { |
| // Oh well. |
| AntModule.err.notify(ErrorManager.INFORMATIONAL, e); |
| doGutProject = false; |
| } |
| } |
| |
| private static class PostRun implements Runnable { |
| private Project project; |
| private NbBuildLogger logger; |
| public PostRun(Project project, NbBuildLogger logger) { |
| this.project = project; |
| this.logger = logger; |
| } |
| public @Override void run() { |
| IntrospectedInfo custom = AntSettings.getCustomDefs(); |
| @SuppressWarnings("rawtypes") |
| Map<String,Map<String,Class>> defs = new HashMap<String, Map<String, Class>>(); |
| try { |
| defs.put("task", NbCollections.checkedMapByCopy(project.getTaskDefinitions(), String.class, Class.class, true)); |
| defs.put("type", NbCollections.checkedMapByCopy(project.getDataTypeDefinitions(), String.class, Class.class, true)); |
| } catch (ThreadDeath t) { |
| // #137883: late clicks on Stop which can be ignored. |
| } |
| custom.scanProject(defs); |
| AntSettings.setCustomDefs(custom); |
| logger.shutdown(); |
| // #85698: do not invoke multiple refreshes at once |
| refreshFilesystemsTask.schedule(0); |
| gutProject(project); |
| // #185853: make sure these fields do not stick around |
| project = null; |
| logger = null; |
| } |
| } |
| |
| } |