001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.apache.tools.ant.module.bridge.impl;
043:
044: import java.beans.Introspector;
045: import java.io.File;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.io.PrintStream;
049: import java.lang.reflect.Constructor;
050: import java.lang.reflect.Field;
051: import java.lang.reflect.Method;
052: import java.lang.reflect.Modifier;
053: import java.net.URL;
054: import java.util.Collection;
055: import java.util.HashMap;
056: import java.util.HashSet;
057: import java.util.LinkedHashSet;
058: import java.util.List;
059: import java.util.Map;
060: import java.util.Set;
061: import java.util.Vector;
062: import java.util.WeakHashMap;
063: import java.util.logging.Level;
064: import java.util.logging.Logger;
065: import org.apache.tools.ant.BuildException;
066: import org.apache.tools.ant.DemuxOutputStream;
067: import org.apache.tools.ant.IntrospectionHelper;
068: import org.apache.tools.ant.Main;
069: import org.apache.tools.ant.Project;
070: import org.apache.tools.ant.ProjectHelper;
071: import org.apache.tools.ant.module.AntModule;
072: import org.apache.tools.ant.module.AntSettings;
073: import org.apache.tools.ant.module.api.IntrospectedInfo;
074: import org.apache.tools.ant.module.bridge.AntBridge;
075: import org.apache.tools.ant.module.bridge.BridgeInterface;
076: import org.apache.tools.ant.module.bridge.IntrospectionHelperProxy;
077: import org.apache.tools.ant.types.EnumeratedAttribute;
078: import org.apache.tools.ant.types.Path;
079: import org.netbeans.api.progress.ProgressHandle;
080: import org.openide.ErrorManager;
081: import org.openide.awt.StatusDisplayer;
082: import org.openide.filesystems.FileObject;
083: import org.openide.filesystems.FileStateInvalidException;
084: import org.openide.filesystems.FileSystem;
085: import org.openide.filesystems.FileUtil;
086: import org.openide.util.Exceptions;
087: import org.openide.util.NbBundle;
088: import org.openide.util.NbCollections;
089: import org.openide.util.RequestProcessor;
090: import org.openide.windows.OutputWriter;
091:
092: /**
093: * Implements the BridgeInterface using the current version of Ant.
094: * @author Jesse Glick
095: */
096: public class BridgeImpl implements BridgeInterface {
097:
098: /** Number of milliseconds to wait before forcibly halting a runaway process. */
099: private static final int STOP_TIMEOUT = 3000;
100:
101: private static boolean classpathInitialized = false;
102:
103: /**
104: * Index of loggers by active thread.
105: * @see #stop
106: */
107: private static final Map<Thread, NbBuildLogger> loggersByThread = new WeakHashMap<Thread, NbBuildLogger>();
108:
109: public BridgeImpl() {
110: }
111:
112: public String getAntVersion() {
113: try {
114: return Main.getAntVersion();
115: } catch (BuildException be) {
116: AntModule.err.notify(ErrorManager.INFORMATIONAL, be);
117: return NbBundle.getMessage(BridgeImpl.class,
118: "LBL_ant_version_unknown");
119: }
120: }
121:
122: public boolean isAnt16() {
123: try {
124: Class.forName("org.apache.tools.ant.taskdefs.Antlib"); // NOI18N
125: return true;
126: } catch (ClassNotFoundException e) {
127: // Fine, 1.5
128: return false;
129: }
130: }
131:
132: public IntrospectionHelperProxy getIntrospectionHelper(
133: Class<?> clazz) {
134: return new IntrospectionHelperImpl(clazz);
135: }
136:
137: public boolean toBoolean(String val) {
138: return Project.toBoolean(val);
139: }
140:
141: public String[] getEnumeratedValues(Class<?> c) {
142: if (EnumeratedAttribute.class.isAssignableFrom(c)) {
143: try {
144: return ((EnumeratedAttribute) c.newInstance())
145: .getValues();
146: } catch (Exception e) {
147: AntModule.err.notify(ErrorManager.INFORMATIONAL, e);
148: }
149: } else if (Enum.class.isAssignableFrom(c)) { // Ant 1.7.0 (#41058)
150: try {
151: Enum<?>[] vals = (Enum<?>[]) c.getMethod("values")
152: .invoke(null);
153: String[] names = new String[vals.length];
154: for (int i = 0; i < vals.length; i++) {
155: names[i] = vals[i].name();
156: }
157: return names;
158: } catch (Exception x) {
159: Exceptions.printStackTrace(x);
160: }
161: }
162: return null;
163: }
164:
165: public boolean run(File buildFile, List<String> targets,
166: InputStream in, OutputWriter out, OutputWriter err,
167: Map<String, String> properties, int verbosity,
168: String displayName, Runnable interestingOutputCallback,
169: ProgressHandle handle) {
170: if (!classpathInitialized) {
171: classpathInitialized = true;
172: // #46171: Ant expects this path to have itself and whatever else you loaded with it,
173: // or AntClassLoader.getResources will not be able to find anything in the Ant loader.
174: Path.systemClasspath = new Path(null, AntBridge
175: .getMainClassPath());
176: }
177:
178: boolean ok = false;
179:
180: // Important for various other stuff.
181: final boolean ant16 = isAnt16();
182:
183: // Make sure "main Ant loader" is used as context loader for duration of the
184: // run. Otherwise some code, e.g. JAXP, will accidentally pick up NB classes,
185: // which can cause various undesirable effects.
186: ClassLoader oldCCL = Thread.currentThread()
187: .getContextClassLoader();
188: ClassLoader newCCL = Project.class.getClassLoader();
189: if (AntModule.err.isLoggable(ErrorManager.INFORMATIONAL)) {
190: AntModule.err
191: .log("Fixing CCL: " + oldCCL + " -> " + newCCL);
192: }
193: Thread.currentThread().setContextClassLoader(newCCL);
194: AntBridge.fakeJavaClassPath();
195: try {
196:
197: final Project project;
198:
199: // first use the ProjectHelper to create the project object
200: // from the given build file.
201: final NbBuildLogger logger = new NbBuildLogger(buildFile,
202: out, err, verbosity, displayName,
203: interestingOutputCallback, handle);
204: Vector<String> targs;
205: try {
206: project = new Project();
207: project.addBuildListener(logger);
208: project.init();
209: project.addTaskDefinition("java",
210: ForkedJavaOverride.class); // #56341
211: try {
212: addCustomDefs(project);
213: } catch (IOException e) {
214: throw new BuildException(e);
215: }
216: project.setUserProperty("ant.file", buildFile
217: .getAbsolutePath()); // NOI18N
218: // #14993:
219: project.setUserProperty("ant.version", Main
220: .getAntVersion()); // NOI18N
221: File antHome = AntSettings.getAntHome();
222: if (antHome != null) {
223: project.setUserProperty("ant.home", antHome
224: .getAbsolutePath()); // NOI18N
225: }
226: for (Map.Entry<String, String> entry : properties
227: .entrySet()) {
228: project.setUserProperty(entry.getKey(), entry
229: .getValue());
230: }
231: if (in != null && ant16) {
232: try {
233: Method m = Project.class.getMethod(
234: "setDefaultInputStream",
235: InputStream.class); // NOI18N
236: m.invoke(project, in);
237: } catch (Exception e) {
238: AntModule.err.notify(
239: ErrorManager.INFORMATIONAL, e);
240: }
241: }
242: if (AntModule.err
243: .isLoggable(ErrorManager.INFORMATIONAL)) {
244: AntModule.err
245: .log("CCL when configureProject is called: "
246: + Thread.currentThread()
247: .getContextClassLoader());
248: }
249: ProjectHelper projhelper = ProjectHelper
250: .getProjectHelper();
251: // Cf. Ant #32668 & #32216; ProjectHelper.configureProject undeprecated in 1.7
252: project.addReference("ant.projectHelper", projhelper); // NOI18N
253: projhelper.parse(project, buildFile);
254:
255: project.setInputHandler(new NbInputHandler(
256: interestingOutputCallback));
257:
258: if (targets != null) {
259: targs = new Vector<String>(targets);
260: } else {
261: targs = new Vector<String>(1);
262: targs.add(project.getDefaultTarget());
263: }
264: logger.setActualTargets(targets != null ? targets
265: .toArray(new String[targets.size()]) : null);
266: } catch (BuildException be) {
267: logger.buildInitializationFailed(be);
268: logger.shutdown();
269: if (in != null) {
270: try {
271: in.close();
272: } catch (IOException e) {
273: AntModule.err.notify(e);
274: }
275: }
276: return false;
277: }
278:
279: project.fireBuildStarted();
280:
281: // Save & restore system output streams.
282: InputStream is = System.in;
283: if (in != null && ant16) {
284: try {
285: Class<? extends InputStream> dis = Class.forName(
286: "org.apache.tools.ant.DemuxInputStream")
287: .asSubclass(InputStream.class); // NOI18N
288: Constructor<? extends InputStream> c = dis
289: .getConstructor(Project.class);
290: is = c.newInstance(project);
291: } catch (Exception e) {
292: AntModule.err.notify(ErrorManager.INFORMATIONAL, e);
293: }
294: }
295: AntBridge.pushSystemInOutErr(is, new PrintStream(
296: new DemuxOutputStream(project, false)),
297: new PrintStream(
298: new DemuxOutputStream(project, true)));
299:
300: Thread currentThread = Thread.currentThread();
301: synchronized (loggersByThread) {
302: assert !loggersByThread.containsKey(currentThread);
303: loggersByThread.put(currentThread, logger);
304: }
305: try {
306: // Execute the configured project
307: //writer.println("#4"); // NOI18N
308: project.executeTargets(targs);
309: //writer.println("#5"); // NOI18N
310: project.fireBuildFinished(null);
311: ok = true;
312: } catch (Throwable t) {
313: // Really need to catch everything, else AntClassLoader.cleanup may
314: // not be called, resulting in a memory leak and/or locked JARs (#42431).
315: project.fireBuildFinished(t);
316: } finally {
317: AntBridge.restoreSystemInOutErr();
318: if (in != null) {
319: try {
320: in.close();
321: } catch (IOException e) {
322: AntModule.err.notify(e);
323: }
324: }
325: synchronized (loggersByThread) {
326: loggersByThread.remove(currentThread);
327: }
328: }
329:
330: // Now check to see if the Project defined any cool new custom tasks.
331: RequestProcessor.getDefault().post(new Runnable() {
332: public void run() {
333: IntrospectedInfo custom = AntSettings
334: .getCustomDefs();
335: Map<String, Map<String, Class>> defs = new HashMap<String, Map<String, Class>>();
336: defs.put("task", NbCollections.checkedMapByCopy(
337: project.getTaskDefinitions(), String.class,
338: Class.class, true));
339: defs.put("type", NbCollections.checkedMapByCopy(
340: project.getDataTypeDefinitions(),
341: String.class, Class.class, true));
342: custom.scanProject(defs);
343: AntSettings.setCustomDefs(custom);
344: logger.shutdown();
345: // #85698: do not invoke multiple refreshes at once
346: refreshFilesystemsTask.schedule(0);
347: gutProject(project);
348: if (!ant16) {
349: // #36393 - memory leak in Ant 1.5.
350: RequestProcessor.getDefault().post(
351: new Runnable() {
352: public void run() {
353: hack36393();
354: }
355: });
356: }
357: }
358: });
359:
360: } finally {
361: AntBridge.unfakeJavaClassPath();
362: if (AntModule.err.isLoggable(ErrorManager.INFORMATIONAL)) {
363: AntModule.err.log("Restoring CCL: " + oldCCL);
364: }
365: Thread.currentThread().setContextClassLoader(oldCCL);
366: }
367:
368: return ok;
369: }
370:
371: private static final RequestProcessor.Task refreshFilesystemsTask = RequestProcessor
372: .getDefault().create(new Runnable() {
373: public void run() {
374: Logger.getLogger(BridgeImpl.class.getName()).log(
375: Level.FINE, "Refreshing filesystems");
376: FileUtil.refreshAll();
377: }
378: });
379:
380: public void stop(final Thread process) {
381: NbBuildLogger logger;
382: synchronized (loggersByThread) {
383: logger = loggersByThread.get(process);
384: }
385: if (logger != null) {
386: // Try stopping at a safe point.
387: StatusDisplayer.getDefault().setStatusText(
388: NbBundle.getMessage(BridgeImpl.class,
389: "MSG_stopping", logger
390: .getDisplayNameNoLock()));
391: logger.stop();
392: process.interrupt();
393: // But if that doesn't do it, double-check later...
394: // Yes Thread.stop() is deprecated; that is why we try to avoid using it.
395: RequestProcessor.getDefault().create(new Runnable() {
396: public void run() {
397: forciblyStop(process);
398: }
399: }).schedule(STOP_TIMEOUT);
400: } else {
401: // Try killing it now!
402: forciblyStop(process);
403: }
404: }
405:
406: private void forciblyStop(Thread process) {
407: if (process.isAlive()) {
408: StatusDisplayer.getDefault()
409: .setStatusText(
410: NbBundle.getMessage(BridgeImpl.class,
411: "MSG_halting"));
412: stopThread(process);
413: }
414: }
415:
416: @SuppressWarnings("deprecation")
417: private static void stopThread(Thread process) {
418: process.stop();
419: }
420:
421: private static void addCustomDefs(Project project)
422: throws BuildException, IOException {
423: long start = System.currentTimeMillis();
424: if (AntBridge.getInterface().isAnt16()) {
425: Map<String, ClassLoader> antlibLoaders = AntBridge
426: .getCustomDefClassLoaders();
427: for (Map.Entry<String, ClassLoader> entry : antlibLoaders
428: .entrySet()) {
429: String cnb = entry.getKey();
430: ClassLoader l = entry.getValue();
431: String resource = cnb.replace('.', '/') + "/antlib.xml"; // NOI18N
432: URL antlib = l.getResource(resource);
433: if (antlib == null) {
434: throw new IOException("Could not find " + antlib
435: + " in ant/nblib/" + cnb.replace('.', '-')
436: + ".jar"); // NOI18N
437: }
438: // Once with no namespaces.
439: NbAntlib.process(project, antlib, null, l);
440: // Once with.
441: String antlibUri = "antlib:" + cnb; // NOI18N
442: NbAntlib.process(project, antlib, antlibUri, l);
443: }
444: } else {
445: // For Ant 1.5, just dump in old-style defs in the simplest manner.
446: Map<String, Map<String, Class>> customDefs = AntBridge
447: .getCustomDefsNoNamespace();
448: for (Map.Entry<String, Class> entry : customDefs
449: .get("task").entrySet()) { // NOI18N
450: project.addTaskDefinition(entry.getKey(), entry
451: .getValue());
452: }
453: for (Map.Entry<String, Class> entry : customDefs
454: .get("type").entrySet()) { // NOI18N
455: project.addDataTypeDefinition(entry.getKey(), entry
456: .getValue());
457: }
458: }
459: if (AntModule.err.isLoggable(ErrorManager.INFORMATIONAL)) {
460: AntModule.err.log("addCustomDefs took "
461: + (System.currentTimeMillis() - start) + "msec");
462: }
463: }
464:
465: private static boolean doHack36393 = true;
466:
467: /**
468: * Remove any outstanding ProcessDestroyer shutdown hooks.
469: * They should not be left in the JRE static area.
470: * Workaround for bug in Ant 1.5.x, fixed already in Ant 1.6.
471: */
472: private static void hack36393() {
473: if (!doHack36393) {
474: // Failed last time, skip this time.
475: return;
476: }
477: try {
478: Class shutdownC = Class.forName("java.lang.Shutdown"); // NOI18N
479: Class wrappedHookC = Class
480: .forName("java.lang.Shutdown$WrappedHook"); // NOI18N
481: Field hooksF = shutdownC.getDeclaredField("hooks"); // NOI18N
482: hooksF.setAccessible(true);
483: Field hookF = wrappedHookC.getDeclaredField("hook"); // NOI18N
484: hookF.setAccessible(true);
485: Field lockF = shutdownC.getDeclaredField("lock"); // NOI18N
486: lockF.setAccessible(true);
487: Object lock = lockF.get(null);
488: Set<Thread> toRemove = new HashSet<Thread>();
489: synchronized (lock) {
490: @SuppressWarnings("unchecked")
491: Set<Object> hooks = (Set) hooksF.get(null);
492: for (Object wrappedHook : hooks) {
493: Thread hook = (Thread) hookF.get(wrappedHook);
494: if (hook
495: .getClass()
496: .getName()
497: .equals(
498: "org.apache.tools.ant.taskdefs.ProcessDestroyer")) { // NOI18N
499: // Don't remove it now - will get ConcurrentModificationException.
500: toRemove.add(hook);
501: }
502: }
503: }
504: for (Thread hook : toRemove) {
505: if (!Runtime.getRuntime().removeShutdownHook(hook)) {
506: throw new IllegalStateException(
507: "Hook was not really registered!"); // NOI18N
508: }
509: AntModule.err
510: .log("#36393: removing an unwanted ProcessDestroyer shutdown hook");
511: // #36395: memory leak in ThreadGroup if the thread is not started.
512: hook.start();
513: }
514: } catch (Exception e) {
515: // Oh well.
516: AntModule.err.notify(ErrorManager.INFORMATIONAL, e);
517: doHack36393 = false;
518: }
519: }
520:
521: private static boolean doGutProject = !Boolean
522: .getBoolean("org.apache.tools.ant.module.bridge.impl.BridgeImpl.doNotGutProject");
523:
524: /**
525: * Try to break up as many references in a project as possible.
526: * Helpful to mitigate the effects of unsolved memory leaks: at
527: * least one project will not hold onto all subprojects, and a
528: * taskdef will not hold onto its siblings, etc.
529: */
530: private static void gutProject(Project p) {
531: if (!doGutProject) {
532: return;
533: }
534: // XXX should ideally try to wait for all other threads in this thread group
535: // to finish - see e.g. #51962 for example of what can happen otherwise.
536: try {
537: String s = p.getName();
538: AntModule.err.log("Gutting extra references in project \""
539: + s + "\"");
540: Field[] fs = Project.class.getDeclaredFields();
541: for (int i = 0; i < fs.length; i++) {
542: if (Modifier.isStatic(fs[i].getModifiers())) {
543: continue;
544: }
545: if (fs[i].getType().isPrimitive()) {
546: continue;
547: }
548: fs[i].setAccessible(true);
549: Object o = fs[i].get(p);
550: try {
551: if (o instanceof Collection) {
552: ((Collection) o).clear();
553: // #69727: do not null out the field (e.g. Project.listeners) in this case.
554: continue;
555: } else if (o instanceof Map) {
556: ((Map) o).clear();
557: continue;
558: }
559: } catch (UnsupportedOperationException e) {
560: // ignore
561: }
562: if (Modifier.isFinal(fs[i].getModifiers())) {
563: continue;
564: }
565: fs[i].set(p, null);
566: }
567: // #43113: IntrospectionHelper can hold strong refs to dynamically loaded classes
568: Field helpersF;
569: try {
570: helpersF = IntrospectionHelper.class
571: .getDeclaredField("helpers");
572: } catch (NoSuchFieldException x) { // Ant 1.7.0
573: helpersF = IntrospectionHelper.class
574: .getDeclaredField("HELPERS");
575: }
576: helpersF.setAccessible(true);
577: Object helpersO = helpersF.get(null);
578: Map helpersM = (Map) helpersO;
579: helpersM.clear();
580: // #46532: java.beans.Introspector caches not cleared well in all cases.
581: Introspector.flushCaches();
582: } catch (Exception e) {
583: // Oh well.
584: AntModule.err.notify(ErrorManager.INFORMATIONAL, e);
585: doGutProject = false;
586: }
587: }
588:
589: }
|