88import java .io .File ;
99import java .io .FileOutputStream ;
1010import java .io .InputStream ;
11+ import java .lang .reflect .InvocationTargetException ;
1112import java .lang .reflect .Method ;
1213import java .net .URI ;
14+ import java .net .URISyntaxException ;
1315import java .net .URL ;
1416import java .net .URLClassLoader ;
1517import java .util .ArrayList ;
1618import java .util .Arrays ;
1719import java .util .Enumeration ;
20+ import java .util .HashMap ;
1821import java .util .List ;
22+ import java .util .Map ;
1923import java .util .jar .JarEntry ;
2024import java .util .jar .JarFile ;
2125
2226public class JarMain implements Runnable {
23- public static final String MAIN = "/" + JarMain .class .getName ().replace ('.' , '/' ) + ".class" ;
24-
25- private String [] args ;
26- private String path , jarfile ;
27- private boolean debug ;
28- private File extractRoot ;
29-
30- public JarMain (String [] args ) throws Exception {
27+
28+ static final String MAIN = "/" + JarMain .class .getName ().replace ('.' , '/' ) + ".class" ;
29+
30+ final boolean debug = isDebug ();
31+
32+ protected final String [] args ;
33+ protected final String archive ;
34+ private final String path ;
35+
36+ protected File extractRoot ;
37+
38+ JarMain (String [] args ) {
3139 this .args = args ;
3240 URL mainClass = getClass ().getResource (MAIN );
33- this .path = mainClass .toURI ().getSchemeSpecificPart ();
34- this .jarfile = this .path .replace ("!" + MAIN , "" ).replace ("file:" , "" );
35- this .debug = isDebug ();
36- this .extractRoot = File .createTempFile ("jruby" , "extract" );
37- this .extractRoot .delete ();
38- this .extractRoot .mkdirs ();
41+ try {
42+ this .path = mainClass .toURI ().getSchemeSpecificPart ();
43+ }
44+ catch (URISyntaxException e ) {
45+ throw new RuntimeException (e );
46+ }
47+ archive = this .path .replace ("!" + MAIN , "" ).replace ("file:" , "" );
48+
3949 Runtime .getRuntime ().addShutdownHook (new Thread (this ));
4050 }
51+
52+ protected URL [] extractArchive () throws Exception {
53+ final JarFile jarFile = new JarFile (archive );
54+ try {
55+ Map <String , JarEntry > jarNames = new HashMap <String , JarEntry >();
56+ for (Enumeration <JarEntry > e = jarFile .entries (); e .hasMoreElements (); ) {
57+ JarEntry entry = e .nextElement ();
58+ String extractPath = getExtractEntryPath (entry );
59+ if ( extractPath != null ) jarNames .put (extractPath , entry );
60+ }
61+
62+ extractRoot = File .createTempFile ("jruby" , "extract" );
63+ extractRoot .delete (); extractRoot .mkdirs ();
4164
42- private URL [] extractJRuby () throws Exception {
43- JarFile jf = new JarFile (this .jarfile );
44- List <String > jarNames = new ArrayList <String >();
45- for (Enumeration <JarEntry > eje = jf .entries (); eje .hasMoreElements (); ) {
46- String name = eje .nextElement ().getName ();
47- if (name .startsWith ("META-INF/lib" ) && name .endsWith (".jar" )) {
48- jarNames .add ("/" + name );
65+ final List <URL > urls = new ArrayList <URL >();
66+ for (Map .Entry <String , JarEntry > e : jarNames .entrySet ()) {
67+ URL entryURL = extractEntry (e .getValue (), e .getKey ());
68+ if (entryURL != null ) urls .add ( entryURL );
4969 }
70+ return (URL []) urls .toArray (new URL [urls .size ()]);
5071 }
51-
52- List <URL > urls = new ArrayList <URL >();
53- for (String name : jarNames ) {
54- urls .add (extractJar (name ));
72+ finally {
73+ jarFile .close ();
5574 }
56-
57- return (URL []) urls .toArray (new URL [urls .size ()]);
5875 }
5976
60- private URL extractJar (String jarpath ) throws Exception {
61- InputStream jarStream = new URI ("jar" , path .replace (MAIN , jarpath ), null ).toURL ().openStream ();
62- String jarname = jarpath .substring (jarpath .lastIndexOf ("/" ) + 1 , jarpath .lastIndexOf ("." ));
63- File jarFile = new File (extractRoot , jarname + ".jar" );
64- jarFile .deleteOnExit ();
65- FileOutputStream outStream = new FileOutputStream (jarFile );
77+ protected String getExtractEntryPath (final JarEntry entry ) {
78+ final String name = entry .getName ();
79+ if ( name .startsWith ("META-INF/lib" ) && name .endsWith (".jar" ) ) {
80+ return name .substring (name .lastIndexOf ("/" ) + 1 );
81+ }
82+ return null ; // do not extract entry
83+ }
84+
85+ protected URL extractEntry (final JarEntry entry , final String path ) throws Exception {
86+ final File file = new File (extractRoot , path );
87+ if ( entry .isDirectory () ) {
88+ file .mkdirs ();
89+ return null ;
90+ }
91+ final String entryPath = entryPath (entry .getName ());
92+ final InputStream entryStream ;
93+ try {
94+ entryStream = new URI ("jar" , entryPath , null ).toURL ().openStream ();
95+ }
96+ catch (IllegalArgumentException e ) {
97+ // TODO gems '%' file name "encoding" ?!
98+ debug ("failed to open jar:" + entryPath + " skipping entry: " + entry .getName (), e );
99+ return null ;
100+ }
101+ final File parent = file .getParentFile ();
102+ if ( parent != null ) parent .mkdirs ();
103+ FileOutputStream outStream = new FileOutputStream (file );
104+ final byte [] buf = new byte [65536 ];
66105 try {
67- byte [] buf = new byte [65536 ];
68106 int bytesRead = 0 ;
69- while ((bytesRead = jarStream .read (buf )) != -1 ) {
107+ while ((bytesRead = entryStream .read (buf )) != -1 ) {
70108 outStream .write (buf , 0 , bytesRead );
71109 }
72- } finally {
73- jarStream .close ();
110+ }
111+ finally {
112+ entryStream .close ();
74113 outStream .close ();
114+ file .deleteOnExit ();
75115 }
76- debug (jarname + ".jar extracted to " + jarFile .getPath ());
77- return jarFile .toURI ().toURL ();
116+ if (false ) debug (entry .getName () + " extracted to " + file .getPath ());
117+ return file .toURI ().toURL ();
118+ }
119+
120+ protected String entryPath (String name ) {
121+ if ( ! name .startsWith ("/" ) ) name = "/" + name ;
122+ return path .replace (MAIN , name );
78123 }
79124
80- private int launchJRuby ( URL [] jars ) throws Exception {
125+ protected Object newScriptingContainer ( final URL [] jars ) throws Exception {
81126 System .setProperty ("org.jruby.embed.class.path" , "" );
82- URLClassLoader loader = new URLClassLoader (jars );
127+ ClassLoader loader = new URLClassLoader (jars );
83128 Class scriptingContainerClass = Class .forName ("org.jruby.embed.ScriptingContainer" , true , loader );
84129 Object scriptingContainer = scriptingContainerClass .newInstance ();
85-
86- Method argv = scriptingContainerClass .getDeclaredMethod ("setArgv" , new Class [] {String [].class });
87- argv .invoke (scriptingContainer , new Object [] {args });
88- Method setClassLoader = scriptingContainerClass .getDeclaredMethod ("setClassLoader" , new Class [] {ClassLoader .class });
89- setClassLoader .invoke (scriptingContainer , new Object [] {loader });
90- debug ("invoking " + jarfile + " with: " + Arrays .deepToString (args ));
91-
92- Method runScriptlet = scriptingContainerClass .getDeclaredMethod ("runScriptlet" , new Class [] {String .class });
93- return ((Number ) runScriptlet .invoke (scriptingContainer , new Object [] {
94- "begin\n " +
95- "require 'META-INF/init.rb'\n " +
96- "require 'META-INF/main.rb'\n " +
97- "0\n " +
98- "rescue SystemExit => e\n " +
99- "e.status\n " +
100- "end"
101- })).intValue ();
130+ debug ("scripting container class loader urls: " + Arrays .toString (jars ));
131+ invokeMethod (scriptingContainer , "setArgv" , (Object ) args );
132+ invokeMethod (scriptingContainer , "setClassLoader" , new Class [] { ClassLoader .class }, loader );
133+ return scriptingContainer ;
134+ }
135+
136+ protected int launchJRuby (final URL [] jars ) throws Exception {
137+ final Object scriptingContainer = newScriptingContainer (jars );
138+ debug ("invoking " + archive + " with: " + Arrays .deepToString (args ));
139+ Object outcome = invokeMethod (scriptingContainer , "runScriptlet" , launchScript ());
140+ return ( outcome instanceof Number ) ? ( (Number ) outcome ).intValue () : 0 ;
102141 }
103142
104- private int start () throws Exception {
105- URL [] u = extractJRuby ();
106- return launchJRuby (u );
143+ protected String launchScript () {
144+ return
145+ "begin\n " +
146+ " require 'META-INF/init.rb'\n " +
147+ " require 'META-INF/main.rb'\n " +
148+ " 0\n " +
149+ "rescue SystemExit => e\n " +
150+ " e.status\n " +
151+ "end" ;
152+ }
153+
154+ protected int start () throws Exception {
155+ final URL [] jars = extractArchive ();
156+ return launchJRuby (jars );
107157 }
108158
109- private void debug (String msg ) {
110- if (debug ) {
111- System .out .println (msg );
112- }
159+ protected void debug (String msg ) {
160+ debug (msg , null );
113161 }
114162
115- private void delete (File f ) {
163+ protected void debug (String msg , Throwable t ) {
164+ if (debug ) System .out .println (msg );
165+ if (debug && t != null ) t .printStackTrace (System .out );
166+ }
167+
168+ protected void delete (File f ) {
116169 if (f .isDirectory ()) {
117170 File [] children = f .listFiles ();
118171 for (int i = 0 ; i < children .length ; i ++) {
@@ -121,30 +174,57 @@ private void delete(File f) {
121174 }
122175 f .delete ();
123176 }
124-
177+
125178 public void run () {
126- delete (extractRoot );
179+ if ( extractRoot != null ) delete (extractRoot );
127180 }
128181
129182 public static void main (String [] args ) {
183+ doStart (new JarMain (args ));
184+ }
185+
186+ protected static void doStart (final JarMain main ) {
130187 try {
131188 int exit = new JarMain (args ).start ();
132189 if (isSystemExitEnabled ()) System .exit (exit );
133190 } catch (Exception e ) {
191+ System .err .println ("error: " + e .toString ());
134192 Throwable t = e ;
135193 while (t .getCause () != null && t .getCause () != t ) {
136194 t = t .getCause ();
137195 }
138-
139196 if (isDebug ()) {
140197 t .printStackTrace ();
141198 }
142199 System .exit (1 );
143200 }
144201 }
202+
203+ protected static Object invokeMethod (final Object self , final String name , final Object ... args )
204+ throws NoSuchMethodException , IllegalAccessException , Exception {
205+
206+ final Class [] signature = new Class [args .length ];
207+ for ( int i = 0 ; i < args .length ; i ++ ) signature [i ] = args [i ].getClass ();
208+ return invokeMethod (self , name , signature , args );
209+ }
145210
146- private static boolean isDebug () {
147- return System .getProperty ("warbler.debug" ) != null ;
211+ protected static Object invokeMethod (final Object self , final String name , final Class [] signature , final Object ... args )
212+ throws NoSuchMethodException , IllegalAccessException , Exception {
213+ Method method = self .getClass ().getDeclaredMethod (name , signature );
214+ try {
215+ return method .invoke (self , args );
216+ }
217+ catch (InvocationTargetException e ) {
218+ Throwable target = e .getTargetException ();
219+ if (target instanceof Exception ) {
220+ throw (Exception ) target ;
221+ }
222+ throw e ;
223+ }
224+ }
225+
226+ static boolean isDebug () {
227+ return Boolean .getBoolean ("warbler.debug" );
148228 }
149229
150230 /**
@@ -155,4 +235,5 @@ private static boolean isDebug() {
155235 private static boolean isSystemExitEnabled (){
156236 return System .getProperty ("warbler.skip_system_exit" ) == null ; //omission enables System.exit use
157237 }
238+
158239}
0 commit comments