Line 0
Link Here
|
|
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 2008 Sun Microsystems, Inc. All rights reserved. |
5 |
* |
6 |
* The contents of this file are subject to the terms of either the GNU |
7 |
* General Public License Version 2 only ("GPL") or the Common |
8 |
* Development and Distribution License("CDDL") (collectively, the |
9 |
* "License"). You may not use this file except in compliance with the |
10 |
* License. You can obtain a copy of the License at |
11 |
* http://www.netbeans.org/cddl-gplv2.html |
12 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
13 |
* specific language governing permissions and limitations under the |
14 |
* License. When distributing the software, include this License Header |
15 |
* Notice in each file and include the License file at |
16 |
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this |
17 |
* particular file as subject to the "Classpath" exception as provided |
18 |
* by Sun in the GPL Version 2 section of the License file that |
19 |
* accompanied this code. If applicable, add the following below the |
20 |
* License Header, with the fields enclosed by brackets [] replaced by |
21 |
* your own identifying information: |
22 |
* "Portions Copyrighted [year] [name of copyright owner]" |
23 |
* |
24 |
* If you wish your version of this file to be governed by only the CDDL |
25 |
* or only the GPL Version 2, indicate your decision by adding |
26 |
* "[Contributor] elects to include this software in this distribution |
27 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
28 |
* single choice of license, a recipient has the option to distribute |
29 |
* your version of this file under either the CDDL, the GPL Version 2 or |
30 |
* to extend the choice of license to its licensees as provided above. |
31 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
32 |
* Version 2 license, then the option applies only if the new code is |
33 |
* made subject to such option by the copyright holder. |
34 |
* |
35 |
* Contributor(s): |
36 |
* |
37 |
* Portions Copyrighted 2008 Sun Microsystems, Inc. |
38 |
*/ |
39 |
|
40 |
package org.netbeans.spi.java.project.runner; |
41 |
|
42 |
import java.io.File; |
43 |
import java.io.IOException; |
44 |
import java.lang.reflect.InvocationTargetException; |
45 |
import java.lang.reflect.Method; |
46 |
import java.util.Properties; |
47 |
import java.util.logging.Level; |
48 |
import java.util.logging.Logger; |
49 |
import org.apache.tools.ant.module.api.support.ActionUtils; |
50 |
import org.netbeans.api.java.classpath.ClassPath; |
51 |
import org.netbeans.api.java.classpath.ClassPath.Entry; |
52 |
import org.openide.filesystems.FileObject; |
53 |
import org.openide.filesystems.FileUtil; |
54 |
import org.openide.filesystems.Repository; |
55 |
import org.openide.util.Exceptions; |
56 |
import org.openide.util.Lookup; |
57 |
import org.openide.util.Parameters; |
58 |
|
59 |
/** |
60 |
* <p>Class that allows to execute given file(s). API clients can check whether given |
61 |
* command is support, by calling |
62 |
* {@link #isSupported(String)} and execute the command by calling |
63 |
* {@link #execute(String, Properties, List)}. Please consult documentation of particular |
64 |
* commands for the list of supported properties.</p> |
65 |
* |
66 |
* <p>SPI clients (command providers) should write a short ant build script performing |
67 |
* the given command and register it on the default filesystem as <code>executor-snippets/<command>.xml</code>. |
68 |
* The project runner will automatically set the following properties:</p> |
69 |
* <ul> |
70 |
* <li><strong>classpath</strong> contains executable classpath of the executed file</li> |
71 |
* <li><strong>classname</strong> contains a classname corresponding to the file that should be executed</li> |
72 |
* </ul> |
73 |
* |
74 |
* <p>If the script defines an attribute <code>checkSupported</code> of a string value, the value is |
75 |
* interpreted as a name of a static method with one parameter of type {@link FileObject}. |
76 |
* When the API client invokes the {@link #isSupported(String,FileObject)} method, the |
77 |
* method specified by the <code>checkSupported</code> is invoked. Its parameter is |
78 |
* the same as the second parameter of {@link #isSupported(String,FileObject)}. |
79 |
* {@link #isSupported(String,FileObject)} returns true if and only if script exists for the given |
80 |
* command and either the <code>checkSupported</code> attribute is not specified or |
81 |
* the method specified by this attribute exists and returns boolean value <code>true</code>. |
82 |
* |
83 |
* @since 1.19 |
84 |
* |
85 |
* @author Jan Lahoda |
86 |
*/ |
87 |
public final class ProjectRunner { |
88 |
|
89 |
/** |
90 |
* "Test" run the given file. Classfiles produced by the Java infrastructure will be |
91 |
* executed. |
92 |
* |
93 |
* Supported properties: |
94 |
* <ul> |
95 |
* <li><strong>run.jvmargs</strong> arguments that will be passed to the Java Virtual Machine</li> |
96 |
* <li><strong>application.args</strong> arguments that will be passed to the executed files</li> |
97 |
* </ul> |
98 |
* |
99 |
* @since 1.19 |
100 |
*/ |
101 |
public static final String QUICK_RUN = "run"; |
102 |
|
103 |
/** |
104 |
* "Test" run the given file in the debugging mode. Classfiles produced by the Java infrastructure will be |
105 |
* executed. |
106 |
* |
107 |
* Supported properties: |
108 |
* <ul> |
109 |
* <li><strong>run.jvmargs</strong> arguments that will be passed to the Java Virtual Machine</li> |
110 |
* <li><strong>application.args</strong> arguments that will be passed to the executed files</li> |
111 |
* </ul> |
112 |
* |
113 |
* @since 1.19 |
114 |
*/ |
115 |
public static final String QUICK_DEBUG = "debug"; |
116 |
|
117 |
/** |
118 |
* "Test" run the given test. Classfiles produced by the Java infrastructure will be |
119 |
* executed. |
120 |
* |
121 |
* Supported properties: none. |
122 |
* |
123 |
* @since 1.19 |
124 |
*/ |
125 |
public static final String QUICK_TEST = "junit"; |
126 |
|
127 |
/** |
128 |
* "Test" run the given test in the debugging mode. Classfiles produced by the Java infrastructure will be |
129 |
* executed. |
130 |
* |
131 |
* Supported properties: none. |
132 |
* |
133 |
* @since 1.19 |
134 |
*/ |
135 |
public static final String QUICK_TEST_DEBUG = "junit-debug"; |
136 |
|
137 |
private static final Logger LOG = Logger.getLogger(ProjectRunner.class.getName()); |
138 |
|
139 |
/** |
140 |
* Check whether the given command is supported. |
141 |
* |
142 |
* @param command command name |
143 |
* @param toRun either the file that would be executed, or the project folder |
144 |
* @return true if and only if the given command is supported for given file/folder |
145 |
* |
146 |
* @since 1.19 |
147 |
*/ |
148 |
public static boolean isSupported(String command, FileObject toRun) { |
149 |
Parameters.notNull("command", command); |
150 |
Parameters.notNull("toRun", toRun); |
151 |
|
152 |
FileObject script = locateScript(command); |
153 |
|
154 |
if (script == null) { |
155 |
return false; |
156 |
} |
157 |
|
158 |
Object attribute = script.getAttribute("checkSupported"); |
159 |
|
160 |
if (attribute == null) { |
161 |
return true; |
162 |
} |
163 |
|
164 |
if (!(attribute instanceof String)) { |
165 |
LOG.log(Level.WARNING, "checkSupported attribute of {0} is not a String value (class: {1})", new Object[] {FileUtil.getFileDisplayName(script), attribute.getClass()}); |
166 |
return false; |
167 |
} |
168 |
|
169 |
try { |
170 |
String m = (String) attribute; |
171 |
int lastDot = m.lastIndexOf('.'); |
172 |
|
173 |
if (lastDot == (-1)) { |
174 |
LOG.log(Level.WARNING, "checkSupported attribute of {0} is malformed: {1})", new Object[] {FileUtil.getFileDisplayName(script), m}); |
175 |
return false; |
176 |
} |
177 |
|
178 |
String clazzName = m.substring(0, lastDot); |
179 |
String methodName = m.substring(lastDot + 1); |
180 |
|
181 |
ClassLoader loader = Lookup.getDefault().lookup(ClassLoader.class); |
182 |
Class clazz; |
183 |
|
184 |
if (loader != null) { |
185 |
clazz = Class.forName(clazzName, true, loader); |
186 |
} else { |
187 |
clazz = Class.forName(clazzName); |
188 |
} |
189 |
|
190 |
Method method = clazz.getDeclaredMethod(methodName, FileObject.class); |
191 |
|
192 |
method.setAccessible(true); |
193 |
|
194 |
Object r = method.invoke(null, toRun); |
195 |
|
196 |
return r instanceof Boolean && (Boolean) r; |
197 |
} catch (IllegalAccessException ex) { |
198 |
LOG.log(Level.WARNING, "Cannot execute checkSupported method for {0} because of an exception.", new Object[] {FileUtil.getFileDisplayName(script), ex}); |
199 |
return false; |
200 |
} catch (IllegalArgumentException ex) { |
201 |
LOG.log(Level.WARNING, "Cannot execute checkSupported method for {0} because of an exception.", new Object[] {FileUtil.getFileDisplayName(script), ex}); |
202 |
return false; |
203 |
} catch (InvocationTargetException ex) { |
204 |
LOG.log(Level.WARNING, "Cannot execute checkSupported method for {0} because of an exception.", new Object[] {FileUtil.getFileDisplayName(script), ex}); |
205 |
return false; |
206 |
} catch (NoSuchMethodException ex) { |
207 |
LOG.log(Level.WARNING, "Cannot execute checkSupported method for {0} because of an exception.", new Object[] {FileUtil.getFileDisplayName(script), ex}); |
208 |
return false; |
209 |
} catch (SecurityException ex) { |
210 |
LOG.log(Level.WARNING, "Cannot execute checkSupported method for {0} because of an exception.", new Object[] {FileUtil.getFileDisplayName(script), ex}); |
211 |
return false; |
212 |
} catch (ClassNotFoundException ex) { |
213 |
LOG.log(Level.WARNING, "Cannot execute checkSupported method for {0} because of an exception.", new Object[] {FileUtil.getFileDisplayName(script), ex}); |
214 |
return false; |
215 |
} |
216 |
} |
217 |
|
218 |
/** |
219 |
* Execute the given command with given parameters. Please refer to the documentation |
220 |
* of the given command for supported properties. |
221 |
* |
222 |
* @param command command to execute |
223 |
* @param props properties |
224 |
* @param toRun file to run |
225 |
* @throws java.io.IOException if execution fails |
226 |
* |
227 |
* @since 1.19 |
228 |
*/ |
229 |
public static void execute(String command, Properties props, FileObject toRun) throws IOException { |
230 |
ClassPath exec = ClassPath.getClassPath(toRun, ClassPath.EXECUTE); |
231 |
ClassPath source = ClassPath.getClassPath(toRun, ClassPath.SOURCE); |
232 |
|
233 |
LOG.log(Level.FINE, "execute classpath={0}", exec); |
234 |
|
235 |
String cp = toString(exec); |
236 |
|
237 |
Properties antProps = (Properties) props.clone(); |
238 |
|
239 |
antProps.setProperty("classpath", cp); |
240 |
antProps.setProperty("classname", source.getResourceName(toRun, '.', false)); |
241 |
|
242 |
ActionUtils.runTarget(buildScript(command), new String[] {"execute"}, antProps); |
243 |
} |
244 |
|
245 |
private static String toString(ClassPath exec) { |
246 |
StringBuilder cp = new StringBuilder(); |
247 |
boolean first = true; |
248 |
|
249 |
for (Entry e : exec.entries()) { |
250 |
if (!first) { |
251 |
cp.append(File.pathSeparatorChar); |
252 |
} |
253 |
|
254 |
File f = FileUtil.archiveOrDirForURL(e.getURL()); |
255 |
|
256 |
if (f != null) { |
257 |
cp.append(f.getAbsolutePath()); |
258 |
first = false; |
259 |
} |
260 |
} |
261 |
|
262 |
return cp.toString(); |
263 |
} |
264 |
|
265 |
private static FileObject locateScript(String actionName) { |
266 |
return Repository.getDefault().getDefaultFileSystem().findResource("executor-snippets/" + actionName + ".xml"); |
267 |
} |
268 |
|
269 |
private static FileObject buildScript(String actionName) { |
270 |
FileObject script = locateScript(actionName); |
271 |
|
272 |
if (script == null) { |
273 |
return null; |
274 |
} |
275 |
|
276 |
File scriptFile = new File(getCacheFolder(), actionName + ".xml"); |
277 |
|
278 |
if (!scriptFile.canRead() || script.lastModified().getTime() > scriptFile.lastModified()) { |
279 |
try { |
280 |
scriptFile.delete(); |
281 |
|
282 |
FileObject parent = FileUtil.createFolder(scriptFile.getParentFile()); |
283 |
|
284 |
return FileUtil.copyFile(script, parent, actionName); |
285 |
} catch (IOException ex) { |
286 |
Exceptions.printStackTrace(ex); |
287 |
return null; |
288 |
} |
289 |
} |
290 |
|
291 |
return FileUtil.toFileObject(scriptFile); |
292 |
} |
293 |
|
294 |
private static final String NB_USER_DIR = "netbeans.user"; //NOI18N |
295 |
private static final String SNIPPETS_CACHE_DIR = "var"+File.separatorChar+"cache"+File.separatorChar+"executor-snippets"; //NOI18N |
296 |
|
297 |
|
298 |
private static String getNbUserDir () { |
299 |
final String nbUserProp = System.getProperty(NB_USER_DIR); |
300 |
return nbUserProp; |
301 |
} |
302 |
|
303 |
private static File cacheFolder; |
304 |
|
305 |
private static synchronized File getCacheFolder () { |
306 |
if (cacheFolder == null) { |
307 |
final String nbUserDirProp = getNbUserDir(); |
308 |
assert nbUserDirProp != null; |
309 |
final File nbUserDir = new File (nbUserDirProp); |
310 |
cacheFolder = FileUtil.normalizeFile(new File (nbUserDir, SNIPPETS_CACHE_DIR)); |
311 |
if (!cacheFolder.exists()) { |
312 |
boolean created = cacheFolder.mkdirs(); |
313 |
assert created : "Cannot create cache folder"; //NOI18N |
314 |
} |
315 |
else { |
316 |
assert cacheFolder.isDirectory() && cacheFolder.canRead() && cacheFolder.canWrite(); |
317 |
} |
318 |
} |
319 |
return cacheFolder; |
320 |
} |
321 |
} |