001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.lang.reflect.InvocationTargetException;
022: import java.lang.reflect.Method;
023: import java.util.Enumeration;
024: import java.util.Vector;
025:
026: /**
027: * Destroys all registered <code>Process</code>es when the VM exits.
028: *
029: * @since Ant 1.5
030: */
031: class ProcessDestroyer implements Runnable {
032:
033: private Vector processes = new Vector();
034: // methods to register and unregister shutdown hooks
035: private Method addShutdownHookMethod;
036: private Method removeShutdownHookMethod;
037: private ProcessDestroyerImpl destroyProcessThread = null;
038:
039: // whether or not this ProcessDestroyer has been registered as a
040: // shutdown hook
041: private boolean added = false;
042: // whether or not this ProcessDestroyer is currently running as
043: // shutdown hook
044: private boolean running = false;
045:
046: private class ProcessDestroyerImpl extends Thread {
047: private boolean shouldDestroy = true;
048:
049: public ProcessDestroyerImpl() {
050: super ("ProcessDestroyer Shutdown Hook");
051: }
052:
053: public void run() {
054: if (shouldDestroy) {
055: ProcessDestroyer.this .run();
056: }
057: }
058:
059: public void setShouldDestroy(boolean shouldDestroy) {
060: this .shouldDestroy = shouldDestroy;
061: }
062: }
063:
064: /**
065: * Constructs a <code>ProcessDestroyer</code> and obtains
066: * <code>Runtime.addShutdownHook()</code> and
067: * <code>Runtime.removeShutdownHook()</code> through reflection. The
068: * ProcessDestroyer manages a list of processes to be destroyed when the
069: * VM exits. If a process is added when the list is empty,
070: * this <code>ProcessDestroyer</code> is registered as a shutdown hook. If
071: * removing a process results in an empty list, the
072: * <code>ProcessDestroyer</code> is removed as a shutdown hook.
073: */
074: public ProcessDestroyer() {
075: try {
076: // check to see if the shutdown hook methods exists
077: // (support pre-JDK 1.3 VMs)
078: Class[] paramTypes = { Thread.class };
079: addShutdownHookMethod = Runtime.class.getMethod(
080: "addShutdownHook", paramTypes);
081:
082: removeShutdownHookMethod = Runtime.class.getMethod(
083: "removeShutdownHook", paramTypes);
084: // wait to add shutdown hook as needed
085: } catch (NoSuchMethodException e) {
086: // it just won't be added as a shutdown hook... :(
087: } catch (Exception e) {
088: e.printStackTrace();
089: }
090: }
091:
092: /**
093: * Registers this <code>ProcessDestroyer</code> as a shutdown hook,
094: * uses reflection to ensure pre-JDK 1.3 compatibility.
095: */
096: private void addShutdownHook() {
097: if (addShutdownHookMethod != null && !running) {
098: destroyProcessThread = new ProcessDestroyerImpl();
099: Object[] args = { destroyProcessThread };
100: try {
101: addShutdownHookMethod
102: .invoke(Runtime.getRuntime(), args);
103: added = true;
104: } catch (IllegalAccessException e) {
105: e.printStackTrace();
106: } catch (InvocationTargetException e) {
107: Throwable t = e.getTargetException();
108: if (t != null
109: && t.getClass() == IllegalStateException.class) {
110: // shutdown already is in progress
111: running = true;
112: } else {
113: e.printStackTrace();
114: }
115: }
116: }
117: }
118:
119: /**
120: * Removes this <code>ProcessDestroyer</code> as a shutdown hook,
121: * uses reflection to ensure pre-JDK 1.3 compatibility
122: */
123: private void removeShutdownHook() {
124: if (removeShutdownHookMethod != null && added && !running) {
125: Object[] args = { destroyProcessThread };
126: try {
127: Boolean removed = (Boolean) removeShutdownHookMethod
128: .invoke(Runtime.getRuntime(), args);
129: if (!removed.booleanValue()) {
130: System.err
131: .println("Could not remove shutdown hook");
132: }
133: } catch (IllegalAccessException e) {
134: e.printStackTrace();
135: } catch (InvocationTargetException e) {
136: Throwable t = e.getTargetException();
137: if (t != null
138: && t.getClass() == IllegalStateException.class) {
139: // shutdown already is in progress
140: running = true;
141: } else {
142: e.printStackTrace();
143: }
144: }
145: // start the hook thread, a unstarted thread may not be
146: // eligible for garbage collection
147: // Cf.: http://developer.java.sun.com/developer/bugParade/bugs/4533087.html
148: destroyProcessThread.setShouldDestroy(false);
149: if (!destroyProcessThread.getThreadGroup().isDestroyed()) {
150: // start() would throw IllegalThreadStateException from
151: // ThreadGroup.add if it were destroyed
152: destroyProcessThread.start();
153: }
154: // this should return quickly, since it basically is a NO-OP.
155: try {
156: destroyProcessThread.join(20000);
157: } catch (InterruptedException ie) {
158: // the thread didn't die in time
159: // it should not kill any processes unexpectedly
160: }
161: destroyProcessThread = null;
162: added = false;
163: }
164: }
165:
166: /**
167: * Returns whether or not the ProcessDestroyer is registered as
168: * as shutdown hook
169: * @return true if this is currently added as shutdown hook
170: */
171: public boolean isAddedAsShutdownHook() {
172: return added;
173: }
174:
175: /**
176: * Returns <code>true</code> if the specified <code>Process</code> was
177: * successfully added to the list of processes to destroy upon VM exit.
178: *
179: * @param process the process to add
180: * @return <code>true</code> if the specified <code>Process</code> was
181: * successfully added
182: */
183: public boolean add(Process process) {
184: synchronized (processes) {
185: // if this list is empty, register the shutdown hook
186: if (processes.size() == 0) {
187: addShutdownHook();
188: }
189: processes.addElement(process);
190: return processes.contains(process);
191: }
192: }
193:
194: /**
195: * Returns <code>true</code> if the specified <code>Process</code> was
196: * successfully removed from the list of processes to destroy upon VM exit.
197: *
198: * @param process the process to remove
199: * @return <code>true</code> if the specified <code>Process</code> was
200: * successfully removed
201: */
202: public boolean remove(Process process) {
203: synchronized (processes) {
204: boolean processRemoved = processes.removeElement(process);
205: if (processRemoved && processes.size() == 0) {
206: removeShutdownHook();
207: }
208: return processRemoved;
209: }
210: }
211:
212: /**
213: * Invoked by the VM when it is exiting.
214: */
215: public void run() {
216: synchronized (processes) {
217: running = true;
218: Enumeration e = processes.elements();
219: while (e.hasMoreElements()) {
220: ((Process) e.nextElement()).destroy();
221: }
222: }
223: }
224: }
|