Line 0
Link Here
|
|
|
1 |
/** |
2 |
* Copyright (c) 2003,2008 Motorola Inc. and others. |
3 |
* All Rights Reserved. |
4 |
* Licensed under the Eclipse Public License - v 1.0 |
5 |
* For more information see http://www.eclipse.org/legal/epl-v10.html |
6 |
* |
7 |
* Contributors: |
8 |
* Gustavo de Paula (Motorola) - Initial Creation |
9 |
*/ |
10 |
package org.eclipse.mtj.core.build.preverifier; |
11 |
|
12 |
import java.io.File; |
13 |
import java.io.FileFilter; |
14 |
import java.io.IOException; |
15 |
import java.util.ArrayList; |
16 |
import java.util.Arrays; |
17 |
import java.util.Iterator; |
18 |
import java.util.List; |
19 |
import java.util.regex.Matcher; |
20 |
import java.util.regex.Pattern; |
21 |
|
22 |
import org.eclipse.core.resources.IFolder; |
23 |
import org.eclipse.core.resources.IResource; |
24 |
import org.eclipse.core.runtime.CoreException; |
25 |
import org.eclipse.core.runtime.IProgressMonitor; |
26 |
import org.eclipse.core.runtime.IStatus; |
27 |
import org.eclipse.core.runtime.Status; |
28 |
import org.eclipse.debug.core.IStreamListener; |
29 |
import org.eclipse.debug.core.model.IProcess; |
30 |
import org.eclipse.debug.core.model.IStreamMonitor; |
31 |
import org.eclipse.debug.core.model.IStreamsProxy; |
32 |
import org.eclipse.jdt.core.IJavaProject; |
33 |
import org.eclipse.jdt.core.JavaModelException; |
34 |
import org.eclipse.jdt.launching.IVMInstall; |
35 |
import org.eclipse.jdt.launching.JavaRuntime; |
36 |
import org.eclipse.mtj.core.MTJCore; |
37 |
import org.eclipse.mtj.core.persistence.IPersistenceProvider; |
38 |
import org.eclipse.mtj.core.persistence.PersistenceException; |
39 |
import org.eclipse.mtj.core.project.IMTJProject; |
40 |
import org.eclipse.mtj.core.project.midp.IMidletSuiteProject; |
41 |
import org.eclipse.mtj.internal.core.IMTJCoreConstants; |
42 |
import org.eclipse.mtj.internal.core.build.BuildConsoleProxy; |
43 |
import org.eclipse.mtj.internal.core.build.BuildLoggingConfiguration; |
44 |
import org.eclipse.mtj.internal.core.build.IBuildConsoleProxy; |
45 |
import org.eclipse.mtj.internal.core.build.preverifier.IClassErrorInformation; |
46 |
import org.eclipse.mtj.internal.core.build.preverifier.PreverificationError; |
47 |
import org.eclipse.mtj.internal.core.build.preverifier.PreverificationErrorLocation; |
48 |
import org.eclipse.mtj.internal.core.build.preverifier.PreverificationErrorLocationType; |
49 |
import org.eclipse.mtj.internal.core.build.preverifier.PreverificationErrorType; |
50 |
import org.eclipse.mtj.internal.core.util.TemporaryFileManager; |
51 |
import org.eclipse.mtj.internal.core.util.Utils; |
52 |
import org.eclipse.mtj.internal.core.util.log.MTJLogger; |
53 |
|
54 |
|
55 |
/** |
56 |
* Use proguard to preverify the MIDlet suite classes. Design and code is mostly |
57 |
* based on org.eclipse.mtj.core.model.impl.StandardPreverifier. |
58 |
* |
59 |
* TODO Refactore code to have a common class to be used both by StandPreverifier and ProguardPreverifier |
60 |
* |
61 |
* @author wgp010 |
62 |
*/ |
63 |
public class ProguardPreverifier implements IPreverifier { |
64 |
|
65 |
/** |
66 |
* The list of locations in which to look for the java executable in |
67 |
* candidate VM install locations, relative to the VM install location. |
68 |
* Code from org.eclipse.mtj.core.model.implJavaEmulatorDevice. |
69 |
* TODO Refactore to have a common place for it |
70 |
*/ |
71 |
private static final String[] CANDIDATE_JAVA_LOCATIONS = { |
72 |
"bin" + File.separatorChar + "java", //$NON-NLS-2$ //$NON-NLS-1$ |
73 |
"bin" + File.separatorChar + "java.exe", //$NON-NLS-2$ //$NON-NLS-1$ |
74 |
"jre" + File.separatorChar + "bin" + File.separatorChar + "java", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ |
75 |
"jre" + File.separatorChar + "bin" + File.separatorChar + "java.exe" }; //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ |
76 |
|
77 |
/** |
78 |
* Path to proguard lib |
79 |
*/ |
80 |
private String proguardJarFilePath = null; |
81 |
|
82 |
/** |
83 |
* Max number of characters in the command |
84 |
*/ |
85 |
private static final int MAX_COMMAND_LENGTH = 2000; |
86 |
|
87 |
|
88 |
// The regular expression we will use to match the preverify |
89 |
// error |
90 |
private static final String PREV_ERR_REGEX = "^Unable to access jarfile (\\S*)$"; |
91 |
|
92 |
// The compiled pattern for regular expression matching |
93 |
private static final Pattern PREV_ERR_PATTERN = Pattern.compile( |
94 |
PREV_ERR_REGEX, Pattern.MULTILINE); |
95 |
|
96 |
/** |
97 |
* Class constructor |
98 |
*/ |
99 |
public ProguardPreverifier () { |
100 |
this.proguardJarFilePath = MTJCore.getProguardJarFile().getAbsolutePath(); |
101 |
} |
102 |
|
103 |
@Override |
104 |
public PreverificationError[] preverify(IMTJProject mtjProject, |
105 |
IResource[] toVerify, IFolder outputFolder, IProgressMonitor monitor) |
106 |
throws CoreException { |
107 |
ArrayList allErrors = new ArrayList(); |
108 |
|
109 |
// Create the temporary file of commands for |
110 |
// the verifier |
111 |
ensureFolderExists(outputFolder, monitor); |
112 |
|
113 |
// construct the command line to call proguard |
114 |
ArrayList baseArguments = constructCommandLine(); |
115 |
|
116 |
// read the final arguments that will be added to proguard command line. those parameters should be added |
117 |
// after the -injar parameter |
118 |
File outputFile = outputFolder.getLocation().toFile(); |
119 |
String[] configurationParameters = getProguardFinalConfigurationParameters(mtjProject, outputFile.getAbsolutePath()); |
120 |
|
121 |
ArrayList arguments = new ArrayList(baseArguments); |
122 |
|
123 |
//add the output folder |
124 |
addOutputToInJar (arguments, mtjProject); |
125 |
|
126 |
for (int i = 0; i < toVerify.length; i++) { |
127 |
IResource resource = toVerify[i]; |
128 |
|
129 |
//add projects and jar files |
130 |
switch (resource.getType()) { |
131 |
case IResource.FOLDER: |
132 |
case IResource.PROJECT: |
133 |
addFileToInJar(arguments, resource.getLocation().toOSString()); |
134 |
break; |
135 |
|
136 |
case IResource.FILE: |
137 |
if (resource.getName().endsWith(".jar")) { |
138 |
addFileToInJar(arguments, resource.getLocation().toOSString()); |
139 |
} |
140 |
break; |
141 |
} |
142 |
|
143 |
if (commandLength(arguments) > MAX_COMMAND_LENGTH) { |
144 |
// Configuration parameters |
145 |
for (String configuration:configurationParameters) { |
146 |
arguments.add(configuration); |
147 |
} |
148 |
|
149 |
// Launch the system process |
150 |
String[] commandLine = (String[]) arguments |
151 |
.toArray(new String[arguments.size()]); |
152 |
PreverificationError[] errors = runPreverifier(commandLine, |
153 |
null, monitor); |
154 |
allErrors.addAll(Arrays.asList(errors)); |
155 |
|
156 |
arguments = new ArrayList(baseArguments); |
157 |
//add the output folder |
158 |
addOutputToInJar (arguments, mtjProject); |
159 |
} |
160 |
} |
161 |
|
162 |
if (arguments.size() != baseArguments.size()) { |
163 |
for (String configuration:configurationParameters) { |
164 |
arguments.add(configuration); |
165 |
} |
166 |
|
167 |
// Launch the system process |
168 |
String[] commandLine = (String[]) arguments |
169 |
.toArray(new String[arguments.size()]); |
170 |
PreverificationError[] errors = runPreverifier(commandLine, null, |
171 |
monitor); |
172 |
allErrors.addAll(Arrays.asList(errors)); |
173 |
} |
174 |
|
175 |
return (PreverificationError[]) allErrors |
176 |
.toArray(new PreverificationError[allErrors.size()]); |
177 |
} |
178 |
|
179 |
@Override |
180 |
public PreverificationError[] preverifyJarFile( |
181 |
IMTJProject mtjProject, File jarFile, |
182 |
IFolder outputFolder, IProgressMonitor monitor) |
183 |
throws CoreException{ |
184 |
// Rather than trying to preverify a jar file, we will expand it |
185 |
// first and then preverify against the expanded classes. |
186 |
File srcDirectory = new File(""); |
187 |
try { |
188 |
srcDirectory = TemporaryFileManager.instance.createTempDirectory( |
189 |
jarFile.getName().replace('.', '_') + "_", ".tmp"); |
190 |
} catch (IOException ioe) { |
191 |
IStatus status = new Status(IStatus.ERROR, |
192 |
IMTJCoreConstants.PLUGIN_ID, "Failed to create directory.", |
193 |
ioe); |
194 |
throw new CoreException(status); |
195 |
} |
196 |
srcDirectory.mkdirs(); |
197 |
|
198 |
try { |
199 |
Utils.extractArchive(jarFile, srcDirectory); |
200 |
} catch (SecurityException se) { |
201 |
IStatus status = new Status(IStatus.ERROR, |
202 |
IMTJCoreConstants.PLUGIN_ID, |
203 |
"Failed to inflate jar file due to a security violation.", |
204 |
se); |
205 |
throw new CoreException(status); |
206 |
} catch (IOException ioe) { |
207 |
IStatus status = new Status(IStatus.ERROR, |
208 |
IMTJCoreConstants.PLUGIN_ID, "Failed to inflate jar file.", |
209 |
ioe); |
210 |
throw new CoreException(status); |
211 |
} |
212 |
|
213 |
// Create the target directory for the preverification. We will |
214 |
// tell the preverifier to use this when doing the preverification. |
215 |
File tgtDirectory = new File(""); |
216 |
try { |
217 |
tgtDirectory = TemporaryFileManager.instance.createTempDirectory( |
218 |
jarFile.getName().replace('.', '_') + "_", ".tmp"); |
219 |
} catch (IOException ioe) { |
220 |
IStatus status = new Status(IStatus.ERROR, |
221 |
IMTJCoreConstants.PLUGIN_ID, "Failed to create directory.", |
222 |
ioe); |
223 |
throw new CoreException(status); |
224 |
} |
225 |
tgtDirectory.mkdirs(); |
226 |
|
227 |
ArrayList arguments = constructCommandLine(); |
228 |
arguments.add(srcDirectory.toString()); |
229 |
|
230 |
// Launch the system process |
231 |
String[] commandLine = (String[]) arguments |
232 |
.toArray(new String[arguments.size()]); |
233 |
PreverificationError[] errors = runPreverifier(commandLine, |
234 |
null, monitor); |
235 |
|
236 |
// TODO we need to test the outcome of the previous before going much |
237 |
// further |
238 |
// here... |
239 |
// Copy all of the non-class resources so they end up back in the |
240 |
// jar file |
241 |
FileFilter classFilter = new FileFilter() { |
242 |
public boolean accept(File pathname) { |
243 |
return pathname.isDirectory() |
244 |
|| !pathname.getName().endsWith(".class"); |
245 |
} |
246 |
}; |
247 |
try { |
248 |
Utils.copy(srcDirectory, tgtDirectory, classFilter); |
249 |
} catch (SecurityException se) { |
250 |
IStatus status = new Status(IStatus.ERROR, |
251 |
IMTJCoreConstants.PLUGIN_ID, |
252 |
"Failed copy specified source due to a security violation.", |
253 |
se); |
254 |
throw new CoreException(status); |
255 |
} catch (IOException ioe) { |
256 |
IStatus status = new Status(IStatus.ERROR, |
257 |
IMTJCoreConstants.PLUGIN_ID, "Failed to copy specified source.", |
258 |
ioe); |
259 |
throw new CoreException(status); |
260 |
} |
261 |
|
262 |
// Finally, re-jar the output of the pre-verification into the requested |
263 |
// jar file... |
264 |
File outputJarFile = new File(outputFolder.getLocation().toFile(), |
265 |
jarFile.getName()); |
266 |
try { |
267 |
Utils.createArchive(outputJarFile, tgtDirectory); |
268 |
} catch (IOException ioe) { |
269 |
IStatus status = new Status(IStatus.ERROR, |
270 |
IMTJCoreConstants.PLUGIN_ID, "Failed to create zip source folder.", |
271 |
ioe); |
272 |
throw new CoreException(status); |
273 |
} |
274 |
|
275 |
return errors; |
276 |
} |
277 |
|
278 |
public void loadUsing(IPersistenceProvider persistenceProvider) |
279 |
throws PersistenceException { |
280 |
// Not necessary to load any data |
281 |
} |
282 |
|
283 |
public void storeUsing(IPersistenceProvider persistenceProvider) |
284 |
throws PersistenceException { |
285 |
// Not necessary to store any data |
286 |
} |
287 |
|
288 |
/** |
289 |
* Run the preverifier program and capture the errors that occurred during |
290 |
* pre-verification. |
291 |
* |
292 |
* @param commandLine |
293 |
* @param environment |
294 |
* @throws CoreException |
295 |
*/ |
296 |
private PreverificationError[] runPreverifier(String[] commandLine, |
297 |
String[] environment, IProgressMonitor monitor) |
298 |
throws CoreException { |
299 |
final ArrayList errorList = new ArrayList(); |
300 |
|
301 |
IProcess process = Utils.launchApplication(commandLine, null, |
302 |
environment, "Preverifier", "CLDC Preverifier"); |
303 |
|
304 |
// Listen on the process output streams |
305 |
IStreamsProxy proxy = process.getStreamsProxy(); |
306 |
if (BuildLoggingConfiguration.getInstance().isPreverifierOutputEnabled()) { |
307 |
BuildConsoleProxy.getInstance() |
308 |
.traceln("======================== Launching Preverification ========================="); |
309 |
BuildConsoleProxy.getInstance().addConsoleStreamListener( |
310 |
IBuildConsoleProxy.Stream.ERROR, proxy |
311 |
.getErrorStreamMonitor()); |
312 |
BuildConsoleProxy.getInstance().addConsoleStreamListener( |
313 |
IBuildConsoleProxy.Stream.OUTPUT, proxy |
314 |
.getOutputStreamMonitor()); |
315 |
} |
316 |
|
317 |
proxy.getErrorStreamMonitor().addListener(new IStreamListener() { |
318 |
public void streamAppended(String text, IStreamMonitor monitor) { |
319 |
handleErrorReceived(text, errorList); |
320 |
} |
321 |
}); |
322 |
|
323 |
// Wait until completion |
324 |
while ((!monitor.isCanceled()) && (!process.isTerminated())) { |
325 |
try { |
326 |
Thread.sleep(100); |
327 |
} catch (InterruptedException e) { |
328 |
} |
329 |
; |
330 |
} |
331 |
|
332 |
if (BuildLoggingConfiguration.getInstance().isPreverifierOutputEnabled()) { |
333 |
BuildConsoleProxy.getInstance() |
334 |
.traceln("======================== Preverification exited with code: " |
335 |
+ process.getExitValue()); |
336 |
} |
337 |
|
338 |
return (PreverificationError[]) errorList |
339 |
.toArray(new PreverificationError[errorList.size()]); |
340 |
} |
341 |
|
342 |
/** |
343 |
* Handle the arrival of text on the error stream. |
344 |
* |
345 |
* TODO Change to support proguard error messages |
346 |
* |
347 |
* @param text |
348 |
* @param errorList |
349 |
*/ |
350 |
private void handleErrorReceived(String text, List errorList) { |
351 |
text = text.trim(); |
352 |
Matcher matcher = PREV_ERR_PATTERN.matcher(text); |
353 |
if (matcher.find()) { |
354 |
// Found a match for the error... |
355 |
if (matcher.groupCount() > 0) { |
356 |
final String classname = matcher.group(1); |
357 |
|
358 |
String errorText = "Error preverifying class"; |
359 |
if (matcher.end() < text.length()) { |
360 |
StringBuffer sb = new StringBuffer(errorText); |
361 |
sb.append(": "); |
362 |
|
363 |
String detail = text.substring(matcher.end()); |
364 |
detail = detail.trim(); |
365 |
sb.append(detail); |
366 |
errorText = sb.toString(); |
367 |
} |
368 |
|
369 |
IClassErrorInformation classInfo = new IClassErrorInformation() { |
370 |
public String getName() { |
371 |
return classname; |
372 |
} |
373 |
|
374 |
public String getSourceFile() { |
375 |
return null; |
376 |
} |
377 |
}; |
378 |
|
379 |
PreverificationErrorLocation location = new PreverificationErrorLocation( |
380 |
PreverificationErrorLocationType.UNKNOWN_LOCATION, |
381 |
classInfo); |
382 |
PreverificationError error = new PreverificationError( |
383 |
PreverificationErrorType.UNKNOWN_ERROR, location, text); |
384 |
errorList.add(error); |
385 |
} |
386 |
} else { |
387 |
MTJLogger.log(IStatus.WARNING, text); |
388 |
} |
389 |
} |
390 |
/** |
391 |
* Ensure the specified output folder exists or create if it does not |
392 |
* already exist. |
393 |
* |
394 |
* @param folder |
395 |
* @param monitor |
396 |
* @throws CoreException |
397 |
*/ |
398 |
private void ensureFolderExists(IFolder folder, IProgressMonitor monitor) |
399 |
throws CoreException { |
400 |
// Make sure the output folder exists before we start |
401 |
if (!folder.exists()) { |
402 |
folder.create(true, true, monitor); |
403 |
} |
404 |
} |
405 |
|
406 |
/** |
407 |
* Construct the command line for the specified pre-verification. |
408 |
* |
409 |
* @param midletProject |
410 |
* @param target |
411 |
* @return |
412 |
* @throws CoreException |
413 |
*/ |
414 |
private ArrayList constructCommandLine() throws CoreException { |
415 |
ArrayList arguments = new ArrayList(); |
416 |
|
417 |
// The program we are running... |
418 |
arguments.add(this.getJavaExecutable().getAbsolutePath()); |
419 |
arguments.add("-jar"); |
420 |
arguments.add(this.proguardJarFilePath); |
421 |
|
422 |
return arguments; |
423 |
} |
424 |
|
425 |
/** |
426 |
* Return the parameters to be used for controlling the proguard preverifier |
427 |
* |
428 |
* @param midletProject |
429 |
* @return |
430 |
* @throws CoreException if an error occurs working with the MIDlet project. |
431 |
*/ |
432 |
private String [] getProguardFinalConfigurationParameters(IMTJProject mtjProject, String output) throws CoreException { |
433 |
return new String [] { |
434 |
"-outjars", |
435 |
"'"+output+"'", |
436 |
"-libraryjars", |
437 |
this.getFullClasspath(mtjProject), |
438 |
"'-ignorewarnings'", |
439 |
"-dontusemixedcaseclassnames", |
440 |
"-dontshrink", |
441 |
"-dontoptimize", |
442 |
"-dontobfuscate", |
443 |
"-microedition" |
444 |
}; |
445 |
} |
446 |
|
447 |
/** |
448 |
* Get the full classpath including all J2ME libraries. |
449 |
* |
450 |
* @param midletProject |
451 |
* @return |
452 |
* @throws CoreException |
453 |
*/ |
454 |
private String getFullClasspath(IMTJProject mtjProject) |
455 |
throws CoreException { |
456 |
IJavaProject javaProject = mtjProject.getJavaProject(); |
457 |
|
458 |
String[] entries = JavaRuntime.computeDefaultRuntimeClassPath(javaProject); |
459 |
|
460 |
// start in 1 to remove the output folder from the runtime. the output folder should be included in the |
461 |
// -injar proguard options |
462 |
StringBuffer sb = new StringBuffer(); |
463 |
for (int i = 1; i < entries.length; i++) { |
464 |
if (i != 1) { |
465 |
sb.append(File.pathSeparatorChar); |
466 |
} |
467 |
sb.append("'"+entries[i]+"'"); |
468 |
} |
469 |
|
470 |
return sb.toString(); |
471 |
} |
472 |
|
473 |
/** |
474 |
* Add the output folder target to be verified. |
475 |
* |
476 |
* @param args |
477 |
* @param resource |
478 |
* @throws JavaModelException |
479 |
*/ |
480 |
private void addOutputToInJar(List args, IMTJProject mtjProject) |
481 |
throws JavaModelException { |
482 |
|
483 |
// Find the source directory this class resides in |
484 |
String outputPath = null; |
485 |
|
486 |
String s1 = mtjProject.getProject().getLocation().toOSString(); |
487 |
String s2 = mtjProject.getJavaProject().getOutputLocation().removeFirstSegments(1).toOSString(); |
488 |
//IPath.SEPARATOR |
489 |
outputPath = s1 + File.separatorChar + s2; |
490 |
|
491 |
if (outputPath != null) { |
492 |
args.add("-injars"); |
493 |
args.add("'"+outputPath+"'"); |
494 |
} |
495 |
} |
496 |
|
497 |
/** |
498 |
* Add any file to be preverified |
499 |
* |
500 |
* @param args |
501 |
* @param resource |
502 |
* @throws JavaModelException |
503 |
*/ |
504 |
private void addFileToInJar(List args, String filePath) |
505 |
throws JavaModelException { |
506 |
if (filePath != null) { |
507 |
args.add("-injars"); |
508 |
args.add("'"+filePath+"'"); |
509 |
} |
510 |
} |
511 |
|
512 |
|
513 |
/** |
514 |
* Return the length of the command-line length given the specified argument |
515 |
* list. |
516 |
* |
517 |
* @param arguments |
518 |
* @return |
519 |
*/ |
520 |
private int commandLength(ArrayList arguments) { |
521 |
int length = 0; |
522 |
|
523 |
Iterator iter = arguments.iterator(); |
524 |
while (iter.hasNext()) { |
525 |
Object arg = (Object) iter.next(); |
526 |
length += arg.toString().length(); |
527 |
if (iter.hasNext()) |
528 |
length++; |
529 |
} |
530 |
|
531 |
return length; |
532 |
} |
533 |
|
534 |
/** |
535 |
* Return the Java executable to be used for launching this device. |
536 |
* Code from org.eclipse.mtj.core.model.implJavaEmulatorDevice. |
537 |
* TODO Refactore to have a common place for it |
538 |
* |
539 |
* @return |
540 |
*/ |
541 |
private File getJavaExecutable() { |
542 |
File executable = null; |
543 |
|
544 |
IVMInstall vmInstall = JavaRuntime.getDefaultVMInstall(); |
545 |
File installLocation = vmInstall.getInstallLocation(); |
546 |
|
547 |
for (int i = 0; i < CANDIDATE_JAVA_LOCATIONS.length; i++) { |
548 |
String javaLocation = CANDIDATE_JAVA_LOCATIONS[i]; |
549 |
File javaExecutable = new File(installLocation, javaLocation); |
550 |
if (javaExecutable.exists()) { |
551 |
executable = javaExecutable; |
552 |
break; |
553 |
} |
554 |
} |
555 |
return executable; |
556 |
} |
557 |
|
558 |
@Override |
559 |
public File getPreverifierExecutable() { |
560 |
// TODO Auto-generated method stub |
561 |
return null; |
562 |
} |
563 |
} |