Subversion Repository Public Repository

litesoft

Diff Revisions 958 vs 959 for /trunk/Java/ScarPlus/src/com/esotericsoftware/scar/Scar.java

Diff revisions: vs.
  @@ -1,1536 +1,1302 @@
1 - package com.esotericsoftware.scar;
2 -
3 - import org.litesoft.logger.*;
4 - import org.litesoft.logger.nonpublic.*;
5 -
6 - import SevenZip.*;
7 - import com.esotericsoftware.filesystem.*;
8 - import com.esotericsoftware.scar.support.*;
9 - import com.esotericsoftware.utils.*;
10 - import com.esotericsoftware.yamlbeans.*;
11 - import com.esotericsoftware.yamlbeans.parser.*;
12 - import com.esotericsoftware.yamlbeans.tokenizer.*;
13 -
14 - import java.io.*;
15 - import java.lang.reflect.*;
16 - import java.net.*;
17 - import java.util.*;
18 - import java.util.Map.*;
19 - import java.util.jar.*;
20 - import java.util.zip.*;
21 -
22 - // BOZO - Add javadocs method.
23 -
24 - /**
25 - * Provides utility methods for common Java build tasks.
26 - */
27 - @SuppressWarnings({"ResultOfMethodCallIgnored"})
28 - public class Scar extends Utils implements ProjectFactory
29 - {
30 - public static final String VERSION = "2.92 - 1.7";
31 -
32 - public static final String DEFAULT_PROJECT_FILE_NAME = "Build";
33 -
34 - public static final String JAVA_EXTENSION = ".java";
35 - public static final String YAML_EXTENSION = ".yaml";
36 -
37 - protected static final Logger LOGGER = LoggerFactory.getLogger( Scar.class );
38 -
39 - protected final ProjectCache mProjectCache = new ProjectCache();
40 -
41 - private Project mLaunchProject;
42 -
43 - /**
44 - * The command line arguments Scar was started with. Empty if Scar was started with no arguments or Scar was not started from
45 - * the command line.
46 - */
47 - public final Arguments mArgs;
48 -
49 - public Scar( Arguments pArgs )
50 - {
51 - mArgs = (pArgs != null) ? pArgs : new Arguments();
52 - }
53 -
54 - @SuppressWarnings({"UnusedDeclaration"})
55 - public Project getLaunchProject()
56 - {
57 - return mLaunchProject;
58 - }
59 -
60 - /**
61 - * Loads the specified project with default values and loads any other projects needed for the "include" property.
62 - *
63 - * @param pCanonicalCurrentDirectory
64 - * @param pPath Path to a YAML project file, or a directory containing a "project.yaml" file.
65 - */
66 - @Override
67 - public Project project( File pCanonicalCurrentDirectory, String pPath )
68 - {
69 - Util.assertNotNull( "CurrentDirectory", pCanonicalCurrentDirectory );
70 - pPath = Util.assertNotEmpty( "Path", pPath );
71 - File zFile = new File( pPath );
72 - if ( !zFile.isAbsolute() )
73 - {
74 - zFile = new File( pCanonicalCurrentDirectory, pPath );
75 - }
76 - try
77 - {
78 - zFile = zFile.getCanonicalFile();
79 - }
80 - catch ( IOException e )
81 - {
82 - throw new WrappedIOException( e );
83 - }
84 -
85 - String zPath = zFile.getPath();
86 - Project zProject = mProjectCache.getByPath( zPath );
87 - if ( zProject != null )
88 - {
89 - return zProject;
90 - }
91 - try
92 - {
93 - if ( zFile.isFile() ) // Assume Project Build File
94 - {
95 - return mProjectCache.initialize( this, zPath, createProject( zFile, zFile.getParentFile() ) );
96 - }
97 - if ( zFile.isDirectory() ) // Assume Project Dir
98 - {
99 - return mProjectCache.initialize( this, zPath, createProject( findBuildFile( zFile ), zFile, zFile.getName() ) );
100 - }
101 - }
102 - catch ( WrappedIOException e )
103 - {
104 - throw new WrappedIOException( pPath, e );
105 - }
106 - throw new IllegalArgumentException( "Project is Neither a Project File, nor a Project Directory: " + zFile );
107 - }
108 -
109 - protected File findBuildFile( File pProjectDir )
110 - {
111 - File[] zFiles = pProjectDir.listFiles( BuildFileFilter.INSTANCE );
112 - if ( (zFiles == null) || (zFiles.length == 0) )
113 - {
114 - return null;
115 - }
116 - if ( zFiles.length == 1 )
117 - {
118 - return zFiles[0];
119 - }
120 - File zFile = findBuildFile( zFiles, JAVA_EXTENSION );
121 - return (zFile != null) ? zFile : findBuildFile( zFiles, YAML_EXTENSION );
122 - }
123 -
124 - private File findBuildFile( File[] pFiles, String pExtension )
125 - {
126 - File rv = null;
127 - for ( File zFile : pFiles )
128 - {
129 - if ( zFile.getName().endsWith( pExtension ) )
130 - {
131 - if ( rv != null )
132 - {
133 - throw new IllegalStateException( "Found Both:\n " + rv + "\n " + zFile );
134 - }
135 - rv = zFile;
136 - }
137 - }
138 - return rv;
139 - }
140 -
141 - protected Project createProject( File pPossibleBuildFile, File pCanonicalProjectDir )
142 - {
143 - String zBuildFileName = pPossibleBuildFile.getName();
144 - int zDotAt = zBuildFileName.lastIndexOf( '.' );
145 - if ( (zDotAt != -1) && (pCanonicalProjectDir != null) )
146 - {
147 - String zName = zBuildFileName.substring( 0, zDotAt ).trim();
148 - if ( DEFAULT_PROJECT_FILE_NAME.equalsIgnoreCase( zName ) )
149 - {
150 - zName = pCanonicalProjectDir.getName();
151 - }
152 - return createProject( pPossibleBuildFile, pCanonicalProjectDir, zName );
153 - }
154 - throw new IllegalArgumentException( "Unacceptable Project Path or Name, Project File " + pPossibleBuildFile );
155 - }
156 -
157 - protected Project createProject( File pBuildFile, File pCanonicalProjectDir, String pProjectName )
158 - {
159 - if ( pBuildFile == null )
160 - {
161 - throw new IllegalArgumentException( "No 'Build.java' or 'Build.yaml' file found in Project Directory: " + pCanonicalProjectDir );
162 - }
163 - String zBuildFileName = pBuildFile.getName();
164 - if ( zBuildFileName.endsWith( JAVA_EXTENSION ) )
165 - {
166 - return createJavaProject( pBuildFile, pCanonicalProjectDir, pProjectName );
167 - }
168 - if ( zBuildFileName.endsWith( YAML_EXTENSION ) )
169 - {
170 - return createYamlProject( pBuildFile, pCanonicalProjectDir, pProjectName );
171 - }
172 - throw new IllegalArgumentException( pBuildFile + " was NOT either a '.java' or a '.yaml' file!" );
173 - }
174 -
175 - protected Project createYamlProject( File pYamlBuildFile, File pCanonicalProjectDir, String pProjectName )
176 - {
177 - List<String> zLines = readLines( pYamlBuildFile );
178 - int at = findLine( zLines, "---" );
179 - String zYAML, zCode = null;
180 - if ( at == -1 )
181 - {
182 - zYAML = mergeLines( zLines, 0 );
183 - }
184 - else
185 - {
186 - zYAML = mergeLines( zLines, 0, at );
187 - zCode = Util.noEmpty( mergeLines( zLines, at + 1 ) );
188 - }
189 - Map<Object, Object> zData = (zYAML.length() == 0) ? new HashMap<Object, Object>() : parseYAML( pCanonicalProjectDir, zYAML );
190 - Class<? extends Project> zClass = createYamlProjectClass();
191 - if ( zCode != null )
192 - {
193 - zClass = createYamlCodeProjectClass( zClass, zCode, at + 1 );
194 - }
195 - return instantiate( pYamlBuildFile, zClass, pCanonicalProjectDir, pProjectName, zData );
196 - }
197 -
198 - protected Project createJavaProject( File pJavaBuildFile, File pCanonicalProjectDir, String pProjectName )
199 - {
200 - String zFile = mergeLines( readLines( pJavaBuildFile ), 0 );
201 - Class<? extends Project> zClass = createJavaProjectClass();
202 - zClass = createJavaCodeProjectClass( zClass, zFile );
203 - return instantiate( pJavaBuildFile, zClass, pCanonicalProjectDir, pProjectName, null );
204 - }
205 -
206 - protected Project instantiate( File zProjectFile, Class<? extends Project> pClass, File pCanonicalProjectDir, String pProjectName, Map<Object, Object> pData )
207 - {
208 - return instantiate( pClass, new ProjectParameters( zProjectFile, pProjectName, pCanonicalProjectDir, pData ) );
209 - }
210 -
211 - protected Project instantiate( Class<? extends Project> pClass, ProjectParameters pParameters )
212 - {
213 - Throwable zCause;
214 - try
215 - {
216 - Constructor zConstructor = pClass.getConstructor( ProjectParameters.class );
217 - return (Project) zConstructor.newInstance( pParameters );
218 - }
219 - catch ( NoSuchMethodException e )
220 - {
221 - zCause = e;
222 - }
223 - catch ( InvocationTargetException e )
224 - {
225 - zCause = e;
226 - }
227 - catch ( ClassCastException e )
228 - {
229 - zCause = e;
230 - }
231 - catch ( InstantiationException e )
232 - {
233 - zCause = e;
234 - }
235 - catch ( IllegalAccessException e )
236 - {
237 - zCause = e;
238 - }
239 - catch ( RuntimeException e )
240 - {
241 - zCause = e;
242 - }
243 - throw new RuntimeException( "Unable to Instantiate Project Class for Project: " + pParameters.getName() + " in dir " + pParameters.getCanonicalProjectDir(), zCause );
244 - }
245 -
246 - protected Class<? extends Project> createYamlProjectClass()
247 - {
248 - return Project.class;
249 - }
250 -
251 - protected Class<? extends Project> createJavaProjectClass()
252 - {
253 - return Project.class;
254 - }
255 -
256 - protected Map<Object, Object> parseYAML( File pProjectDir, String pYAML )
257 - {
258 - final String zProjectDir = pProjectDir.getPath().replace( '\\', '/' );
259 -
260 - YamlReader yamlReader = new YamlReader( new StringReader( pYAML ) )
261 - {
262 - @Override
263 - protected Object readValue( Class type, Class elementType, Class defaultType )
264 - throws YamlException, Parser.ParserException, Tokenizer.TokenizerException
265 - {
266 - Object value = super.readValue( type, elementType, defaultType );
267 - if ( value instanceof String )
268 - {
269 - value = ((String) value).replaceAll( "\\$dir\\$", zProjectDir );
270 - }
271 - return value;
272 - }
273 - };
274 - try
275 - {
276 - //noinspection unchecked
277 - return yamlReader.read( HashMap.class );
278 - }
279 - catch ( IOException e )
280 - {
281 - throw new WrappedIOException( e );
282 - }
283 - finally
284 - {
285 - try
286 - {
287 - yamlReader.close();
288 - }
289 - catch ( IOException e )
290 - {
291 - throw new WrappedIOException( e );
292 - }
293 - }
294 - }
295 -
296 - protected String mergeLines( List<String> pLines, int pFrom )
297 - {
298 - return mergeLines( pLines, pFrom, pLines.size() );
299 - }
300 -
301 - protected String mergeLines( List<String> pLines, int pFrom, int pToExclusive )
302 - {
303 - StringBuilder sb = new StringBuilder();
304 - while ( (pFrom < pToExclusive) && (pFrom < pLines.size()) )
305 - {
306 - sb.append( pLines.get( pFrom++ ) ).append( '\n' );
307 - }
308 - return sb.toString();
309 - }
310 -
311 - private int findLine( List<String> pLines, String zLine )
312 - {
313 - for ( int i = 0; i < pLines.size(); i++ )
314 - {
315 - if ( zLine.equals( pLines.get( i ) ) )
316 - {
317 - return i;
318 - }
319 - }
320 - return -1;
321 - }
322 -
323 - protected List<String> readLines( File zFile )
324 - {
325 - BufferedReader fileReader;
326 - try
327 - {
328 - fileReader = new BufferedReader( new FileReader( zFile ) );
329 - }
330 - catch ( FileNotFoundException e )
331 - {
332 - throw new WrappedIOException( e );
333 - }
334 - try
335 - {
336 - List<String> lines = new ArrayList<String>();
337 - for ( String line; null != (line = fileReader.readLine()); )
338 - {
339 - lines.add( line.trim() );
340 - }
341 - Closeable c = fileReader;
342 - fileReader = null;
343 - c.close();
344 - return lines;
345 - }
346 - catch ( IOException e )
347 - {
348 - if ( fileReader != null )
349 - {
350 - try
351 - {
352 - fileReader.close();
353 - }
354 - catch ( IOException e1 )
355 - {
356 - // Whatever!
357 - }
358 - }
359 - throw new WrappedIOException( e );
360 - }
361 - }
362 -
363 - protected void initLoggerFactory()
364 - {
365 - LoggerFactory.init( createLoggerLevel() );
366 - }
367 -
368 - protected LoggerLevel createLoggerLevel()
369 - {
370 - int zLevel = LoggerLevel.ERROR;
371 - String[] zLevels = LoggerLevel.LEVELS;
372 - for ( int i = 0; i < zLevels.length; i++ )
373 - {
374 - if ( null != mArgs.get( zLevels[i] ) )
375 - {
376 - zLevel = i;
377 - break;
378 - }
379 - }
380 - final int zLoggerLevel = zLevel;
381 - return new LoggerLevel()
382 - {
383 - @Override
384 - public int getEnabledLevel( String pClassName )
385 - {
386 - return zLoggerLevel;
387 - }
388 - };
389 - }
390 -
391 - protected Class<Project> createYamlCodeProjectClass( Class<? extends Project> pClass, String pCode, int pOverheadStartLines )
392 - {
393 - throw new UnsupportedOperationException(); // todo: See - executeDocument();
394 - }
395 -
396 - protected Class<Project> createJavaCodeProjectClass( Class<? extends Project> pClass, String pCode )
397 - {
398 - throw new UnsupportedOperationException(); // todo: See - executeDocument()!;
399 - }
400 -
401 - /**
402 - * Cleans All projects - Normally called reflectively
403 - */
404 - @SuppressWarnings({"UnusedDeclaration"})
405 - public void cleanAll()
406 - {
407 - progress( "CleanAll" );
408 - Set<Project> zProjects = mProjectCache.getAllProjects();
409 - for ( Project zProject : zProjects )
410 - {
411 - zProject.clean();
412 - }
413 - }
414 -
415 - /**
416 - * Builds the Launch Project - Normally called reflectively
417 - */
418 - @SuppressWarnings({"UnusedDeclaration"})
419 - public void build()
420 - {
421 - mLaunchProject.build();
422 - }
423 -
424 - /**
425 - * Versions the current (GWT) Launch Project - Normally called reflectively
426 - */
427 - @SuppressWarnings({"UnusedDeclaration"})
428 - public void versionGWT()
429 - {
430 - mLaunchProject.versionGWT();
431 - }
432 -
433 - /// todo: ==================================================================================================================
434 - /// todo: ==================================================================================================================
435 - /// todo: ==================================================================================================================
436 - /// todo: ==================================================================================================================
437 - /// todo: ==================================================================================================================
438 - /// todo: ==================================================================================================================
439 - /// todo: ==================================================================================================================
440 - /// todo: ==================================================================================================================
441 - /// todo: ==================================================================================================================
442 - /// todo: ==================================================================================================================
443 - /// todo: ==================================================================================================================
444 - /// todo: vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv Here be Dragons vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
445 -
446 - /**
447 - * Loads the specified project with the specified defaults and loads any other projects needed for the "include" property.
448 - *
449 - * @param path Path to a YAML project file, or a directory containing a "project.yaml" file.
450 - */
451 - // public Project project( String path, Project defaults )
452 - // {
453 - // Util.assertNotNull( "path", path );
454 - // Util.assertNotNull( "defaults", defaults );
455 - //
456 - // Project actualProject = new Project( path, this );
457 - //
458 - // Project project = new Project();
459 - // project.replace( defaults );
460 - //
461 - // File parent = actualProject.getDirectory().getParentFile();
462 - // while ( parent != null )
463 - // {
464 - // File includeFile = new File( parent, "include.yaml" );
465 - // if ( includeFile.exists() )
466 - // {
467 - // try
468 - // {
469 - // project.replace( project( includeFile.getAbsolutePath(), defaults ) );
470 - // }
471 - // catch ( RuntimeException ex )
472 - // {
473 - // throw new RuntimeException( "Error loading included project: " + includeFile.getAbsolutePath(), ex );
474 - // }
475 - // }
476 - // parent = parent.getParentFile();
477 - // }
478 - //
479 - // for ( String include : actualProject.getInclude() )
480 - // {
481 - // try
482 - // {
483 - // project.replace( project( actualProject.path( include ), defaults ) );
484 - // }
485 - // catch ( RuntimeException ex )
486 - // {
487 - // throw new RuntimeException( "Error loading included project: " + actualProject.path( include ), ex );
488 - // }
489 - // }
490 - // project.replace( actualProject );
491 - // return project;
492 - // }
493 -
494 - /**
495 - * Removes any code signatures on the specified JAR. Removes any signature files in the META-INF directory and removes any
496 - * signature entries from the JAR's manifest.
497 - *
498 - * @return The path to the JAR file.
499 - */
500 - public String unsign( String jarFile )
501 - {
502 - Util.assertNotNull( "jarFile", jarFile );
503 -
504 - progress( "Removing signature from JAR: " + jarFile );
505 -
506 - File tempFile;
507 - try
508 - {
509 - tempFile = File.createTempFile( "scar", "removejarsig" );
510 - }
511 - catch ( IOException e )
512 - {
513 - throw new WrappedIOException( e );
514 - }
515 - JarOutputStream jarOutput = null;
516 - JarInputStream jarInput = null;
517 - try
518 - {
519 - jarOutput = new JarOutputStream( new FileOutputStream( tempFile ) );
520 - jarInput = new JarInputStream( new FileInputStream( jarFile ) );
521 - Manifest manifest = jarInput.getManifest();
522 - if ( manifest != null )
523 - {
524 - // Remove manifest file entries.
525 - manifest.getEntries().clear();
526 - jarOutput.putNextEntry( new JarEntry( "META-INF/MANIFEST.MF" ) );
527 - manifest.write( jarOutput );
528 - }
529 - byte[] buffer = new byte[4096];
530 - while ( true )
531 - {
532 - JarEntry entry = jarInput.getNextJarEntry();
533 - if ( entry == null )
534 - {
535 - break;
536 - }
537 - String name = entry.getName();
538 - // Skip signature files.
539 - if ( name.startsWith( "META-INF/" ) && (name.endsWith( ".SF" ) || name.endsWith( ".DSA" ) || name.endsWith( ".RSA" )) )
540 - {
541 - continue;
542 - }
543 - jarOutput.putNextEntry( new JarEntry( name ) );
544 - while ( true )
545 - {
546 - int length = jarInput.read( buffer );
547 - if ( length == -1 )
548 - {
549 - break;
550 - }
551 - jarOutput.write( buffer, 0, length );
552 - }
553 - }
554 - jarInput.close();
555 - jarOutput.close();
556 - copyFile( tempFile.getAbsolutePath(), jarFile );
557 - }
558 - catch ( IOException ex )
559 - {
560 - throw new WrappedIOException( "Error unsigning JAR file: " + jarFile, ex );
561 - }
562 - finally
563 - {
564 - dispose( jarInput );
565 - dispose( jarOutput );
566 - tempFile.delete();
567 - }
568 - return jarFile;
569 - }
570 -
571 - /**
572 - * Signs the specified JAR.
573 - *
574 - * @return The path to the JAR.
575 - */
576 - public String sign( String jarFile, String keystoreFile, String alias, String password )
577 - {
578 - Util.assertNotNull( "jarFile", jarFile );
579 - Util.assertNotNull( "keystoreFile", keystoreFile );
580 - Util.assertNotNull( "alias", alias );
581 - Util.assertNotNull( "password", password );
582 - if ( password.length() < 6 )
583 - {
584 - throw new IllegalArgumentException( "password must be 6 or more characters." );
585 - }
586 - progress( "Signing JAR (" + keystoreFile + ", " + alias + ":" + password + "): " + jarFile );
587 -
588 - shell( "jarsigner", "-keystore", keystoreFile, "-storepass", password, "-keypass", password, jarFile, alias );
589 - return jarFile;
590 - }
591 -
592 - /**
593 - * Encodes the specified file with pack200. The resulting filename is the filename plus ".pack". The file is deleted after
594 - * encoding.
595 - *
596 - * @return The path to the encoded file.
597 - */
598 - public String pack200( String jarFile )
599 - {
600 - String packedFile = pack200( jarFile, jarFile + ".pack" );
601 - delete( jarFile );
602 - return packedFile;
603 - }
604 -
605 - /**
606 - * Encodes the specified file with pack200.
607 - *
608 - * @return The path to the encoded file.
609 - */
610 - public String pack200( String jarFile, String packedFile )
611 - {
612 - Util.assertNotNull( "jarFile", jarFile );
613 - Util.assertNotNull( "packedFile", packedFile );
614 -
615 - progress( "Packing JAR: " + jarFile + " -> " + packedFile );
616 -
617 - shell( "pack200", "--no-gzip", "--segment-limit=-1", "--no-keep-file-order", "--effort=7", "--modification-time=latest", packedFile, jarFile );
618 - return packedFile;
619 - }
620 -
621 - /**
622 - * Decodes the specified file with pack200. The filename must end in ".pack" and the resulting filename has this stripped. The
623 - * encoded file is deleted after decoding.
624 - *
625 - * @return The path to the decoded file.
626 - */
627 - public String unpack200( String packedFile )
628 - {
629 - Util.assertNotNull( "packedFile", packedFile );
630 - if ( !packedFile.endsWith( ".pack" ) )
631 - {
632 - throw new IllegalArgumentException( "packedFile must end with .pack: " + packedFile );
633 - }
634 -
635 - String jarFile = unpack200( packedFile, substring( packedFile, 0, -5 ) );
636 - delete( packedFile );
637 - return jarFile;
638 - }
639 -
640 - /**
641 - * Decodes the specified file with pack200.
642 - *
643 - * @return The path to the decoded file.
644 - */
645 - public String unpack200( String packedFile, String jarFile )
646 - {
647 - Util.assertNotNull( "packedFile", packedFile );
648 - Util.assertNotNull( "jarFile", jarFile );
649 -
650 - progress( "Unpacking JAR: " + packedFile + " -> " + jarFile );
651 -
652 - shell( "unpack200", packedFile, jarFile );
653 - return jarFile;
654 - }
655 -
656 - /**
657 - * Encodes the specified file with GZIP. The resulting filename is the filename plus ".gz". The file is deleted after encoding.
658 - *
659 - * @return The path to the encoded file.
660 - */
661 - public String gzip( String file )
662 - {
663 - String gzipFile = gzip( file, file + ".gz" );
664 - delete( file );
665 - return gzipFile;
666 - }
667 -
668 - /**
669 - * Encodes the specified file with GZIP.
670 - *
671 - * @return The path to the encoded file.
672 - */
673 - public String gzip( String file, String gzipFile )
674 - {
675 - Util.assertNotNull( "file", file );
676 - Util.assertNotNull( "gzipFile", gzipFile );
677 -
678 - progress( "GZIP encoding: " + file + " -> " + gzipFile );
679 -
680 - InputStream input;
681 - try
682 - {
683 - input = new FileInputStream( file );
684 - }
685 - catch ( FileNotFoundException e )
686 - {
687 - throw new WrappedIOException( e );
688 - }
689 - try
690 - {
691 - copyStreamAndCloseEm( input, new GZIPOutputStream( new FileOutputStream( gzipFile ) ) );
692 - }
693 - catch ( IOException e )
694 - {
695 - throw new WrappedIOException( e );
696 - }
697 - finally
698 - {
699 - dispose( input );
700 - }
701 - return gzipFile;
702 - }
703 -
704 - /**
705 - * Decodes the specified GZIP file. The filename must end in ".gz" and the resulting filename has this stripped. The encoded
706 - * file is deleted after decoding.
707 - *
708 - * @return The path to the decoded file.
709 - */
710 - public String ungzip( String gzipFile )
711 - {
712 - Util.assertNotNull( "gzipFile", gzipFile );
713 - if ( !gzipFile.endsWith( ".gz" ) )
714 - {
715 - throw new IllegalArgumentException( "gzipFile must end with .gz: " + gzipFile );
716 - }
717 -
718 - String file = ungzip( gzipFile, substring( gzipFile, 0, -3 ) );
719 - delete( gzipFile );
720 - return file;
721 - }
722 -
723 - /**
724 - * Decodes the specified GZIP file.
725 - *
726 - * @return The path to the decoded file.
727 - */
728 - public String ungzip( String gzipFile, String file )
729 - {
730 - Util.assertNotNull( "gzipFile", gzipFile );
731 - Util.assertNotNull( "file", file );
732 - progress( "GZIP decoding: " + gzipFile + " -> " + file );
733 -
734 - InputStream input;
735 - try
736 - {
737 - input = new GZIPInputStream( new FileInputStream( gzipFile ) );
738 - }
739 - catch ( IOException e )
740 - {
741 - throw new WrappedIOException( e );
742 - }
743 - try
744 - {
745 - copyStreamAndCloseEm( input, new FileOutputStream( file ) );
746 - }
747 - catch ( FileNotFoundException e )
748 - {
749 - throw new WrappedIOException( e );
750 - }
751 - finally
752 - {
753 - dispose( input );
754 - }
755 - return file;
756 - }
757 -
758 - /**
759 - * Encodes the specified files with ZIP.
760 - *
761 - * @return The path to the encoded file.
762 - */
763 - public String zip( Paths paths, String zipFile )
764 - {
765 - Util.assertNotNull( "paths", paths );
766 - Util.assertNotNull( "zipFile", zipFile );
767 - progress( "Creating ZIP (" + paths.count() + " entries): " + zipFile );
768 -
769 - paths.zip( zipFile );
770 - return zipFile;
771 - }
772 -
773 - /**
774 - * Encodes the specified file with LZMA. The resulting filename is the filename plus ".lzma". The file is deleted after
775 - * encoding.
776 - *
777 - * @return The path to the encoded file.
778 - */
779 - public String lzma( String file )
780 - {
781 - String lzmaFile = lzma( file, file + ".lzma" );
782 - delete( file );
783 - return lzmaFile;
784 - }
785 -
786 - /**
787 - * Encodes the specified file with LZMA.
788 - *
789 - * @return The path to the encoded file.
790 - */
791 - public String lzma( String file, String lzmaFile )
792 - {
793 - Util.assertNotNull( "file", file );
794 - Util.assertNotNull( "lzmaFile", lzmaFile );
795 - progress( "LZMA encoding: " + file + " -> " + lzmaFile );
796 -
797 - try
798 - {
799 - LzmaAlone.main( new String[]{"e", file, lzmaFile} );
800 - }
801 - catch ( Exception ex )
802 - {
803 - throw new WrappedIOException( "Error lzma compressing file: " + file, ex );
804 - }
805 - return lzmaFile;
806 - }
807 -
808 - /**
809 - * Decodes the specified LZMA file. The filename must end in ".lzma" and the resulting filename has this stripped. The encoded
810 - * file is deleted after decoding.
811 - *
812 - * @return The path to the decoded file.
813 - */
814 - public String unlzma( String lzmaFile )
815 - {
816 - Util.assertNotNull( "lzmaFile", lzmaFile );
817 - if ( !lzmaFile.endsWith( ".lzma" ) )
818 - {
819 - throw new IllegalArgumentException( "lzmaFile must end with .lzma: " + lzmaFile );
820 - }
821 -
822 - String file = unlzma( lzmaFile, substring( lzmaFile, 0, -5 ) );
823 - delete( lzmaFile );
824 - return file;
825 - }
826 -
827 - /**
828 - * Decodes the specified LZMA file.
829 - *
830 - * @return The path to the decoded file.
831 - */
832 - public String unlzma( String lzmaFile, String file )
833 - {
834 - Util.assertNotNull( "lzmaFile", lzmaFile );
835 - Util.assertNotNull( "file", file );
836 - progress( "LZMA decoding: " + lzmaFile + " -> " + file );
837 -
838 - try
839 - {
840 - LzmaAlone.main( new String[]{"d", lzmaFile, file} );
841 - }
842 - catch ( Exception ex )
843 - {
844 - throw new WrappedIOException( "Error lzma decompressing file: " + file, ex );
845 - }
846 - return file;
847 - }
848 -
849 - /**
850 - * Copies all the JAR and JNLP files from the "dist" directory to a "jws" directory under the "target" directory. It then uses
851 - * the specified keystore to sign each JAR. If the "pack" parameter is true, it also compresses each JAR using pack200 and
852 - * GZIP.
853 - */
854 - public void jws( Project project, boolean pack, String keystoreFile, String alias, String password )
855 - {
856 - Util.assertNotNull( "Project", project );
857 - Util.assertNotNull( "keystoreFile", keystoreFile );
858 - Util.assertNotNull( "alias", alias );
859 - Util.assertNotNull( "password", password );
860 - if ( password.length() < 6 )
861 - {
862 - throw new IllegalArgumentException( "password must be 6 or more characters." );
863 - }
864 -
865 - progress( "JWS: " + project );
866 -
867 - String jwsDir = mkdir( project.path( "$target$/jws/" ) );
868 - String distDir = project.path( "$target$/dist/" );
869 - new Paths( distDir, "*.jar", "*.jnlp" ).copyTo( jwsDir );
870 - for ( String file : new Paths( jwsDir, "*.jar" ).getFullPaths() )
871 - {
872 - sign( unpack200( pack200( unsign( file ) ) ), keystoreFile, alias, password );
873 - }
874 - if ( pack )
875 - {
876 - String unpackedDir = mkdir( jwsDir + "unpacked/" );
877 - String packedDir = mkdir( jwsDir + "packed/" );
878 - for ( String file : new Paths( jwsDir, "*.jar", "!*native*" ).getFullPaths() )
879 - {
880 - String fileName = fileName( file );
881 - String unpackedFile = unpackedDir + fileName;
882 - moveFile( file, unpackedFile );
883 - String packedFile = packedDir + fileName;
884 - gzip( pack200( copyFile( unpackedFile, packedFile ) ) );
885 - }
886 - }
887 - }
888 -
889 - /**
890 - * Generates ".htaccess" and "type map" VAR files in the "jws" directory. These files allow Apache to serve both pack200/GZIP
891 - * JARs and regular JARs, based on capability of the client requesting the JAR.
892 - */
893 - public void jwsHtaccess( Project project )
894 - {
895 - Util.assertNotNull( "Project", project );
896 -
897 - progress( "JWS htaccess: " + project );
898 -
899 - String jwsDir = mkdir( project.path( "$target$/jws/" ) );
900 - for ( String packedFile : new Paths( jwsDir + "packed", "*.jar.pack.gz" ).getFullPaths() )
901 - {
902 - String packedFileName = fileName( packedFile );
903 - String jarFileName = substring( packedFileName, 0, -8 );
904 - FileWriter writer;
905 - try
906 - {
907 - writer = new FileWriter( jwsDir + jarFileName + ".var" );
908 - }
909 - catch ( IOException e )
910 - {
911 - throw new WrappedIOException( e );
912 - }
913 - try
914 - {
915 - writer.write( "URI: packed/" + packedFileName + "\n" );
916 - writer.write( "Content-Type: x-java-archive\n" );
917 - writer.write( "Content-Encoding: pack200-gzip\n" );
918 - writer.write( "URI: unpacked/" + jarFileName + "\n" );
919 - writer.write( "Content-Type: x-java-archive\n" );
920 - }
921 - catch ( IOException e )
922 - {
923 - throw new WrappedIOException( e );
924 - }
925 - finally
926 - {
927 - dispose( writer );
928 - }
929 - }
930 - FileWriter writer;
931 - try
932 - {
933 - writer = new FileWriter( jwsDir + ".htaccess" );
934 - }
935 - catch ( IOException e )
936 - {
937 - throw new WrappedIOException( e );
938 - }
939 - try
940 - {
941 - writer.write( "AddType application/x-java-jnlp-file .jnlp" ); // JNLP mime type.
942 - writer.write( "AddType application/x-java-archive .jar\n" ); // JAR mime type.
943 - writer.write( "AddHandler application/x-type-map .var\n" ); // Enable type maps.
944 - writer.write( "Options +MultiViews\n" );
945 - writer.write( "MultiViewsMatch Any\n" ); // Apache 2.0 only.
946 - writer.write( "<Files *.pack.gz>\n" );
947 - writer.write( "AddEncoding pack200-gzip .jar\n" ); // Enable Content-Encoding header for .jar.pack.gz files.
948 - writer.write( "RemoveEncoding .gz\n" ); // Prevent mod_gzip from messing with the Content-Encoding response.
949 - writer.write( "</Files>\n" );
950 - }
951 - catch ( IOException e )
952 - {
953 - throw new WrappedIOException( e );
954 - }
955 - finally
956 - {
957 - dispose( writer );
958 - }
959 - }
960 -
961 - /**
962 - * Generates a JNLP file in the "jws" directory. JARs in the "jws" directory are included in the JNLP. JARs containing "native"
963 - * and "win", "mac", "linux", or "solaris" are properly included in the native section of the JNLP. The "main" property is used
964 - * for the main class in the JNLP.
965 - *
966 - * @param splashImage Can be null.
967 - */
968 - public void jnlp( Project project, String url, String company, String title, String splashImage )
969 - {
970 - Util.assertNotNull( "Project", project );
971 - Util.assertNotNull( "company", company );
972 - Util.assertNotNull( "title", title );
973 - Util.assertNotNull( "url", url );
974 - if ( !url.startsWith( "http" ) )
975 - {
976 - throw new RuntimeException( "Invalid url: " + url );
977 - }
978 -
979 - if ( LOGGER.debug.isEnabled() )
980 - {
981 - progress( "JNLP: " + project + " (" + url + ", " + company + ", " + title + ", " + splashImage + ")" );
982 - }
983 - else
984 - {
985 - progress( "JNLP: " + project );
986 - }
987 -
988 - if ( !project.hasMain() )
989 - {
990 - throw new RuntimeException( "Unable to generate JNLP: project has no main class" );
991 - }
992 -
993 - int firstSlash = url.indexOf( "/", 7 );
994 - int lastSlash = url.lastIndexOf( "/" );
995 - if ( firstSlash == -1 || lastSlash == -1 )
996 - {
997 - throw new RuntimeException( "Invalid url: " + url );
998 - }
999 - String domain = url.substring( 0, firstSlash + 1 );
1000 - String path = url.substring( firstSlash + 1, lastSlash + 1 );
1001 - String jnlpFile = url.substring( lastSlash + 1 );
1002 -
1003 - String jwsDir = mkdir( project.path( "$target$/jws/" ) );
1004 - FileWriter writer;
1005 - try
1006 - {
1007 - writer = new FileWriter( jwsDir + jnlpFile );
1008 - }
1009 - catch ( IOException e )
1010 - {
1011 - throw new WrappedIOException( e );
1012 - }
1013 - try
1014 - {
1015 - writer.write( "<?xml version='1.0' encoding='utf-8'?>\n" );
1016 - writer.write( "<jnlp spec='1.0+' codebase='" + domain + "' href='" + path + jnlpFile + "'>\n" );
1017 - writer.write( "<information>\n" );
1018 - writer.write( "\t<title>" + title + "</title>\n" );
1019 - writer.write( "\t<vendor>" + company + "</vendor>\n" );
1020 - writer.write( "\t<homepage href='" + domain + "'/>\n" );
1021 - writer.write( "\t<description>" + title + "</description>\n" );
1022 - writer.write( "\t<description kind='short'>" + title + "</description>\n" );
1023 - if ( splashImage != null )
1024 - {
1025 - writer.write( "\t<icon kind='splash' href='" + path + splashImage + "'/>\n" );
1026 - }
1027 - writer.write( "</information>\n" );
1028 - writer.write( "<security>\n" );
1029 - writer.write( "\t<all-permissions/>\n" );
1030 - writer.write( "</security>\n" );
1031 - writer.write( "<resources>\n" );
1032 - writer.write( "\t<j2se href='http://java.sun.com/products/autodl/j2se' version='1.5+' max-heap-size='128m'/>\n" );
1033 -
1034 - // JAR with main class first.
1035 - String projectJarName;
1036 - if ( project.hasVersion() )
1037 - {
1038 - projectJarName = project.format( "$name$-$version$.jar" );
1039 - }
1040 - else
1041 - {
1042 - projectJarName = project.format( "$name$.jar" );
1043 - }
1044 - writer.write( "\t<jar href='" + path + projectJarName + "'/>\n" );
1045 -
1046 - // Rest of JARs, except natives.
1047 - for ( String file : new Paths( jwsDir, "**/*.jar", "!*native*", "!**/" + projectJarName ).getFullPaths() )
1048 - {
1049 - writer.write( "\t<jar href='" + path + fileName( file ) + "'/>\n" );
1050 - }
1051 -
1052 - writer.write( "</resources>\n" );
1053 - Paths nativePaths = new Paths( jwsDir, "*native*win*", "*win*native*" );
1054 - if ( nativePaths.count() == 1 )
1055 - {
1056 - writer.write( "<resources os='Windows'>\n" );
1057 - writer.write( "\t<j2se href='http://java.sun.com/products/autodl/j2se' version='1.5+' max-heap-size='128m'/>\n" );
1058 - writer.write( "\t<nativelib href='" + path + nativePaths.getNames().get( 0 ) + "'/>\n" );
1059 - writer.write( "</resources>\n" );
1060 - }
1061 - nativePaths = new Paths( jwsDir, "*native*mac*", "*mac*native*" );
1062 - if ( nativePaths.count() == 1 )
1063 - {
1064 - writer.write( "<resources os='Mac'>\n" );
1065 - writer.write( "\t<j2se href='http://java.sun.com/products/autodl/j2se' version='1.5+' max-heap-size='128m'/>\n" );
1066 - writer.write( "\t<nativelib href='" + path + nativePaths.getNames().get( 0 ) + "'/>\n" );
1067 - writer.write( "</resources>\n" );
1068 - }
1069 - nativePaths = new Paths( jwsDir, "*native*linux*", "*linux*native*" );
1070 - if ( nativePaths.count() == 1 )
1071 - {
1072 - writer.write( "<resources os='Linux'>\n" );
1073 - writer.write( "\t<j2se href='http://java.sun.com/products/autodl/j2se' version='1.5+' max-heap-size='128m'/>\n" );
1074 - writer.write( "\t<nativelib href='" + path + nativePaths.getNames().get( 0 ) + "'/>\n" );
1075 - writer.write( "</resources>\n" );
1076 - }
1077 - nativePaths = new Paths( jwsDir, "*native*solaris*", "*solaris*native*" );
1078 - if ( nativePaths.count() == 1 )
1079 - {
1080 - writer.write( "<resources os='SunOS'>\n" );
1081 - writer.write( "\t<j2se href='http://java.sun.com/products/autodl/j2se' version='1.5+' max-heap-size='128m'/>\n" );
1082 - writer.write( "\t<nativelib href='" + path + nativePaths.getNames().get( 0 ) + "'/>\n" );
1083 - writer.write( "</resources>\n" );
1084 - }
1085 - writer.write( "<application-desc main-class='" + project.getMain() + "'/>\n" );
1086 - writer.write( "</jnlp>" );
1087 - }
1088 - catch ( IOException e )
1089 - {
1090 - throw new WrappedIOException( e );
1091 - }
1092 - finally
1093 - {
1094 - dispose( writer );
1095 - }
1096 - }
1097 -
1098 - public String lwjglApplet( Project project, String keystoreFile, String alias, String password )
1099 - {
1100 - Util.assertNotNull( "Project", project );
1101 - Util.assertNotNull( "keystoreFile", keystoreFile );
1102 - Util.assertNotNull( "alias", alias );
1103 - Util.assertNotNull( "password", password );
1104 - if ( password.length() < 6 )
1105 - {
1106 - throw new IllegalArgumentException( "password must be 6 or more characters." );
1107 - }
1108 -
1109 - progress( "LWJGL applet: " + project );
1110 -
1111 - String appletDir = mkdir( project.path( "$target$/applet-lwjgl/" ) );
1112 - String distDir = project.path( "$target$/dist/" );
1113 - new Paths( distDir, "**/*.jar", "*.html", "*.htm" ).flatten().copyTo( appletDir );
1114 - for ( String jarFile : new Paths( appletDir, "*.jar" ).getFullPaths() )
1115 - {
1116 - sign( unpack200( pack200( unsign( jarFile ) ) ), keystoreFile, alias, password );
1117 - String fileName = fileName( jarFile );
1118 - if ( fileName.equals( "lwjgl_util_applet.jar" ) || fileName.equals( "lzma.jar" ) )
1119 - {
1120 - continue;
1121 - }
1122 - if ( fileName.contains( "native" ) )
1123 - {
1124 - lzma( jarFile );
1125 - }
1126 - else
1127 - {
1128 - lzma( pack200( jarFile ) );
1129 - }
1130 - }
1131 -
1132 - if ( !new Paths( appletDir, "*.html", "*.htm" ).isEmpty() )
1133 - {
1134 - return appletDir;
1135 - }
1136 - if ( !project.hasMain() )
1137 - {
1138 - LOGGER.debug.log( "Unable to generate applet.html: project has no main class" );
1139 - return appletDir;
1140 - }
1141 - progress( "Generating: applet.html" );
1142 - FileWriter writer;
1143 - try
1144 - {
1145 - writer = new FileWriter( appletDir + "applet.html" );
1146 - }
1147 - catch ( IOException e )
1148 - {
1149 - throw new WrappedIOException( e );
1150 - }
1151 - try
1152 - {
1153 - writer.write( "<html>\n" );
1154 - writer.write( "<head><title>Applet</title></head>\n" );
1155 - writer.write( "<body>\n" );
1156 - writer.write( "<applet code='org.lwjgl.util.applet.AppletLoader' archive='lwjgl_util_applet.jar, lzma.jar' codebase='.' width='640' height='480'>\n" );
1157 - if ( project.hasVersion() )
1158 - {
1159 - writer.write( "<param name='al_version' value='" + project.getVersion() + "'>\n" );
1160 - }
1161 - writer.write( "<param name='al_title' value='" + project + "'>\n" );
1162 - writer.write( "<param name='al_main' value='" + project.getMain() + "'>\n" );
1163 - writer.write( "<param name='al_jars' value='" );
1164 - int i = 0;
1165 - for ( String name : new Paths( appletDir, "*.jar.pack.lzma" ).getNames() )
1166 - {
1167 - if ( i++ > 0 )
1168 - {
1169 - writer.write( ", " );
1170 - }
1171 - writer.write( name );
1172 - }
1173 - writer.write( "'>\n" );
1174 - Paths nativePaths = new Paths( appletDir, "*native*win*.jar.lzma", "*win*native*.jar.lzma" );
1175 - if ( nativePaths.count() == 1 )
1176 - {
1177 - writer.write( "<param name='al_windows' value='" + nativePaths.getNames().get( 0 ) + "'>\n" );
1178 - }
1179 - nativePaths = new Paths( appletDir, "*native*mac*.jar.lzma", "*mac*native*.jar.lzma" );
1180 - if ( nativePaths.count() == 1 )
1181 - {
1182 - writer.write( "<param name='al_mac' value='" + nativePaths.getNames().get( 0 ) + "'>\n" );
1183 - }
1184 - nativePaths = new Paths( appletDir, "*native*linux*.jar.lzma", "*linux*native*.jar.lzma" );
1185 - if ( nativePaths.count() == 1 )
1186 - {
1187 - writer.write( "<param name='al_linux' value='" + nativePaths.getNames().get( 0 ) + "'>\n" );
1188 - }
1189 - nativePaths = new Paths( appletDir, "*native*solaris*.jar.lzma", "*solaris*native*.jar.lzma" );
1190 - if ( nativePaths.count() == 1 )
1191 - {
1192 - writer.write( "<param name='al_solaris' value='" + nativePaths.getNames().get( 0 ) + "'>\n" );
1193 - }
1194 - writer.write( "<param name='al_logo' value='appletlogo.png'>\n" );
1195 - writer.write( "<param name='al_progressbar' value='appletprogress.gif'>\n" );
1196 - writer.write( "<param name='separate_jvm' value='true'>\n" );
1197 - writer.write( "<param name='java_arguments' value='-Dsun.java2d.noddraw=true -Dsun.awt.noerasebackground=true -Dsun.java2d.d3d=false -Dsun.java2d.opengl=false -Dsun.java2d.pmoffscreen=false'>\n" );
1198 - writer.write( "</applet>\n" );
1199 - writer.write( "</body></html>\n" );
1200 - }
1201 - catch ( IOException e )
1202 - {
1203 - throw new WrappedIOException( e );
1204 - }
1205 - finally
1206 - {
1207 - dispose( writer );
1208 - }
1209 - return appletDir;
1210 - }
1211 -
1212 - /**
1213 - * Compiles and executes the specified Java code. The code is compiled as if it were a Java method body.
1214 - * <p/>
1215 - * Imports statements can be used at the start of the code. These imports are automatically used:<br>
1216 - * import com.esotericsoftware.scar.Scar;<br>
1217 - * import com.esotericsoftware.filesystem.Paths;<br>
1218 - * import com.esotericsoftware.minlog.Log;<br>
1219 - * import static com.esotericsoftware.scar.Scar.*;<br>
1220 - * import static com.esotericsoftware.minlog.Log.*;<br>
1221 - * <p/>
1222 - * Entries can be added to the classpath by using "classpath [url];" statements at the start of the code. These classpath
1223 - * entries are checked before the classloader that loaded the Scar class is checked. Examples:<br>
1224 - * classpath someTools.jar;<br>
1225 - * classpath some/directory/of/class/files;<br>
1226 - * classpath http://example.com/someTools.jar;<br>
1227 - *
1228 - * @param parameters These parameters will be available in the scope where the code is executed.
1229 - */
1230 - public void executeCode( Project project, String code, HashMap<String, Object> parameters )
1231 - {
1232 - try
1233 - {
1234 - // Wrap code in a class.
1235 - StringBuilder classBuffer = new StringBuilder( 2048 );
1236 - classBuffer.append( "import com.esotericsoftware.scar.*;\n" );
1237 - classBuffer.append( "import com.esotericsoftware.minlog.Log;\n" );
1238 - classBuffer.append( "import com.esotericsoftware.filesystem.Paths;\n" );
1239 - classBuffer.append( "import static com.esotericsoftware.scar.Scar.*;\n" );
1240 - classBuffer.append( "import static com.esotericsoftware.minlog.Log.*;\n" );
1241 - classBuffer.append( "public class Generated {\n" );
1242 - int pOverheadStartLines = 6;
1243 - classBuffer.append( "public void execute (" );
1244 - int i = 0;
1245 - for ( Entry<String, Object> entry : parameters.entrySet() )
1246 - {
1247 - if ( i++ > 0 )
1248 - {
1249 - classBuffer.append( ',' );
1250 - }
1251 - classBuffer.append( '\n' );
1252 - pOverheadStartLines++;
1253 - classBuffer.append( entry.getValue().getClass().getName() );
1254 - classBuffer.append( ' ' );
1255 - classBuffer.append( entry.getKey() );
1256 - }
1257 - classBuffer.append( "\n) throws Exception {\n" );
1258 - pOverheadStartLines += 2;
1259 -
1260 - // Append code, collecting imports statements and classpath URLs.
1261 - StringBuilder importBuffer = new StringBuilder( 512 );
1262 - ArrayList<URL> classpathURLs = new ArrayList<URL>();
1263 - BufferedReader reader = new BufferedReader( new StringReader( code ) );
1264 - boolean header = true;
1265 - while ( true )
1266 - {
1267 - String line = reader.readLine();
1268 - if ( line == null )
1269 - {
1270 - break;
1271 - }
1272 - String trimmed = line.trim();
1273 - if ( header && trimmed.startsWith( "import " ) && trimmed.endsWith( ";" ) )
1274 - {
1275 - importBuffer.append( line );
1276 - importBuffer.append( '\n' );
1277 - }
1278 - else if ( header && trimmed.startsWith( "classpath " ) && trimmed.endsWith( ";" ) )
1279 - {
1280 - String path = substring( line.trim(), 10, -1 );
1281 - try
1282 - {
1283 - classpathURLs.add( new URL( path ) );
1284 - }
1285 - catch ( MalformedURLException ex )
1286 - {
1287 - classpathURLs.add( new File( project.path( path ) ).toURI().toURL() );
1288 - }
1289 - }
1290 - else
1291 - {
1292 - if ( trimmed.length() > 0 )
1293 - {
1294 - header = false;
1295 - }
1296 - classBuffer.append( line );
1297 - classBuffer.append( '\n' );
1298 - }
1299 - }
1300 - classBuffer.append( "}}" );
1301 -
1302 - final String classCode = importBuffer.append( classBuffer ).toString();
1303 - if ( LOGGER.trace.isEnabled() )
1304 - {
1305 - progress( "Executing code:\n" + classCode );
1306 - }
1307 - // Compile class.
1308 - Class generatedClass = compileDynamicCodeToClass( pOverheadStartLines, classCode, classpathURLs.toArray( new URL[classpathURLs.size()] ) );
1309 -
1310 - // Execute.
1311 - Class[] parameterTypes = new Class[parameters.size()];
1312 - Object[] parameterValues = new Object[parameters.size()];
1313 - i = 0;
1314 - for ( Object object : parameters.values() )
1315 - {
1316 - parameterValues[i] = object;
1317 - parameterTypes[i++] = object.getClass();
1318 - }
1319 - generatedClass.getMethod( "execute", parameterTypes ).invoke( generatedClass.newInstance(), parameterValues );
1320 - }
1321 - catch ( Throwable ex )
1322 - {
1323 - throw new RuntimeException( "Error executing code:\n" + code.trim(), ex );
1324 - }
1325 - }
1326 -
1327 - // /**
1328 - // * Executes Java code in the specified project's document, if any.
1329 - // *
1330 - // * @return true if code was executed.
1331 - // */
1332 - // public boolean executeDocument( Project project )
1333 - // {
1334 - // String code = null; // todo: was -- project.getDocument();
1335 - // if ( code == null || code.trim().isEmpty() )
1336 - // {
1337 - // return false;
1338 - // }
1339 - // HashMap<String, Object> parameters = new HashMap<String, Object>();
1340 - // parameters.put( "project", project );
1341 - // executeCode( project, code, parameters );
1342 - // return true;
1343 - // }
1344 -
1345 - // /**
1346 - // * List of project names that have been built. {@link #buildDependencies(Project)} will skip any projects with a matching name.
1347 - // */
1348 - // static public final List<String> builtProjects = new ArrayList<String>();
1349 - //
1350 - static
1351 - {
1352 - Paths.addDefaultGlobExcludes( "**/.svn/**" );
1353 - }
1354 -
1355 - /// todo: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Here be Dragons ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1356 - /// todo: ==================================================================================================================
1357 - /// todo: ==================================================================================================================
1358 - /// todo: ==================================================================================================================
1359 - /// todo: ==================================================================================================================
1360 - /// todo: ==================================================================================================================
1361 - /// todo: ==================================================================================================================
1362 - /// todo: ==================================================================================================================
1363 - /// todo: ==================================================================================================================
1364 - /// todo: ==================================================================================================================
1365 - /// todo: ==================================================================================================================
1366 - /// todo: ==================================================================================================================
1367 -
1368 - private static class BuildFileFilter implements FileFilter
1369 - {
1370 - private static final String DEFAULT_JAVA_PROJECT_FILE_NAME = DEFAULT_PROJECT_FILE_NAME + JAVA_EXTENSION;
1371 - private static final String DEFAULT_YAML_PROJECT_FILE_NAME = DEFAULT_PROJECT_FILE_NAME + YAML_EXTENSION;
1372 -
1373 - @Override
1374 - public boolean accept( File pFile )
1375 - {
1376 - if ( pFile.isFile() )
1377 - {
1378 - String zName = pFile.getName();
1379 - if ( DEFAULT_JAVA_PROJECT_FILE_NAME.equalsIgnoreCase( zName ) || DEFAULT_YAML_PROJECT_FILE_NAME.equalsIgnoreCase( zName ) )
1380 - {
1381 - return pFile.canRead();
1382 - }
1383 - }
1384 - return false;
1385 - }
1386 -
1387 - public static final FileFilter INSTANCE = new BuildFileFilter();
1388 - }
1389 -
1390 - private static class ProjectCache
1391 - {
1392 - private Map<String, Project> mProjectByName = new HashMap<String, Project>();
1393 - private Map<String, Project> mProjectByPath = new HashMap<String, Project>();
1394 -
1395 - public synchronized Project getByPath( String pPath )
1396 - {
1397 - return mProjectByPath.get( pPath );
1398 - }
1399 -
1400 - private Project initialize( ProjectFactory pFactory, String pPath, Project pProject )
1401 - {
1402 - synchronized ( this )
1403 - {
1404 - String zName = pProject.getName();
1405 - Project zProject = mProjectByName.get( zName );
1406 - if ( zProject != null )
1407 - {
1408 - mProjectByPath.put( pPath, zProject );
1409 - return zProject;
1410 - }
1411 - mProjectByPath.put( pPath, pProject );
1412 - mProjectByName.put( zName, pProject );
1413 - }
1414 - pProject.initialize( pFactory );
1415 - return pProject;
1416 - }
1417 -
1418 - public Set<Project> getAllProjects()
1419 - {
1420 - return new HashSet<Project>( mProjectByName.values() );
1421 - }
1422 - }
1423 -
1424 - protected Runnable createRunnableFor( String pMethodName )
1425 - {
1426 - Runnable zRunnable = createRunnableFor( this, pMethodName );
1427 - return (zRunnable != null) ? zRunnable : createRunnableFor( mLaunchProject, pMethodName );
1428 - }
1429 -
1430 - protected Runnable createRunnableFor( final Object pObject, String pMethodName )
1431 - {
1432 - final Method zMethod = getMatchingMethod( pObject, pMethodName );
1433 - return (zMethod == null) ? null : new Runnable()
1434 - {
1435 - @Override public void run()
1436 - {
1437 - try
1438 - {
1439 - zMethod.invoke( pObject );
1440 - }
1441 - catch ( Exception e )
1442 - {
1443 - throw new RuntimeException( e );
1444 - }
1445 - }
1446 - };
1447 - }
1448 -
1449 - protected Method getMatchingMethod( Object pObject, String pMethodName )
1450 - {
1451 - List<Method> zFound = new ArrayList<Method>();
1452 - Method[] zMethods = pObject.getClass().getMethods();
1453 - for ( Method zMethod : zMethods )
1454 - {
1455 - if ( zMethod.getReturnType().equals( Void.TYPE ) && (zMethod.getParameterTypes().length == 0) )
1456 - {
1457 - if ( pMethodName.equals( zMethod.getName() ) )
1458 - {
1459 - return zMethod;
1460 - }
1461 - if ( pMethodName.equalsIgnoreCase( zMethod.getName() ) )
1462 - {
1463 - zFound.add( zMethod );
1464 - }
1465 - }
1466 - }
1467 - if ( zFound.size() == 0 )
1468 - {
1469 - return null;
1470 - }
1471 - if ( zFound.size() == 1 )
1472 - {
1473 - return zFound.get( 0 );
1474 - }
1475 - throw new IllegalArgumentException( "Multiple Methods " + zFound + " found on '" + pObject.getClass().getSimpleName() + "' than match: " + pMethodName );
1476 - }
1477 -
1478 - protected void createLaunchProject()
1479 - {
1480 - mLaunchProject = project( CANONICAL_USER_DIR, mArgs.get( "file", "." ) );
1481 - }
1482 -
1483 - protected int run()
1484 - {
1485 - System.out.println( getClass().getSimpleName() + " vs " + VERSION );
1486 - if ( mArgs.count() == 0 )
1487 - {
1488 - mLaunchProject.build();
1489 - return 0;
1490 - }
1491 - List<Runnable> zToExecute = getArgsBasedRunnables();
1492 - for ( Runnable zRunnable : zToExecute )
1493 - {
1494 - zRunnable.run();
1495 - }
1496 - return 0;
1497 - }
1498 -
1499 - private ArrayList<Runnable> getArgsBasedRunnables()
1500 - {
1501 - ArrayList<Runnable> zRunnables = new ArrayList<Runnable>();
1502 - List<String> zUnrecognizedNames = new ArrayList<String>();
1503 - for ( Arguments.NameValuePair zPair; null != (zPair = mArgs.getNext()); )
1504 - {
1505 - Runnable zRunnable = createRunnableFor( zPair.getName() );
1506 - if ( zRunnable != null )
1507 - {
1508 - zRunnables.add( zRunnable );
1509 - }
1510 - else
1511 - {
1512 - zUnrecognizedNames.add( zPair.getName() );
1513 - }
1514 - }
1515 - if ( !zUnrecognizedNames.isEmpty() )
1516 - {
1517 - System.err.println( "\nUnrecognized Command Line Args:" );
1518 - for ( String zName : zUnrecognizedNames )
1519 - {
1520 - System.err.println( " " + zName );
1521 - }
1522 - System.exit( 1 );
1523 - }
1524 - return zRunnables;
1525 - }
1526 -
1527 - public static void main( String[] args )
1528 - throws Exception
1529 - {
1530 - Arguments arguments = new Arguments( args );
1531 - Scar scar = new Scar( arguments );
1532 - scar.initLoggerFactory();
1533 - scar.createLaunchProject();
1534 - System.exit( scar.run() );
1535 - }
1536 - }
1 + package com.esotericsoftware.scar;
2 +
3 + import org.litesoft.logger.*;
4 + import org.litesoft.logger.nonpublic.*;
5 +
6 + import SevenZip.*;
7 + import com.esotericsoftware.filesystem.*;
8 + import com.esotericsoftware.scar.support.*;
9 + import com.esotericsoftware.utils.*;
10 + import com.esotericsoftware.yamlbeans.*;
11 + import com.esotericsoftware.yamlbeans.parser.*;
12 + import com.esotericsoftware.yamlbeans.tokenizer.*;
13 +
14 + import java.io.*;
15 + import java.lang.reflect.*;
16 + import java.net.*;
17 + import java.util.*;
18 + import java.util.Map.*;
19 + import java.util.jar.*;
20 + import java.util.zip.*;
21 +
22 + // BOZO - Add javadocs method.
23 +
24 + /**
25 + * Provides utility methods for common Java build tasks.
26 + */
27 + @SuppressWarnings({"ResultOfMethodCallIgnored"})
28 + public class Scar extends Utils implements ProjectFactory {
29 + public static final String VERSION = "2.95 - 1.7";
30 +
31 + public static final String DEFAULT_PROJECT_FILE_NAME = "Build";
32 +
33 + public static final String JAVA_EXTENSION = ".java";
34 + public static final String YAML_EXTENSION = ".yaml";
35 +
36 + protected static final Logger LOGGER = LoggerFactory.getLogger( Scar.class );
37 +
38 + protected final ProjectCache mProjectCache = new ProjectCache();
39 +
40 + private Project mLaunchProject;
41 +
42 + /**
43 + * The command line arguments Scar was started with. Empty if Scar was started with no arguments or Scar was not started from
44 + * the command line.
45 + */
46 + public final Arguments mArgs;
47 +
48 + public Scar( Arguments pArgs ) {
49 + mArgs = (pArgs != null) ? pArgs : new Arguments();
50 + }
51 +
52 + @SuppressWarnings({"UnusedDeclaration"})
53 + public Project getLaunchProject() {
54 + return mLaunchProject;
55 + }
56 +
57 + /**
58 + * Loads the specified project with default values and loads any other projects needed for the "include" property.
59 + *
60 + * @param pCanonicalCurrentDirectory
61 + * @param pPath Path to a YAML project file, or a directory containing a "project.yaml" file.
62 + */
63 + @Override
64 + public Project project( File pCanonicalCurrentDirectory, String pPath ) {
65 + Util.assertNotNull( "CurrentDirectory", pCanonicalCurrentDirectory );
66 + pPath = Util.assertNotEmpty( "Path", pPath );
67 + File zFile = new File( pPath );
68 + if ( !zFile.isAbsolute() ) {
69 + zFile = new File( pCanonicalCurrentDirectory, pPath );
70 + }
71 + try {
72 + zFile = zFile.getCanonicalFile();
73 + }
74 + catch ( IOException e ) {
75 + throw new WrappedIOException( e );
76 + }
77 +
78 + String zPath = zFile.getPath();
79 + Project zProject = mProjectCache.getByPath( zPath );
80 + if ( zProject != null ) {
81 + return zProject;
82 + }
83 + try {
84 + if ( zFile.isFile() ) { // Assume Project Build File
85 + return mProjectCache.initialize( this, zPath, createProject( zFile, zFile.getParentFile() ) );
86 + }
87 + if ( zFile.isDirectory() ) { // Assume Project Dir
88 + return mProjectCache.initialize( this, zPath, createProject( findBuildFile( zFile ), zFile, zFile.getName() ) );
89 + }
90 + }
91 + catch ( WrappedIOException e ) {
92 + throw new WrappedIOException( pPath, e );
93 + }
94 + throw new IllegalArgumentException( "Project is Neither a Project File, nor a Project Directory: " + zFile );
95 + }
96 +
97 + protected File findBuildFile( File pProjectDir ) {
98 + File[] zFiles = pProjectDir.listFiles( BuildFileFilter.INSTANCE );
99 + if ( (zFiles == null) || (zFiles.length == 0) ) {
100 + return null;
101 + }
102 + if ( zFiles.length == 1 ) {
103 + return zFiles[0];
104 + }
105 + File zFile = findBuildFile( zFiles, JAVA_EXTENSION );
106 + return (zFile != null) ? zFile : findBuildFile( zFiles, YAML_EXTENSION );
107 + }
108 +
109 + private File findBuildFile( File[] pFiles, String pExtension ) {
110 + File rv = null;
111 + for ( File zFile : pFiles ) {
112 + if ( zFile.getName().endsWith( pExtension ) ) {
113 + if ( rv != null ) {
114 + throw new IllegalStateException( "Found Both:\n " + rv + "\n " + zFile );
115 + }
116 + rv = zFile;
117 + }
118 + }
119 + return rv;
120 + }
121 +
122 + protected Project createProject( File pPossibleBuildFile, File pCanonicalProjectDir ) {
123 + String zBuildFileName = pPossibleBuildFile.getName();
124 + int zDotAt = zBuildFileName.lastIndexOf( '.' );
125 + if ( (zDotAt != -1) && (pCanonicalProjectDir != null) ) {
126 + String zName = zBuildFileName.substring( 0, zDotAt ).trim();
127 + if ( DEFAULT_PROJECT_FILE_NAME.equalsIgnoreCase( zName ) ) {
128 + zName = pCanonicalProjectDir.getName();
129 + }
130 + return createProject( pPossibleBuildFile, pCanonicalProjectDir, zName );
131 + }
132 + throw new IllegalArgumentException( "Unacceptable Project Path or Name, Project File " + pPossibleBuildFile );
133 + }
134 +
135 + protected Project createProject( File pBuildFile, File pCanonicalProjectDir, String pProjectName ) {
136 + if ( pBuildFile == null ) {
137 + throw new IllegalArgumentException( "No 'Build.java' or 'Build.yaml' file found in Project Directory: " + pCanonicalProjectDir );
138 + }
139 + String zBuildFileName = pBuildFile.getName();
140 + if ( zBuildFileName.endsWith( JAVA_EXTENSION ) ) {
141 + return createJavaProject( pBuildFile, pCanonicalProjectDir, pProjectName );
142 + }
143 + if ( zBuildFileName.endsWith( YAML_EXTENSION ) ) {
144 + return createYamlProject( pBuildFile, pCanonicalProjectDir, pProjectName );
145 + }
146 + throw new IllegalArgumentException( pBuildFile + " was NOT either a '.java' or a '.yaml' file!" );
147 + }
148 +
149 + protected Project createYamlProject( File pYamlBuildFile, File pCanonicalProjectDir, String pProjectName ) {
150 + List<String> zLines = readLines( pYamlBuildFile );
151 + int at = findLine( zLines, "---" );
152 + String zYAML, zCode = null;
153 + if ( at == -1 ) {
154 + zYAML = mergeLines( zLines, 0 );
155 + } else {
156 + zYAML = mergeLines( zLines, 0, at );
157 + zCode = Util.noEmpty( mergeLines( zLines, at + 1 ) );
158 + }
159 + Map<Object, Object> zData = (zYAML.length() == 0) ? new HashMap<Object, Object>() : parseYAML( pCanonicalProjectDir, zYAML );
160 + Class<? extends Project> zClass = createYamlProjectClass();
161 + if ( zCode != null ) {
162 + zClass = createYamlCodeProjectClass( zClass, zCode, at + 1 );
163 + }
164 + return instantiate( pYamlBuildFile, zClass, pCanonicalProjectDir, pProjectName, zData );
165 + }
166 +
167 + protected Project createJavaProject( File pJavaBuildFile, File pCanonicalProjectDir, String pProjectName ) {
168 + String zFile = mergeLines( readLines( pJavaBuildFile ), 0 );
169 + Class<? extends Project> zClass = createJavaProjectClass();
170 + zClass = createJavaCodeProjectClass( zClass, zFile );
171 + return instantiate( pJavaBuildFile, zClass, pCanonicalProjectDir, pProjectName, null );
172 + }
173 +
174 + protected Project instantiate( File zProjectFile, Class<? extends Project> pClass, File pCanonicalProjectDir, String pProjectName, Map<Object, Object> pData ) {
175 + return instantiate( pClass, new ProjectParameters( zProjectFile, pProjectName, pCanonicalProjectDir, pData ) );
176 + }
177 +
178 + protected Project instantiate( Class<? extends Project> pClass, ProjectParameters pParameters ) {
179 + Throwable zCause;
180 + try {
181 + Constructor zConstructor = pClass.getConstructor( ProjectParameters.class );
182 + return (Project) zConstructor.newInstance( pParameters );
183 + }
184 + catch ( NoSuchMethodException e ) {
185 + zCause = e;
186 + }
187 + catch ( InvocationTargetException e ) {
188 + zCause = e;
189 + }
190 + catch ( ClassCastException e ) {
191 + zCause = e;
192 + }
193 + catch ( InstantiationException e ) {
194 + zCause = e;
195 + }
196 + catch ( IllegalAccessException e ) {
197 + zCause = e;
198 + }
199 + catch ( RuntimeException e ) {
200 + zCause = e;
201 + }
202 + throw new RuntimeException( "Unable to Instantiate Project Class for Project: " + pParameters.getName() + " in dir " + pParameters.getCanonicalProjectDir(), zCause );
203 + }
204 +
205 + protected Class<? extends Project> createYamlProjectClass() {
206 + return Project.class;
207 + }
208 +
209 + protected Class<? extends Project> createJavaProjectClass() {
210 + return Project.class;
211 + }
212 +
213 + protected Map<Object, Object> parseYAML( File pProjectDir, String pYAML ) {
214 + final String zProjectDir = pProjectDir.getPath().replace( '\\', '/' );
215 +
216 + YamlReader yamlReader = new YamlReader( new StringReader( pYAML ) ) {
217 + @Override
218 + protected Object readValue( Class type, Class elementType, Class defaultType )
219 + throws YamlException, Parser.ParserException, Tokenizer.TokenizerException {
220 + Object value = super.readValue( type, elementType, defaultType );
221 + if ( value instanceof String ) {
222 + value = ((String) value).replaceAll( "\\$dir\\$", zProjectDir );
223 + }
224 + return value;
225 + }
226 + };
227 + try {
228 + //noinspection unchecked
229 + return yamlReader.read( HashMap.class );
230 + }
231 + catch ( IOException e ) {
232 + throw new WrappedIOException( e );
233 + }
234 + finally {
235 + try {
236 + yamlReader.close();
237 + }
238 + catch ( IOException e ) {
239 + throw new WrappedIOException( e );
240 + }
241 + }
242 + }
243 +
244 + protected String mergeLines( List<String> pLines, int pFrom ) {
245 + return mergeLines( pLines, pFrom, pLines.size() );
246 + }
247 +
248 + protected String mergeLines( List<String> pLines, int pFrom, int pToExclusive ) {
249 + StringBuilder sb = new StringBuilder();
250 + while ( (pFrom < pToExclusive) && (pFrom < pLines.size()) ) {
251 + sb.append( pLines.get( pFrom++ ) ).append( '\n' );
252 + }
253 + return sb.toString();
254 + }
255 +
256 + private int findLine( List<String> pLines, String zLine ) {
257 + for ( int i = 0; i < pLines.size(); i++ ) {
258 + if ( zLine.equals( pLines.get( i ) ) ) {
259 + return i;
260 + }
261 + }
262 + return -1;
263 + }
264 +
265 + protected List<String> readLines( File zFile ) {
266 + BufferedReader fileReader;
267 + try {
268 + fileReader = new BufferedReader( new FileReader( zFile ) );
269 + }
270 + catch ( FileNotFoundException e ) {
271 + throw new WrappedIOException( e );
272 + }
273 + try {
274 + List<String> lines = new ArrayList<String>();
275 + for ( String line; null != (line = fileReader.readLine()); ) {
276 + lines.add( line.trim() );
277 + }
278 + Closeable c = fileReader;
279 + fileReader = null;
280 + c.close();
281 + return lines;
282 + }
283 + catch ( IOException e ) {
284 + if ( fileReader != null ) {
285 + try {
286 + fileReader.close();
287 + }
288 + catch ( IOException e1 ) {
289 + // Whatever!
290 + }
291 + }
292 + throw new WrappedIOException( e );
293 + }
294 + }
295 +
296 + protected void initLoggerFactory() {
297 + LoggerFactory.init( createLoggerLevel() );
298 + }
299 +
300 + protected LoggerLevel createLoggerLevel() {
301 + int zLevel = LoggerLevel.ERROR;
302 + String[] zLevels = LoggerLevel.LEVELS;
303 + for ( int i = 0; i < zLevels.length; i++ ) {
304 + if ( null != mArgs.get( zLevels[i] ) ) {
305 + zLevel = i;
306 + break;
307 + }
308 + }
309 + final int zLoggerLevel = zLevel;
310 + return new LoggerLevel() {
311 + @Override
312 + public int getEnabledLevel( String pClassName ) {
313 + return zLoggerLevel;
314 + }
315 + };
316 + }
317 +
318 + protected Class<Project> createYamlCodeProjectClass( Class<? extends Project> pClass, String pCode, int pOverheadStartLines ) {
319 + throw new UnsupportedOperationException(); // todo: See - executeDocument();
320 + }
321 +
322 + protected Class<Project> createJavaCodeProjectClass( Class<? extends Project> pClass, String pCode ) {
323 + throw new UnsupportedOperationException(); // todo: See - executeDocument()!;
324 + }
325 +
326 + /**
327 + * Cleans All projects - Normally called reflectively
328 + */
329 + @SuppressWarnings({"UnusedDeclaration"})
330 + public void cleanAll() {
331 + progress( "CleanAll" );
332 + Set<Project> zProjects = mProjectCache.getAllProjects();
333 + for ( Project zProject : zProjects ) {
334 + zProject.clean();
335 + }
336 + }
337 +
338 + /**
339 + * Builds the Launch Project - Normally called reflectively
340 + */
341 + @SuppressWarnings({"UnusedDeclaration"})
342 + public void build() {
343 + mLaunchProject.build();
344 + }
345 +
346 + /**
347 + * Versions the current (GWT) Launch Project - Normally called reflectively
348 + */
349 + @SuppressWarnings({"UnusedDeclaration"})
350 + public void versionGWT() {
351 + mLaunchProject.versionGWT();
352 + }
353 +
354 + /// todo: ==================================================================================================================
355 + /// todo: ==================================================================================================================
356 + /// todo: ==================================================================================================================
357 + /// todo: ==================================================================================================================
358 + /// todo: ==================================================================================================================
359 + /// todo: ==================================================================================================================
360 + /// todo: ==================================================================================================================
361 + /// todo: ==================================================================================================================
362 + /// todo: ==================================================================================================================
363 + /// todo: ==================================================================================================================
364 + /// todo: ==================================================================================================================
365 + /// todo: vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv Here be Dragons vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
366 +
367 + /**
368 + * Loads the specified project with the specified defaults and loads any other projects needed for the "include" property.
369 + *
370 + * @param path Path to a YAML project file, or a directory containing a "project.yaml" file.
371 + */
372 + // public Project project( String path, Project defaults )
373 + // {
374 + // Util.assertNotNull( "path", path );
375 + // Util.assertNotNull( "defaults", defaults );
376 + //
377 + // Project actualProject = new Project( path, this );
378 + //
379 + // Project project = new Project();
380 + // project.replace( defaults );
381 + //
382 + // File parent = actualProject.getDirectory().getParentFile();
383 + // while ( parent != null )
384 + // {
385 + // File includeFile = new File( parent, "include.yaml" );
386 + // if ( includeFile.exists() )
387 + // {
388 + // try
389 + // {
390 + // project.replace( project( includeFile.getAbsolutePath(), defaults ) );
391 + // }
392 + // catch ( RuntimeException ex )
393 + // {
394 + // throw new RuntimeException( "Error loading included project: " + includeFile.getAbsolutePath(), ex );
395 + // }
396 + // }
397 + // parent = parent.getParentFile();
398 + // }
399 + //
400 + // for ( String include : actualProject.getInclude() )
401 + // {
402 + // try
403 + // {
404 + // project.replace( project( actualProject.path( include ), defaults ) );
405 + // }
406 + // catch ( RuntimeException ex )
407 + // {
408 + // throw new RuntimeException( "Error loading included project: " + actualProject.path( include ), ex );
409 + // }
410 + // }
411 + // project.replace( actualProject );
412 + // return project;
413 + // }
414 +
415 + /**
416 + * Removes any code signatures on the specified JAR. Removes any signature files in the META-INF directory and removes any
417 + * signature entries from the JAR's manifest.
418 + *
419 + * @return The path to the JAR file.
420 + */
421 + public String unsign( String jarFile ) {
422 + Util.assertNotNull( "jarFile", jarFile );
423 +
424 + progress( "Removing signature from JAR: " + jarFile );
425 +
426 + File tempFile;
427 + try {
428 + tempFile = File.createTempFile( "scar", "removejarsig" );
429 + }
430 + catch ( IOException e ) {
431 + throw new WrappedIOException( e );
432 + }
433 + JarOutputStream jarOutput = null;
434 + JarInputStream jarInput = null;
435 + try {
436 + jarOutput = new JarOutputStream( new FileOutputStream( tempFile ) );
437 + jarInput = new JarInputStream( new FileInputStream( jarFile ) );
438 + Manifest manifest = jarInput.getManifest();
439 + if ( manifest != null ) {
440 + // Remove manifest file entries.
441 + manifest.getEntries().clear();
442 + jarOutput.putNextEntry( new JarEntry( "META-INF/MANIFEST.MF" ) );
443 + manifest.write( jarOutput );
444 + }
445 + byte[] buffer = new byte[4096];
446 + while ( true ) {
447 + JarEntry entry = jarInput.getNextJarEntry();
448 + if ( entry == null ) {
449 + break;
450 + }
451 + String name = entry.getName();
452 + // Skip signature files.
453 + if ( name.startsWith( "META-INF/" ) && (name.endsWith( ".SF" ) || name.endsWith( ".DSA" ) || name.endsWith( ".RSA" )) ) {
454 + continue;
455 + }
456 + jarOutput.putNextEntry( new JarEntry( name ) );
457 + while ( true ) {
458 + int length = jarInput.read( buffer );
459 + if ( length == -1 ) {
460 + break;
461 + }
462 + jarOutput.write( buffer, 0, length );
463 + }
464 + }
465 + jarInput.close();
466 + jarOutput.close();
467 + copyFile( tempFile.getAbsolutePath(), jarFile );
468 + }
469 + catch ( IOException ex ) {
470 + throw new WrappedIOException( "Error unsigning JAR file: " + jarFile, ex );
471 + }
472 + finally {
473 + dispose( jarInput );
474 + dispose( jarOutput );
475 + tempFile.delete();
476 + }
477 + return jarFile;
478 + }
479 +
480 + /**
481 + * Signs the specified JAR.
482 + *
483 + * @return The path to the JAR.
484 + */
485 + public String sign( String jarFile, String keystoreFile, String alias, String password ) {
486 + Util.assertNotNull( "jarFile", jarFile );
487 + Util.assertNotNull( "keystoreFile", keystoreFile );
488 + Util.assertNotNull( "alias", alias );
489 + Util.assertNotNull( "password", password );
490 + if ( password.length() < 6 ) {
491 + throw new IllegalArgumentException( "password must be 6 or more characters." );
492 + }
493 + progress( "Signing JAR (" + keystoreFile + ", " + alias + ":" + password + "): " + jarFile );
494 +
495 + shell( "jarsigner", "-keystore", keystoreFile, "-storepass", password, "-keypass", password, jarFile, alias );
496 + return jarFile;
497 + }
498 +
499 + /**
500 + * Encodes the specified file with pack200. The resulting filename is the filename plus ".pack". The file is deleted after
501 + * encoding.
502 + *
503 + * @return The path to the encoded file.
504 + */
505 + public String pack200( String jarFile ) {
506 + String packedFile = pack200( jarFile, jarFile + ".pack" );
507 + delete( jarFile );
508 + return packedFile;
509 + }
510 +
511 + /**
512 + * Encodes the specified file with pack200.
513 + *
514 + * @return The path to the encoded file.
515 + */
516 + public String pack200( String jarFile, String packedFile ) {
517 + Util.assertNotNull( "jarFile", jarFile );
518 + Util.assertNotNull( "packedFile", packedFile );
519 +
520 + progress( "Packing JAR: " + jarFile + " -> " + packedFile );
521 +
522 + shell( "pack200", "--no-gzip", "--segment-limit=-1", "--no-keep-file-order", "--effort=7", "--modification-time=latest", packedFile, jarFile );
523 + return packedFile;
524 + }
525 +
526 + /**
527 + * Decodes the specified file with pack200. The filename must end in ".pack" and the resulting filename has this stripped. The
528 + * encoded file is deleted after decoding.
529 + *
530 + * @return The path to the decoded file.
531 + */
532 + public String unpack200( String packedFile ) {
533 + Util.assertNotNull( "packedFile", packedFile );
534 + if ( !packedFile.endsWith( ".pack" ) ) {
535 + throw new IllegalArgumentException( "packedFile must end with .pack: " + packedFile );
536 + }
537 +
538 + String jarFile = unpack200( packedFile, substring( packedFile, 0, -5 ) );
539 + delete( packedFile );
540 + return jarFile;
541 + }
542 +
543 + /**
544 + * Decodes the specified file with pack200.
545 + *
546 + * @return The path to the decoded file.
547 + */
548 + public String unpack200( String packedFile, String jarFile ) {
549 + Util.assertNotNull( "packedFile", packedFile );
550 + Util.assertNotNull( "jarFile", jarFile );
551 +
552 + progress( "Unpacking JAR: " + packedFile + " -> " + jarFile );
553 +
554 + shell( "unpack200", packedFile, jarFile );
555 + return jarFile;
556 + }
557 +
558 + /**
559 + * Encodes the specified file with GZIP. The resulting filename is the filename plus ".gz". The file is deleted after encoding.
560 + *
561 + * @return The path to the encoded file.
562 + */
563 + public String gzip( String file ) {
564 + String gzipFile = gzip( file, file + ".gz" );
565 + delete( file );
566 + return gzipFile;
567 + }
568 +
569 + /**
570 + * Encodes the specified file with GZIP.
571 + *
572 + * @return The path to the encoded file.
573 + */
574 + public String gzip( String file, String gzipFile ) {
575 + Util.assertNotNull( "file", file );
576 + Util.assertNotNull( "gzipFile", gzipFile );
577 +
578 + progress( "GZIP encoding: " + file + " -> " + gzipFile );
579 +
580 + InputStream input;
581 + try {
582 + input = new FileInputStream( file );
583 + }
584 + catch ( FileNotFoundException e ) {
585 + throw new WrappedIOException( e );
586 + }
587 + try {
588 + copyStreamAndCloseEm( input, new GZIPOutputStream( new FileOutputStream( gzipFile ) ) );
589 + }
590 + catch ( IOException e ) {
591 + throw new WrappedIOException( e );
592 + }
593 + finally {
594 + dispose( input );
595 + }
596 + return gzipFile;
597 + }
598 +
599 + /**
600 + * Decodes the specified GZIP file. The filename must end in ".gz" and the resulting filename has this stripped. The encoded
601 + * file is deleted after decoding.
602 + *
603 + * @return The path to the decoded file.
604 + */
605 + public String ungzip( String gzipFile ) {
606 + Util.assertNotNull( "gzipFile", gzipFile );
607 + if ( !gzipFile.endsWith( ".gz" ) ) {
608 + throw new IllegalArgumentException( "gzipFile must end with .gz: " + gzipFile );
609 + }
610 +
611 + String file = ungzip( gzipFile, substring( gzipFile, 0, -3 ) );
612 + delete( gzipFile );
613 + return file;
614 + }
615 +
616 + /**
617 + * Decodes the specified GZIP file.
618 + *
619 + * @return The path to the decoded file.
620 + */
621 + public String ungzip( String gzipFile, String file ) {
622 + Util.assertNotNull( "gzipFile", gzipFile );
623 + Util.assertNotNull( "file", file );
624 + progress( "GZIP decoding: " + gzipFile + " -> " + file );
625 +
626 + InputStream input;
627 + try {
628 + input = new GZIPInputStream( new FileInputStream( gzipFile ) );
629 + }
630 + catch ( IOException e ) {
631 + throw new WrappedIOException( e );
632 + }
633 + try {
634 + copyStreamAndCloseEm( input, new FileOutputStream( file ) );
635 + }
636 + catch ( FileNotFoundException e ) {
637 + throw new WrappedIOException( e );
638 + }
639 + finally {
640 + dispose( input );
641 + }
642 + return file;
643 + }
644 +
645 + /**
646 + * Encodes the specified files with ZIP.
647 + *
648 + * @return The path to the encoded file.
649 + */
650 + public String zip( Paths paths, String zipFile ) {
651 + Util.assertNotNull( "paths", paths );
652 + Util.assertNotNull( "zipFile", zipFile );
653 + progress( "Creating ZIP (" + paths.count() + " entries): " + zipFile );
654 +
655 + paths.zip( zipFile );
656 + return zipFile;
657 + }
658 +
659 + /**
660 + * Encodes the specified file with LZMA. The resulting filename is the filename plus ".lzma". The file is deleted after
661 + * encoding.
662 + *
663 + * @return The path to the encoded file.
664 + */
665 + public String lzma( String file ) {
666 + String lzmaFile = lzma( file, file + ".lzma" );
667 + delete( file );
668 + return lzmaFile;
669 + }
670 +
671 + /**
672 + * Encodes the specified file with LZMA.
673 + *
674 + * @return The path to the encoded file.
675 + */
676 + public String lzma( String file, String lzmaFile ) {
677 + Util.assertNotNull( "file", file );
678 + Util.assertNotNull( "lzmaFile", lzmaFile );
679 + progress( "LZMA encoding: " + file + " -> " + lzmaFile );
680 +
681 + try {
682 + LzmaAlone.main( new String[]{"e", file, lzmaFile} );
683 + }
684 + catch ( Exception ex ) {
685 + throw new WrappedIOException( "Error lzma compressing file: " + file, ex );
686 + }
687 + return lzmaFile;
688 + }
689 +
690 + /**
691 + * Decodes the specified LZMA file. The filename must end in ".lzma" and the resulting filename has this stripped. The encoded
692 + * file is deleted after decoding.
693 + *
694 + * @return The path to the decoded file.
695 + */
696 + public String unlzma( String lzmaFile ) {
697 + Util.assertNotNull( "lzmaFile", lzmaFile );
698 + if ( !lzmaFile.endsWith( ".lzma" ) ) {
699 + throw new IllegalArgumentException( "lzmaFile must end with .lzma: " + lzmaFile );
700 + }
701 +
702 + String file = unlzma( lzmaFile, substring( lzmaFile, 0, -5 ) );
703 + delete( lzmaFile );
704 + return file;
705 + }
706 +
707 + /**
708 + * Decodes the specified LZMA file.
709 + *
710 + * @return The path to the decoded file.
711 + */
712 + public String unlzma( String lzmaFile, String file ) {
713 + Util.assertNotNull( "lzmaFile", lzmaFile );
714 + Util.assertNotNull( "file", file );
715 + progress( "LZMA decoding: " + lzmaFile + " -> " + file );
716 +
717 + try {
718 + LzmaAlone.main( new String[]{"d", lzmaFile, file} );
719 + }
720 + catch ( Exception ex ) {
721 + throw new WrappedIOException( "Error lzma decompressing file: " + file, ex );
722 + }
723 + return file;
724 + }
725 +
726 + /**
727 + * Copies all the JAR and JNLP files from the "dist" directory to a "jws" directory under the "target" directory. It then uses
728 + * the specified keystore to sign each JAR. If the "pack" parameter is true, it also compresses each JAR using pack200 and
729 + * GZIP.
730 + */
731 + public void jws( Project project, boolean pack, String keystoreFile, String alias, String password ) {
732 + Util.assertNotNull( "Project", project );
733 + Util.assertNotNull( "keystoreFile", keystoreFile );
734 + Util.assertNotNull( "alias", alias );
735 + Util.assertNotNull( "password", password );
736 + if ( password.length() < 6 ) {
737 + throw new IllegalArgumentException( "password must be 6 or more characters." );
738 + }
739 +
740 + progress( "JWS: " + project );
741 +
742 + String jwsDir = mkdir( project.path( "$target$/jws/" ) );
743 + String distDir = project.path( "$target$/dist/" );
744 + new Paths( distDir, "*.jar", "*.jnlp" ).copyTo( jwsDir );
745 + for ( String file : new Paths( jwsDir, "*.jar" ).getFullPaths() ) {
746 + sign( unpack200( pack200( unsign( file ) ) ), keystoreFile, alias, password );
747 + }
748 + if ( pack ) {
749 + String unpackedDir = mkdir( jwsDir + "unpacked/" );
750 + String packedDir = mkdir( jwsDir + "packed/" );
751 + for ( String file : new Paths( jwsDir, "*.jar", "!*native*" ).getFullPaths() ) {
752 + String fileName = fileName( file );
753 + String unpackedFile = unpackedDir + fileName;
754 + moveFile( file, unpackedFile );
755 + String packedFile = packedDir + fileName;
756 + gzip( pack200( copyFile( unpackedFile, packedFile ) ) );
757 + }
758 + }
759 + }
760 +
761 + /**
762 + * Generates ".htaccess" and "type map" VAR files in the "jws" directory. These files allow Apache to serve both pack200/GZIP
763 + * JARs and regular JARs, based on capability of the client requesting the JAR.
764 + */
765 + public void jwsHtaccess( Project project ) {
766 + Util.assertNotNull( "Project", project );
767 +
768 + progress( "JWS htaccess: " + project );
769 +
770 + String jwsDir = mkdir( project.path( "$target$/jws/" ) );
771 + for ( String packedFile : new Paths( jwsDir + "packed", "*.jar.pack.gz" ).getFullPaths() ) {
772 + String packedFileName = fileName( packedFile );
773 + String jarFileName = substring( packedFileName, 0, -8 );
774 + FileWriter writer;
775 + try {
776 + writer = new FileWriter( jwsDir + jarFileName + ".var" );
777 + }
778 + catch ( IOException e ) {
779 + throw new WrappedIOException( e );
780 + }
781 + try {
782 + writer.write( "URI: packed/" + packedFileName + "\n" );
783 + writer.write( "Content-Type: x-java-archive\n" );
784 + writer.write( "Content-Encoding: pack200-gzip\n" );
785 + writer.write( "URI: unpacked/" + jarFileName + "\n" );
786 + writer.write( "Content-Type: x-java-archive\n" );
787 + }
788 + catch ( IOException e ) {
789 + throw new WrappedIOException( e );
790 + }
791 + finally {
792 + dispose( writer );
793 + }
794 + }
795 + FileWriter writer;
796 + try {
797 + writer = new FileWriter( jwsDir + ".htaccess" );
798 + }
799 + catch ( IOException e ) {
800 + throw new WrappedIOException( e );
801 + }
802 + try {
803 + writer.write( "AddType application/x-java-jnlp-file .jnlp" ); // JNLP mime type.
804 + writer.write( "AddType application/x-java-archive .jar\n" ); // JAR mime type.
805 + writer.write( "AddHandler application/x-type-map .var\n" ); // Enable type maps.
806 + writer.write( "Options +MultiViews\n" );
807 + writer.write( "MultiViewsMatch Any\n" ); // Apache 2.0 only.
808 + writer.write( "<Files *.pack.gz>\n" );
809 + writer.write( "AddEncoding pack200-gzip .jar\n" ); // Enable Content-Encoding header for .jar.pack.gz files.
810 + writer.write( "RemoveEncoding .gz\n" ); // Prevent mod_gzip from messing with the Content-Encoding response.
811 + writer.write( "</Files>\n" );
812 + }
813 + catch ( IOException e ) {
814 + throw new WrappedIOException( e );
815 + }
816 + finally {
817 + dispose( writer );
818 + }
819 + }
820 +
821 + /**
822 + * Generates a JNLP file in the "jws" directory. JARs in the "jws" directory are included in the JNLP. JARs containing "native"
823 + * and "win", "mac", "linux", or "solaris" are properly included in the native section of the JNLP. The "main" property is used
824 + * for the main class in the JNLP.
825 + *
826 + * @param splashImage Can be null.
827 + */
828 + public void jnlp( Project project, String url, String company, String title, String splashImage ) {
829 + Util.assertNotNull( "Project", project );
830 + Util.assertNotNull( "company", company );
831 + Util.assertNotNull( "title", title );
832 + Util.assertNotNull( "url", url );
833 + if ( !url.startsWith( "http" ) ) {
834 + throw new RuntimeException( "Invalid url: " + url );
835 + }
836 +
837 + if ( LOGGER.debug.isEnabled() ) {
838 + progress( "JNLP: " + project + " (" + url + ", " + company + ", " + title + ", " + splashImage + ")" );
839 + } else {
840 + progress( "JNLP: " + project );
841 + }
842 +
843 + if ( !project.hasMain() ) {
844 + throw new RuntimeException( "Unable to generate JNLP: project has no main class" );
845 + }
846 +
847 + int firstSlash = url.indexOf( "/", 7 );
848 + int lastSlash = url.lastIndexOf( "/" );
849 + if ( firstSlash == -1 || lastSlash == -1 ) {
850 + throw new RuntimeException( "Invalid url: " + url );
851 + }
852 + String domain = url.substring( 0, firstSlash + 1 );
853 + String path = url.substring( firstSlash + 1, lastSlash + 1 );
854 + String jnlpFile = url.substring( lastSlash + 1 );
855 +
856 + String jwsDir = mkdir( project.path( "$target$/jws/" ) );
857 + FileWriter writer;
858 + try {
859 + writer = new FileWriter( jwsDir + jnlpFile );
860 + }
861 + catch ( IOException e ) {
862 + throw new WrappedIOException( e );
863 + }
864 + try {
865 + writer.write( "<?xml version='1.0' encoding='utf-8'?>\n" );
866 + writer.write( "<jnlp spec='1.0+' codebase='" + domain + "' href='" + path + jnlpFile + "'>\n" );
867 + writer.write( "<information>\n" );
868 + writer.write( "\t<title>" + title + "</title>\n" );
869 + writer.write( "\t<vendor>" + company + "</vendor>\n" );
870 + writer.write( "\t<homepage href='" + domain + "'/>\n" );
871 + writer.write( "\t<description>" + title + "</description>\n" );
872 + writer.write( "\t<description kind='short'>" + title + "</description>\n" );
873 + if ( splashImage != null ) {
874 + writer.write( "\t<icon kind='splash' href='" + path + splashImage + "'/>\n" );
875 + }
876 + writer.write( "</information>\n" );
877 + writer.write( "<security>\n" );
878 + writer.write( "\t<all-permissions/>\n" );
879 + writer.write( "</security>\n" );
880 + writer.write( "<resources>\n" );
881 + writer.write( "\t<j2se href='http://java.sun.com/products/autodl/j2se' version='1.5+' max-heap-size='128m'/>\n" );
882 +
883 + // JAR with main class first.
884 + String projectJarName;
885 + if ( project.hasVersion() ) {
886 + projectJarName = project.format( "$name$-$version$.jar" );
887 + } else {
888 + projectJarName = project.format( "$name$.jar" );
889 + }
890 + writer.write( "\t<jar href='" + path + projectJarName + "'/>\n" );
891 +
892 + // Rest of JARs, except natives.
893 + for ( String file : new Paths( jwsDir, "**/*.jar", "!*native*", "!**/" + projectJarName ).getFullPaths() ) {
894 + writer.write( "\t<jar href='" + path + fileName( file ) + "'/>\n" );
895 + }
896 +
897 + writer.write( "</resources>\n" );
898 + Paths nativePaths = new Paths( jwsDir, "*native*win*", "*win*native*" );
899 + if ( nativePaths.count() == 1 ) {
900 + writer.write( "<resources os='Windows'>\n" );
901 + writer.write( "\t<j2se href='http://java.sun.com/products/autodl/j2se' version='1.5+' max-heap-size='128m'/>\n" );
902 + writer.write( "\t<nativelib href='" + path + nativePaths.getNames().get( 0 ) + "'/>\n" );
903 + writer.write( "</resources>\n" );
904 + }
905 + nativePaths = new Paths( jwsDir, "*native*mac*", "*mac*native*" );
906 + if ( nativePaths.count() == 1 ) {
907 + writer.write( "<resources os='Mac'>\n" );
908 + writer.write( "\t<j2se href='http://java.sun.com/products/autodl/j2se' version='1.5+' max-heap-size='128m'/>\n" );
909 + writer.write( "\t<nativelib href='" + path + nativePaths.getNames().get( 0 ) + "'/>\n" );
910 + writer.write( "</resources>\n" );
911 + }
912 + nativePaths = new Paths( jwsDir, "*native*linux*", "*linux*native*" );
913 + if ( nativePaths.count() == 1 ) {
914 + writer.write( "<resources os='Linux'>\n" );
915 + writer.write( "\t<j2se href='http://java.sun.com/products/autodl/j2se' version='1.5+' max-heap-size='128m'/>\n" );
916 + writer.write( "\t<nativelib href='" + path + nativePaths.getNames().get( 0 ) + "'/>\n" );
917 + writer.write( "</resources>\n" );
918 + }
919 + nativePaths = new Paths( jwsDir, "*native*solaris*", "*solaris*native*" );
920 + if ( nativePaths.count() == 1 ) {
921 + writer.write( "<resources os='SunOS'>\n" );
922 + writer.write( "\t<j2se href='http://java.sun.com/products/autodl/j2se' version='1.5+' max-heap-size='128m'/>\n" );
923 + writer.write( "\t<nativelib href='" + path + nativePaths.getNames().get( 0 ) + "'/>\n" );
924 + writer.write( "</resources>\n" );
925 + }
926 + writer.write( "<application-desc main-class='" + project.getMain() + "'/>\n" );
927 + writer.write( "</jnlp>" );
928 + }
929 + catch ( IOException e ) {
930 + throw new WrappedIOException( e );
931 + }
932 + finally {
933 + dispose( writer );
934 + }
935 + }
936 +
937 + public String lwjglApplet( Project project, String keystoreFile, String alias, String password ) {
938 + Util.assertNotNull( "Project", project );
939 + Util.assertNotNull( "keystoreFile", keystoreFile );
940 + Util.assertNotNull( "alias", alias );
941 + Util.assertNotNull( "password", password );
942 + if ( password.length() < 6 ) {
943 + throw new IllegalArgumentException( "password must be 6 or more characters." );
944 + }
945 +
946 + progress( "LWJGL applet: " + project );
947 +
948 + String appletDir = mkdir( project.path( "$target$/applet-lwjgl/" ) );
949 + String distDir = project.path( "$target$/dist/" );
950 + new Paths( distDir, "**/*.jar", "*.html", "*.htm" ).flatten().copyTo( appletDir );
951 + for ( String jarFile : new Paths( appletDir, "*.jar" ).getFullPaths() ) {
952 + sign( unpack200( pack200( unsign( jarFile ) ) ), keystoreFile, alias, password );
953 + String fileName = fileName( jarFile );
954 + if ( fileName.equals( "lwjgl_util_applet.jar" ) || fileName.equals( "lzma.jar" ) ) {
955 + continue;
956 + }
957 + if ( fileName.contains( "native" ) ) {
958 + lzma( jarFile );
959 + } else {
960 + lzma( pack200( jarFile ) );
961 + }
962 + }
963 +
964 + if ( !new Paths( appletDir, "*.html", "*.htm" ).isEmpty() ) {
965 + return appletDir;
966 + }
967 + if ( !project.hasMain() ) {
968 + LOGGER.debug.log( "Unable to generate applet.html: project has no main class" );
969 + return appletDir;
970 + }
971 + progress( "Generating: applet.html" );
972 + FileWriter writer;
973 + try {
974 + writer = new FileWriter( appletDir + "applet.html" );
975 + }
976 + catch ( IOException e ) {
977 + throw new WrappedIOException( e );
978 + }
979 + try {
980 + writer.write( "<html>\n" );
981 + writer.write( "<head><title>Applet</title></head>\n" );
982 + writer.write( "<body>\n" );
983 + writer.write( "<applet code='org.lwjgl.util.applet.AppletLoader' archive='lwjgl_util_applet.jar, lzma.jar' codebase='.' width='640' height='480'>\n" );
984 + if ( project.hasVersion() ) {
985 + writer.write( "<param name='al_version' value='" + project.getVersion() + "'>\n" );
986 + }
987 + writer.write( "<param name='al_title' value='" + project + "'>\n" );
988 + writer.write( "<param name='al_main' value='" + project.getMain() + "'>\n" );
989 + writer.write( "<param name='al_jars' value='" );
990 + int i = 0;
991 + for ( String name : new Paths( appletDir, "*.jar.pack.lzma" ).getNames() ) {
992 + if ( i++ > 0 ) {
993 + writer.write( ", " );
994 + }
995 + writer.write( name );
996 + }
997 + writer.write( "'>\n" );
998 + Paths nativePaths = new Paths( appletDir, "*native*win*.jar.lzma", "*win*native*.jar.lzma" );
999 + if ( nativePaths.count() == 1 ) {
1000 + writer.write( "<param name='al_windows' value='" + nativePaths.getNames().get( 0 ) + "'>\n" );
1001 + }
1002 + nativePaths = new Paths( appletDir, "*native*mac*.jar.lzma", "*mac*native*.jar.lzma" );
1003 + if ( nativePaths.count() == 1 ) {
1004 + writer.write( "<param name='al_mac' value='" + nativePaths.getNames().get( 0 ) + "'>\n" );
1005 + }
1006 + nativePaths = new Paths( appletDir, "*native*linux*.jar.lzma", "*linux*native*.jar.lzma" );
1007 + if ( nativePaths.count() == 1 ) {
1008 + writer.write( "<param name='al_linux' value='" + nativePaths.getNames().get( 0 ) + "'>\n" );
1009 + }
1010 + nativePaths = new Paths( appletDir, "*native*solaris*.jar.lzma", "*solaris*native*.jar.lzma" );
1011 + if ( nativePaths.count() == 1 ) {
1012 + writer.write( "<param name='al_solaris' value='" + nativePaths.getNames().get( 0 ) + "'>\n" );
1013 + }
1014 + writer.write( "<param name='al_logo' value='appletlogo.png'>\n" );
1015 + writer.write( "<param name='al_progressbar' value='appletprogress.gif'>\n" );
1016 + writer.write( "<param name='separate_jvm' value='true'>\n" );
1017 + writer.write( "<param name='java_arguments' value='-Dsun.java2d.noddraw=true -Dsun.awt.noerasebackground=true -Dsun.java2d.d3d=false -Dsun.java2d.opengl=false -Dsun.java2d.pmoffscreen=false'>\n" );
1018 + writer.write( "</applet>\n" );
1019 + writer.write( "</body></html>\n" );
1020 + }
1021 + catch ( IOException e ) {
1022 + throw new WrappedIOException( e );
1023 + }
1024 + finally {
1025 + dispose( writer );
1026 + }
1027 + return appletDir;
1028 + }
1029 +
1030 + /**
1031 + * Compiles and executes the specified Java code. The code is compiled as if it were a Java method body.
1032 + * <p/>
1033 + * Imports statements can be used at the start of the code. These imports are automatically used:<br>
1034 + * import com.esotericsoftware.scar.Scar;<br>
1035 + * import com.esotericsoftware.filesystem.Paths;<br>
1036 + * import com.esotericsoftware.minlog.Log;<br>
1037 + * import static com.esotericsoftware.scar.Scar.*;<br>
1038 + * import static com.esotericsoftware.minlog.Log.*;<br>
1039 + * <p/>
1040 + * Entries can be added to the classpath by using "classpath [url];" statements at the start of the code. These classpath
1041 + * entries are checked before the classloader that loaded the Scar class is checked. Examples:<br>
1042 + * classpath someTools.jar;<br>
1043 + * classpath some/directory/of/class/files;<br>
1044 + * classpath http://example.com/someTools.jar;<br>
1045 + *
1046 + * @param parameters These parameters will be available in the scope where the code is executed.
1047 + */
1048 + public void executeCode( Project project, String code, HashMap<String, Object> parameters ) {
1049 + try {
1050 + // Wrap code in a class.
1051 + StringBuilder classBuffer = new StringBuilder( 2048 );
1052 + classBuffer.append( "import com.esotericsoftware.scar.*;\n" );
1053 + classBuffer.append( "import com.esotericsoftware.minlog.Log;\n" );
1054 + classBuffer.append( "import com.esotericsoftware.filesystem.Paths;\n" );
1055 + classBuffer.append( "import static com.esotericsoftware.scar.Scar.*;\n" );
1056 + classBuffer.append( "import static com.esotericsoftware.minlog.Log.*;\n" );
1057 + classBuffer.append( "public class Generated {\n" );
1058 + int pOverheadStartLines = 6;
1059 + classBuffer.append( "public void execute (" );
1060 + int i = 0;
1061 + for ( Entry<String, Object> entry : parameters.entrySet() ) {
1062 + if ( i++ > 0 ) {
1063 + classBuffer.append( ',' );
1064 + }
1065 + classBuffer.append( '\n' );
1066 + pOverheadStartLines++;
1067 + classBuffer.append( entry.getValue().getClass().getName() );
1068 + classBuffer.append( ' ' );
1069 + classBuffer.append( entry.getKey() );
1070 + }
1071 + classBuffer.append( "\n) throws Exception {\n" );
1072 + pOverheadStartLines += 2;
1073 +
1074 + // Append code, collecting imports statements and classpath URLs.
1075 + StringBuilder importBuffer = new StringBuilder( 512 );
1076 + ArrayList<URL> classpathURLs = new ArrayList<URL>();
1077 + BufferedReader reader = new BufferedReader( new StringReader( code ) );
1078 + boolean header = true;
1079 + while ( true ) {
1080 + String line = reader.readLine();
1081 + if ( line == null ) {
1082 + break;
1083 + }
1084 + String trimmed = line.trim();
1085 + if ( header && trimmed.startsWith( "import " ) && trimmed.endsWith( ";" ) ) {
1086 + importBuffer.append( line );
1087 + importBuffer.append( '\n' );
1088 + } else if ( header && trimmed.startsWith( "classpath " ) && trimmed.endsWith( ";" ) ) {
1089 + String path = substring( line.trim(), 10, -1 );
1090 + try {
1091 + classpathURLs.add( new URL( path ) );
1092 + }
1093 + catch ( MalformedURLException ex ) {
1094 + classpathURLs.add( new File( project.path( path ) ).toURI().toURL() );
1095 + }
1096 + } else {
1097 + if ( trimmed.length() > 0 ) {
1098 + header = false;
1099 + }
1100 + classBuffer.append( line );
1101 + classBuffer.append( '\n' );
1102 + }
1103 + }
1104 + classBuffer.append( "}}" );
1105 +
1106 + final String classCode = importBuffer.append( classBuffer ).toString();
1107 + if ( LOGGER.trace.isEnabled() ) {
1108 + progress( "Executing code:\n" + classCode );
1109 + }
1110 + // Compile class.
1111 + Class generatedClass = compileDynamicCodeToClass( pOverheadStartLines, classCode, classpathURLs.toArray( new URL[classpathURLs.size()] ) );
1112 +
1113 + // Execute.
1114 + Class[] parameterTypes = new Class[parameters.size()];
1115 + Object[] parameterValues = new Object[parameters.size()];
1116 + i = 0;
1117 + for ( Object object : parameters.values() ) {
1118 + parameterValues[i] = object;
1119 + parameterTypes[i++] = object.getClass();
1120 + }
1121 + generatedClass.getMethod( "execute", parameterTypes ).invoke( generatedClass.newInstance(), parameterValues );
1122 + }
1123 + catch ( Throwable ex ) {
1124 + throw new RuntimeException( "Error executing code:\n" + code.trim(), ex );
1125 + }
1126 + }
1127 +
1128 + // /**
1129 + // * Executes Java code in the specified project's document, if any.
1130 + // *
1131 + // * @return true if code was executed.
1132 + // */
1133 + // public boolean executeDocument( Project project )
1134 + // {
1135 + // String code = null; // todo: was -- project.getDocument();
1136 + // if ( code == null || code.trim().isEmpty() )
1137 + // {
1138 + // return false;
1139 + // }
1140 + // HashMap<String, Object> parameters = new HashMap<String, Object>();
1141 + // parameters.put( "project", project );
1142 + // executeCode( project, code, parameters );
1143 + // return true;
1144 + // }
1145 +
1146 + // /**
1147 + // * List of project names that have been built. {@link #buildDependencies(Project)} will skip any projects with a matching name.
1148 + // */
1149 + // static public final List<String> builtProjects = new ArrayList<String>();
1150 + //
1151 + static {
1152 + Paths.addDefaultGlobExcludes( "**/.svn/**" );
1153 + }
1154 +
1155 + /// todo: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Here be Dragons ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1156 + /// todo: ==================================================================================================================
1157 + /// todo: ==================================================================================================================
1158 + /// todo: ==================================================================================================================
1159 + /// todo: ==================================================================================================================
1160 + /// todo: ==================================================================================================================
1161 + /// todo: ==================================================================================================================
1162 + /// todo: ==================================================================================================================
1163 + /// todo: ==================================================================================================================
1164 + /// todo: ==================================================================================================================
1165 + /// todo: ==================================================================================================================
1166 + /// todo: ==================================================================================================================
1167 +
1168 + private static class BuildFileFilter implements FileFilter {
1169 + private static final String DEFAULT_JAVA_PROJECT_FILE_NAME = DEFAULT_PROJECT_FILE_NAME + JAVA_EXTENSION;
1170 + private static final String DEFAULT_YAML_PROJECT_FILE_NAME = DEFAULT_PROJECT_FILE_NAME + YAML_EXTENSION;
1171 +
1172 + @Override
1173 + public boolean accept( File pFile ) {
1174 + if ( pFile.isFile() ) {
1175 + String zName = pFile.getName();
1176 + if ( DEFAULT_JAVA_PROJECT_FILE_NAME.equalsIgnoreCase( zName ) || DEFAULT_YAML_PROJECT_FILE_NAME.equalsIgnoreCase( zName ) ) {
1177 + return pFile.canRead();
1178 + }
1179 + }
1180 + return false;
1181 + }
1182 +
1183 + public static final FileFilter INSTANCE = new BuildFileFilter();
1184 + }
1185 +
1186 + private static class ProjectCache {
1187 + private Map<String, Project> mProjectByName = new HashMap<String, Project>();
1188 + private Map<String, Project> mProjectByPath = new HashMap<String, Project>();
1189 +
1190 + public synchronized Project getByPath( String pPath ) {
1191 + return mProjectByPath.get( pPath );
1192 + }
1193 +
1194 + private Project initialize( ProjectFactory pFactory, String pPath, Project pProject ) {
1195 + synchronized ( this ) {
1196 + String zName = pProject.getName();
1197 + Project zProject = mProjectByName.get( zName );
1198 + if ( zProject != null ) {
1199 + mProjectByPath.put( pPath, zProject );
1200 + return zProject;
1201 + }
1202 + mProjectByPath.put( pPath, pProject );
1203 + mProjectByName.put( zName, pProject );
1204 + }
1205 + pProject.initialize( pFactory );
1206 + return pProject;
1207 + }
1208 +
1209 + public Set<Project> getAllProjects() {
1210 + return new HashSet<Project>( mProjectByName.values() );
1211 + }
1212 + }
1213 +
1214 + protected Runnable createRunnableFor( String pMethodName ) {
1215 + Runnable zRunnable = createRunnableFor( this, pMethodName );
1216 + return (zRunnable != null) ? zRunnable : createRunnableFor( mLaunchProject, pMethodName );
1217 + }
1218 +
1219 + protected Runnable createRunnableFor( final Object pObject, String pMethodName ) {
1220 + final Method zMethod = getMatchingMethod( pObject, pMethodName );
1221 + return (zMethod == null) ? null : new Runnable() {
1222 + @Override
1223 + public void run() {
1224 + try {
1225 + zMethod.invoke( pObject );
1226 + }
1227 + catch ( Exception e ) {
1228 + throw new RuntimeException( e );
1229 + }
1230 + }
1231 + };
1232 + }
1233 +
1234 + protected Method getMatchingMethod( Object pObject, String pMethodName ) {
1235 + List<Method> zFound = new ArrayList<Method>();
1236 + Method[] zMethods = pObject.getClass().getMethods();
1237 + for ( Method zMethod : zMethods ) {
1238 + if ( zMethod.getReturnType().equals( Void.TYPE ) && (zMethod.getParameterTypes().length == 0) ) {
1239 + if ( pMethodName.equals( zMethod.getName() ) ) {
1240 + return zMethod;
1241 + }
1242 + if ( pMethodName.equalsIgnoreCase( zMethod.getName() ) ) {
1243 + zFound.add( zMethod );
1244 + }
1245 + }
1246 + }
1247 + if ( zFound.size() == 0 ) {
1248 + return null;
1249 + }
1250 + if ( zFound.size() == 1 ) {
1251 + return zFound.get( 0 );
1252 + }
1253 + throw new IllegalArgumentException( "Multiple Methods " + zFound + " found on '" + pObject.getClass().getSimpleName() + "' than match: " + pMethodName );
1254 + }
1255 +
1256 + protected void createLaunchProject() {
1257 + mLaunchProject = project( CANONICAL_USER_DIR, mArgs.get( "file", "." ) );
1258 + }
1259 +
1260 + protected int run() {
1261 + System.out.println( getClass().getSimpleName() + " vs " + VERSION );
1262 + if ( mArgs.count() == 0 ) {
1263 + mLaunchProject.build();
1264 + return 0;
1265 + }
1266 + List<Runnable> zToExecute = getArgsBasedRunnables();
1267 + for ( Runnable zRunnable : zToExecute ) {
1268 + zRunnable.run();
1269 + }
1270 + return 0;
1271 + }
1272 +
1273 + private ArrayList<Runnable> getArgsBasedRunnables() {
1274 + ArrayList<Runnable> zRunnables = new ArrayList<Runnable>();
1275 + List<String> zUnrecognizedNames = new ArrayList<String>();
1276 + for ( Arguments.NameValuePair zPair; null != (zPair = mArgs.getNext()); ) {
1277 + Runnable zRunnable = createRunnableFor( zPair.getName() );
1278 + if ( zRunnable != null ) {
1279 + zRunnables.add( zRunnable );
1280 + } else {
1281 + zUnrecognizedNames.add( zPair.getName() );
1282 + }
1283 + }
1284 + if ( !zUnrecognizedNames.isEmpty() ) {
1285 + System.err.println( "\nUnrecognized Command Line Args:" );
1286 + for ( String zName : zUnrecognizedNames ) {
1287 + System.err.println( " " + zName );
1288 + }
1289 + System.exit( 1 );
1290 + }
1291 + return zRunnables;
1292 + }
1293 +
1294 + public static void main( String[] args )
1295 + throws Exception {
1296 + Arguments arguments = new Arguments( args );
1297 + Scar scar = new Scar( arguments );
1298 + scar.initLoggerFactory();
1299 + scar.createLaunchProject();
1300 + System.exit( scar.run() );
1301 + }
1302 + }