Subversion Repository Public Repository

litesoft

Diff Revisions 949 vs 950 for /trunk/Java/ScarPlus/src/com/esotericsoftware/scar/Scar.java

Diff revisions: vs.
  @@ -1,1536 +1,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 - {
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 + {
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 + }