1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.backsource.utils.lang;
21
22
23 import java.net.URL;
24 import java.net.URLClassLoader;
25 import java.net.JarURLConnection;
26 import java.net.MalformedURLException;
27 import java.lang.reflect.Method;
28 import java.lang.reflect.Modifier;
29 import java.lang.reflect.InvocationTargetException;
30 import java.util.StringTokenizer;
31 import java.util.jar.Attributes;
32 import java.io.IOException;
33
34 import java.net.URL;
35 import java.net.URLConnection;
36 import java.net.URLStreamHandler;
37
38 import java.io.BufferedInputStream;
39 import java.io.BufferedOutputStream;
40 import java.io.DataInputStream;
41 import java.io.File;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.OutputStream;
46
47 import java.util.HashMap;
48 import java.util.Map;
49 /***
50 * A dynamic main to use instead of the normal main i jar-files to be able to load classes from jar-files inside the jarfile run by java -jar.
51 *
52 * <p>main makes it possible to package applications in a jar-file and run the with the java -jar XXX.jar command. This class works like a standin for the main you want to run. by specifying this class as the Main-class in the manifest it is possible to distribute lib:s the applications depends on as embedded jarfiles.</p>
53 *
54 *<p>Main will add all jar-files mentioned in the Class-path attribute to the classpath without them having to exist outside the running jar-application. It will the start the main application specifyed in the Dynamic-main attribute. A normal manifest could look like this:</p>
55 <pre>
56 Main-class: org.backsource.utils.lang.Main
57 Class-path: lib/xerces2.0.2.jar lib/xalan2.4.D1.jar lib/xml-apis.jar lib/timutils.jar
58
59 Name: se/tim/utils/lang/Main.class
60 Dynamic-Main: org.backsource.utils.javadoc.XMLEscaper
61 </pre>
62
63 <p>The libraries would be embedded in the distributed jar-file, which could look like this:</p>
64
65 <pre>
66 [pra@pra jars]$ unzip -l xmlescaper.jar
67 Archive: xmlescaper.jar
68 Length Date Time Name
69 -------- ---- ---- ----
70 0 09-09-02 11:58 META-INF/
71 264 09-09-02 11:58 META-INF/MANIFEST.MF
72 0 08-19-02 13:28 se/
73 0 05-21-02 12:41 se/tim/
74 0 08-22-02 12:58 se/tim/utils/
75 0 08-23-02 16:53 se/tim/utils/lang/
76 4544 09-09-02 11:57 se/tim/utils/lang/Main.class
77 987 09-09-02 11:57 se/tim/utils/lang/Main$MainClassLoader.class
78 3174 09-09-02 11:57 se/tim/utils/lang/Main$Handler.class
79 0 09-09-02 11:58 lib/
80 108484 08-22-02 13:16 lib/xml-apis.jar
81 933730 08-22-02 12:59 lib/xerces2.0.2.jar
82 983377 08-22-02 12:59 lib/xalan2.4.D1.jar
83 58971 09-03-02 10:00 lib/timutils.jar
84 -------- -------
85 2093531 14 files
86 </pre>
87
88 } // end of main ()
89
90 } // end of main ()
91
92 *
93 *
94 * @author <a href="mailto:pra@tim.se">Peter Antman</a>
95 * @version $Revision: 1.1.1.1 $
96 */
97
98 public class Main
99 {
100 public static final Attributes.Name DYNAMIC_MAIN = new Attributes.Name("Dynamic-Main");
101
102
103 public static final boolean debug = false;
104 String dynamicMain;
105 URL[] classpath;
106 URL where;
107
108
109 public Main () throws Exception{
110 Class c = getClass();
111 where = c.getResource("/" + c.getName().replace('.','/') + ".class");
112
113 JarURLConnection uc = (JarURLConnection)where.openConnection();
114 Attributes attr = uc.getMainAttributes();
115 if ( attr != null) {
116
117 setClasspath(uc.getJarFileURL().toString(), attr.getValue(Attributes.Name.CLASS_PATH));
118 }
119 Attributes uattr = uc.getAttributes();
120 dynamicMain = uattr != null ? uattr.getValue(DYNAMIC_MAIN) : null;
121 if (dynamicMain== null ) {
122 throw new Exception("Could not find Dynamic-main to load");
123 }
124 }
125
126
127 public void setClasspath(String jarfile, String manifestcl)throws MalformedURLException,IOException {
128 StringTokenizer toker = new StringTokenizer(manifestcl);
129 classpath = new URL[ toker.countTokens() ];
130 for (int i = 0;toker.hasMoreTokens();i++) {
131 URL inside = new URL("njar","",20,"njar:"+jarfile + "^/" + toker.nextToken()+ "^/", new Handler());
132 JarURLConnection uc = (JarURLConnection)inside.openConnection();
133
134 classpath[i] =new URL("jar:"+uc.getJarFileURL()+"!/");
135
136 if ( debug)
137 System.out.println("URL in cl" + classpath[i]);
138 }
139
140 }
141
142 public void invokeClass(String[] args)
143 throws ClassNotFoundException,
144 NoSuchMethodException,
145 InvocationTargetException,
146 IOException
147 {
148 ClassLoader old = Thread.currentThread().getContextClassLoader();
149 URLClassLoader dynLoader = new MainClassLoader(classpath,old);
150 Thread.currentThread().setContextClassLoader(dynLoader);
151
152
153 URL[] deb = dynLoader.getURLs();
154 for ( int i = 0;i<deb.length;i++) {
155 if ( debug)
156 System.out.println(deb[i]);
157
158
159
160 }
161
162
163 Class c = dynLoader.loadClass(dynamicMain);
164 Method m = c.getMethod("main", new Class[] { args.getClass() });
165 m.setAccessible(true);
166 int mods = m.getModifiers();
167 if (m.getReturnType() != void.class || !Modifier.isStatic(mods) ||
168 !Modifier.isPublic(mods)) {
169 throw new NoSuchMethodException("main");
170 }
171 try {
172 m.invoke(null, new Object[] { args });
173 } catch (IllegalAccessException e) {
174
175 }finally {
176 Thread.currentThread().setContextClassLoader(old);
177 }
178
179 }
180
181 public void pwd() throws Exception {
182 Class c = getClass();
183 URL where = c.getResource("/" + c.getName().replace('.','/') + ".class");
184 if ( debug)
185 System.out.println(where);
186 JarURLConnection uc = (JarURLConnection)where.openConnection();
187 Attributes attr = uc.getMainAttributes();
188 if ( debug) {
189 System.out.println( uc.getJarFileURL() );
190 System.out.println( attr != null ? attr.getValue(Attributes.Name.MAIN_CLASS) : null);
191 System.out.println( attr != null ? attr.getValue(Attributes.Name.CLASS_PATH ) : null);
192
193 }
194 Attributes uattr = uc.getAttributes();
195 if ( debug) {
196
197
198 System.out.println( uattr != null ? uattr.getValue(DYNAMIC_MAIN) : null);
199
200 System.out.println(where.getFile());
201 System.out.println(where.getHost());
202 System.out.println(where.getPath());
203
204 }
205 }
206
207 public static void main(String[] args) throws Exception {
208 Main m = new Main();
209
210 m.invokeClass(args);
211
212 }
213
214 public class MainClassLoader extends URLClassLoader {
215 MainClassLoader(URL[] path, ClassLoader parent) {
216 super(path,parent);
217 }
218
219 protected Class findClass(String name)
220 throws ClassNotFoundException{
221 if (debug)
222 System.out.println("Looking up resource " + name);
223 return super.findClass(name);
224 }
225 }
226
227 public class Handler
228 extends URLStreamHandler
229 {
230
231 public static final String PROTOCOL = "njar";
232 public static final String NJAR_SEPARATOR = "^/";
233 public static final String JAR_SEPARATOR = "!/";
234
235 protected Map savedJars = new HashMap();
236
237 public URLConnection openConnection(final URL url)
238 throws IOException
239 {
240 String file = url.getFile();
241 String embeddedURL = file;
242 String jarPath = "";
243
244 int pos = file.lastIndexOf(NJAR_SEPARATOR);
245 if (pos >= 0)
246 {
247 embeddedURL = file.substring(0, pos);
248 if (file.length() > pos + NJAR_SEPARATOR.length())
249 jarPath = file.substring(pos + NJAR_SEPARATOR.length());
250 }
251
252 if (embeddedURL.startsWith(PROTOCOL))
253 {
254 if (debug)
255 System.out.println("Opening next nested jar: " + embeddedURL);
256 File tempJar = (File) savedJars.get(embeddedURL);
257 if (tempJar == null)
258 {
259 if (debug)
260 System.out.println(embeddedURL);
261 InputStream embededData = new URL(url,embeddedURL, new Handler()).openStream();
262 tempJar = File.createTempFile("nested-", ".jar");
263 tempJar.deleteOnExit();
264
265 storeJar(embededData, new FileOutputStream(tempJar));
266 savedJars.put(embeddedURL, tempJar);
267 }
268
269 String t = tempJar.getCanonicalFile().toURL().toExternalForm();
270 if ( debug)
271 System.out.println("file URL : " + t);
272 t = "njar:" + t + NJAR_SEPARATOR + jarPath;
273 if ( debug)
274 System.out.println("Opening saved jar: " + t);
275
276 return new URL(url,t,new Handler()).openConnection();
277
278 }
279 else
280 {
281 if ( debug)
282 System.out.println("Opening final nested jar: " + "jar:" +embeddedURL+ JAR_SEPARATOR + jarPath);
283 return new URL("jar:" + embeddedURL + JAR_SEPARATOR + jarPath).openConnection();
284 }
285 }
286
287 protected void storeJar(final InputStream in, final OutputStream out)
288 throws IOException
289 {
290
291 BufferedInputStream bis = null;
292 BufferedOutputStream bos = null;
293 try
294 {
295 bis = new BufferedInputStream(in);
296 bos = new BufferedOutputStream(out);
297
298 byte data[] = new byte[512];
299 int c;
300 while ((c = bis.read(data)) >= 0)
301 {
302 bos.write(data, 0, c);
303 }
304
305 }
306 finally
307 {
308 try
309 {
310 bis.close();
311 }
312 catch (IOException ignore)
313 {
314 }
315 try
316 {
317 bos.close();
318 }
319 catch (IOException ignore)
320 {
321 }
322 }
323 }
324
325 }
326 }