View Javadoc
1   package com.starphoenixmedia.candle_pos;
2   
3   import java.io.ByteArrayOutputStream;
4   import java.io.File;
5   import java.io.FileInputStream;
6   import java.io.IOException;
7   import java.io.InputStream;
8   import java.net.MalformedURLException;
9   import java.net.URL;
10  import java.util.ArrayList;
11  import java.util.Arrays;
12  import java.util.Enumeration;
13  import java.util.HashMap;
14  import java.util.Iterator;
15  import java.util.Objects;
16  import java.util.function.Consumer;
17  import java.util.jar.Attributes;
18  import java.util.jar.Attributes.Name;
19  import java.util.jar.JarEntry;
20  import java.util.jar.JarFile;
21  import java.util.jar.Manifest;
22  import java.util.logging.Level;
23  import java.util.logging.Logger;
24  import java.util.zip.ZipEntry;
25  
26  /**
27   *
28   * @author Jessica Hawkwell
29   */
30  public class FireCodeClassLoader extends ClassLoader
31  {
32  	/**
33  	 * JarName -> File mapping.
34  	 * Also used to determine if a jar has already been loaded.
35  	 */
36  	private static HashMap<String, File> jars;
37  	/**
38  	 * ClassPath -> File mapping.
39  	 * Also used to determine if a ClassPath has already been loaded.
40  	 */
41  	private static HashMap<String, File> flds;
42  	/**
43  	 * Set buffer length to 4096.
44  	 */
45  	public static final int BUFFER_LEN = 4096;
46  	/**
47  	 * Package lists.
48  	 */
49  	private static HashMap<String, Package> pkgs;
50  	/**
51  	 * Package -> Jar/CP mapping.
52  	 */
53  	private static HashMap<String, String> maps;
54  	/**
55  	 * Loaded classes.
56  	 */
57  	private static HashMap<String, Class<?>> clss;
58  	/**
59  	 * Parent class loader.
60  	 */
61  	private final ClassLoader parent;
62  
63  	/**
64  	 * Constructor.
65  	 */
66  	public FireCodeClassLoader()
67  	{
68  		parent = getClass().getClassLoader();
69  		jars = new HashMap<>();
70  		flds = new HashMap<>();
71  		pkgs = new HashMap<>();
72  		clss = new HashMap<>();
73  		maps = new HashMap<>();
74  	}
75  
76  	/**
77  	 * Constructor.
78  	 * @param cl ClassLoader to be considered the parent
79  	 */
80  	public FireCodeClassLoader(ClassLoader cl)
81  	{
82  		parent = cl;
83  		jars = new HashMap<>();
84  		flds = new HashMap<>();
85  		pkgs = new HashMap<>();
86  		clss = new HashMap<>();
87  		maps = new HashMap<>();
88  	}
89  
90  	/** Initialize this ClassLoader.
91  	 */
92  	public void init()
93  	{
94  		Iterator<String> paths;
95  		paths = cpDirs(System.getProperty("java.class.path"));
96  
97  		File f;
98  		String s;
99  		while ( paths.hasNext() )
100 		{
101 			s = paths.next();
102 			f = new File(s);
103 			if ( f.isDirectory() ) { addDir(f); }
104 			else if ( s.toLowerCase().endsWith(".jar") ) { addJar(f); }
105 		}
106 	}
107 
108 	@Override public Class<?> findClass(String name) throws ClassNotFoundException
109 	{
110 		if ( clss.containsKey(name) ) { return clss.get(name); }
111 		else { throw new ClassNotFoundException(name); }
112 	}
113 
114 	@Override public URL getResource(final String path) { return getResource(true, path); }
115 	private URL getResource(boolean goup, final String path)
116 	{
117 		String pth;
118 		URL u = null;
119 
120 		if ( path.startsWith("/") ) { pth = path.substring(1); } else { pth = path; }
121 		try { u = this.getResourceLocator(pth); } catch ( Throwable t ) { }
122 		if ( (u == null) && goup ) { u = parent.getResource(path); }
123 
124 		return u;
125 	}
126 
127 	@Override public Package getPackage(String name) { return pkgs.get(name); }
128 	@Override public Package[] getPackages() { return pkgs.values().toArray(new Package[]{}); }
129 
130 	private URL getResourceLocator(final String path) throws Throwable
131 	{
132 		JarFile jf;
133 		ZipEntry ze;
134 		Iterator<File> it;
135 		File f;
136 
137 		// FAST search
138 		if ( !maps.isEmpty() )
139 		{
140 			String pack = path.replace("/", ".").substring(0, path.lastIndexOf("/"));
141 			if ( maps.containsKey(pack) )
142 			{
143 				f = new File(maps.get(pack));
144 				if ( f.isDirectory() )
145 				{ return new URL("file:".concat(maps.get(pack)).concat(File.separator).concat(path)); }
146 				else { return new URL("jar:file:".concat(maps.get(pack)).concat("!/").concat(path)); }
147 			}
148 		}
149 
150 		// slow search
151 		if ( !jars.isEmpty() )
152 		{
153 			System.err.println("Warning: Slow search: ".concat(path));
154 			it = jars.values().iterator();
155 			while ( it.hasNext() )
156 			{
157 				f = it.next();
158 				jf = new JarFile(f);
159 				ze = jf.getEntry(path);
160 				jf.close();
161 				if ( ze != null )
162 				{ return new URL("jar:file:".concat(f.getCanonicalPath()).concat("!/").concat(path)); }
163 			}
164 		}
165 		File t;
166 		if ( !flds.isEmpty() )
167 		{
168 			System.err.println("Warning: Slow search: ".concat(path));
169 			it = flds.values().iterator();
170 			while ( it.hasNext() )
171 			{
172 				f = it.next();
173 				try
174 				{
175 					t = new File("file:".concat(f.getCanonicalPath()).concat(File.separator).concat(path));
176 					if ( t.exists() ) { return new URL(t.getCanonicalPath()); }
177 				}
178 				catch ( IOException x ) {}
179 			}
180 		}
181 		return null;
182 	}
183 
184 	@Override public InputStream getResourceAsStream(final String path)
185 	{ return getResourceAsStream(true, path); }
186 	private InputStream getResourceAsStream(boolean goup, final String path)
187 	{
188 		final URL u = getResource(goup, path);
189 		if ( u == null ) { return null; }
190 		try { return u.openStream(); } catch ( IOException e ) { e.printStackTrace(System.out); }
191 		return null;
192 	}
193 
194 	@Override public Enumeration<URL> getResources(String name) throws IOException
195 	{
196 		if ( jars.isEmpty() ) { return null; }
197 		ArrayList<URL> ar = new ArrayList<>();
198 		Iterator<File> it = jars.values().iterator();
199 		File f;
200 		JarFile jf;
201 		ZipEntry ze;
202 
203 		while ( it.hasNext() )
204 		{
205 			f = it.next();
206 			jf = new JarFile(f);
207 			ze = jf.getEntry(name);
208 			jf.close();
209 			if ( ze != null ) { ar.add(new URL("jar:file:".concat(f.getCanonicalPath()).concat("!/").concat(name))); }
210 		}
211 
212 		return new Iterator2Enum<>(ar.iterator());
213 	}
214 
215 	@Override public Class<?> loadClass(final String name) throws ClassNotFoundException
216 	{ return loadClass(name, true); }
217 	@Override protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException
218 	{
219 		// fast exit
220 		String pack = name.substring(0, name.lastIndexOf('.'));
221 		if ( !maps.containsKey(pack) ) { return parent.loadClass(name); }
222 
223 		//Class<?> cls;
224 		Class<?> cls = findLoadedClass(name);
225 		if ( cls != null ) { return cls; } // */
226 
227 		final InputStream is = getResourceAsStream(false, name.replaceAll("\\.", "/").concat(".class"));
228 		if ( is == null ) { cls = parent.loadClass(name); }
229 		else
230 		{
231 			try { return loadClassWorker(name, resolve, is); }
232 			catch ( IOException ex ) { throw new ClassNotFoundException("Couln't load ".concat(name)); }
233 		}
234 
235 		return cls;
236 	}
237 
238 	private Class<?> loadClassWorker(final String name, final boolean resolve, final InputStream is)
239 		throws ClassNotFoundException, IOException
240 	{
241 		getClassLoadingLock(name);
242 		Class<?> cls;
243 
244 		final byte[] buffer = new byte[BUFFER_LEN];
245 		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
246 		byte[] bytecode;
247 
248 		int i;
249 		while ( (i = is.read(buffer, 0, BUFFER_LEN)) > 0 ) { baos.write(buffer, 0, i); }
250 		is.close();
251 
252 		bytecode = baos.toByteArray();
253 		if ( bytecode == null ) { throw new ClassNotFoundException("Cannot load class: " + name); }
254 
255 		try
256 		{
257 			cls = this.defineClass(name, bytecode, 0, bytecode.length);
258 			if ( resolve ) { this.resolveClass(cls); }
259 		}
260 		catch ( SecurityException e ) { cls = parent.loadClass(name); }
261 
262 		return cls;
263 	}
264 
265 	private String addDefHelper(Attributes manifest, Attributes file, me key)
266 	{
267 		if ( (file != null) && !file.isEmpty() )
268 		{ if ( file.containsKey(key.getPrimary()) ) { return file.getValue(key.getPrimary()); } }
269 
270 		if ( (manifest != null) && !manifest.isEmpty() )
271 		{
272 			if ( manifest.containsKey(key.getPrimary()) ) { return manifest.getValue(key.getPrimary()); }
273 			else { return key.getDefault(); }
274 		}
275 
276 		return "null";
277 	}
278 
279 	private void defPkg(String pack, String file, Attributes master, Attributes pkg)
280 			throws MalformedURLException
281 	{
282 		Package p;
283 		if ( !pkgs.containsKey(pack) )
284 		{
285 			p = definePackage(pack,
286 				addDefHelper(master, pkg, me.SPECTITLE), addDefHelper(master, pkg, me.SPECVERSION),
287 				addDefHelper(master, pkg, me.SPECVENDOR),
288 				addDefHelper(master, pkg, me.IMPLTITLE), addDefHelper(master, pkg, me.IMPLVERSION),
289 				addDefHelper(master, pkg, me.IMPLVENDOR),
290 				new URL("file:".concat(file))
291 			);
292 			pkgs.put(pack, p);
293 			maps.put(pack, file);
294 		}
295 	}
296 
297 	public void addDir(File dir)
298 	{
299 		String pt = null;
300 		try { pt = dir.getCanonicalPath(); }
301 		catch (IOException ex) { Logger.getLogger(FireCodeClassLoader.class.getName()).log(Level.SEVERE, null, ex); }
302 		if ( pt != null )
303 		{
304 			if ( !flds.containsKey(pt) )
305 			{
306 				flds.put(pt, new File(pt));
307 
308 				// easy part done, now scan and add packages
309 				StringBuilder sb = new StringBuilder(pt);
310 				sb.append(File.separator);
311 				sb.append("META-INF");
312 				sb.append(File.separator);
313 				sb.append("MANIFEST.MF");
314 				File f = new File(sb.toString());
315 				try
316 				{
317 					Manifest mf;
318 					if ( f.exists() )
319 					{
320 						FileInputStream fis = new FileInputStream(f);
321 						mf = new Manifest(fis);
322 					}
323 					else { mf = new Manifest(); }
324 					if ( mf.getMainAttributes().containsKey(Name.CLASS_PATH) )
325 					{ addClassPath(mf.getMainAttributes().getValue(Name.CLASS_PATH)); }
326 					addDirHelper(dir, null, "", mf);
327 					System.out.println("Added Dir ".concat(pt));
328 				}
329 				catch (IOException x) {}
330 			}
331 		}
332 	}
333 
334 	private void addDirHelper(File root, File dir, String name, Manifest mf) throws IOException
335 	{
336 		File[] files;
337 		if ( dir != null ) { files = dir.listFiles(); } else { files = root.listFiles(); }
338 		String n;
339 		for ( File f : files )
340 		{
341 			if ( f.isDirectory() )
342 			{
343 				if ( name.isEmpty() ) { addDirHelper(root, f, f.getName(), mf); }
344 				else
345 				{
346 					n = name.concat(".").concat(f.getName());
347 					if ( n.startsWith(".") ) { n = n.substring(1); }
348 					if ( !flds.containsKey(n) )
349 					{ defPkg(n, root.getCanonicalPath(), mf.getMainAttributes(), mf.getAttributes(n)); }
350 					addDirHelper(root, f, n, mf);
351 				}
352 			}
353 			else if ( f.isFile() && f.getName().endsWith(".jar") ) { addJar(f); }
354 		}
355 	}
356 
357 	public void addJar(File jar)
358 	{
359 		String pt = null;
360 		try { pt = jar.getCanonicalPath(); }
361 		catch (IOException ex) { Logger.getLogger(FireCodeClassLoader.class.getName()).log(Level.SEVERE, null, ex); }
362 		if ( pt != null )
363 		{
364 			if ( !jars.containsKey(pt)  )
365 			{
366 				jars.put(pt, new File(pt));
367 				String tp;
368 
369 				// easy part done, now scan and add packages
370 				try
371 				{
372 					JarFile jf = new JarFile(jars.get(pt));
373 					Enumeration<JarEntry> en = jf.entries();
374 					Manifest mf = jf.getManifest();
375 					Attributes msf = mf.getMainAttributes();
376 
377 					JarEntry je;
378 
379 					if ( msf.containsKey(Name.CLASS_PATH) ) { addClassPath(msf.getValue(Name.CLASS_PATH)); }
380 					while ( en.hasMoreElements() )
381 					{
382 						je = en.nextElement();
383 						tp = je.getName();
384 						if ( tp.endsWith("/") ) { tp = tp.substring(0, tp.lastIndexOf("/")); }
385 						if ( je.isDirectory() && !tp.startsWith("META-INF") && tp.contains("/") )
386 						{
387 							tp = je.getName().replace('/', '.');
388 							tp = tp.substring(0, tp.lastIndexOf("."));
389 							defPkg(tp, jf.getName(), msf, je.getAttributes());
390 						}
391 					}
392 					System.out.println("Added Jar ".concat(pt));
393 				}
394 				catch ( IOException x ) {}
395 			}
396 		}
397 	}
398 
399 	private Iterator<String> cpDirs(String cp)
400 	{
401 		String[] pt;
402 		ArrayList<String> al = new ArrayList<>();
403 		if ( cp.contains(File.pathSeparator) || cp.contains(" ") )
404 		{ pt = cp.split("[ ".concat(File.pathSeparator).concat("]")); }
405 		else { pt = new String[]{cp}; }
406 
407 		al.addAll(Arrays.asList(pt));
408 
409 		return al.iterator();
410 	}
411 
412 	private void addClassPath(String list) { addClassPath(cpDirs(list)); }
413 	private void addClassPath(Iterator<String> it)
414 	{
415 		String st;
416 		File finale;
417 		String[] lists = new String[]
418 		{
419 			System.getProperty("user.home").concat("/.m2/repository/"),
420 			"./libs/",
421 			"./"
422 		};
423 		File f;
424 		File t;
425 		while ( it.hasNext() )
426 		{
427 			finale = null;
428 			st = it.next();
429 			f = new File(st);
430 			if ( f.exists() ) { finale = f; }
431 			for ( String s : lists )
432 			{
433 				if ( finale == null )
434 				{
435 					t = new File(s.concat(f.getPath()));
436 					if ( t.exists() ) { finale = t; }
437 				}
438 			}
439 			if ( finale != null )
440 			{
441 				if ( finale.isDirectory() ) { addDir(finale); }
442 				else if ( finale.getName().endsWith(".jar") ) { addJar(finale); }
443 			}
444 		}
445 	}
446 
447 	private enum me
448 	{
449 		NAME(new Name("Package"), "App"),
450 		SPECTITLE(Name.SPECIFICATION_TITLE, "Default"),
451 		SPECVERSION(Name.SPECIFICATION_VERSION, "1.0"),
452 		SPECVENDOR(Name.SPECIFICATION_VENDOR, "Developer"),
453 		IMPLTITLE(Name.IMPLEMENTATION_TITLE, "Default"),
454 		IMPLVERSION(Name.IMPLEMENTATION_VERSION, "1.0"),
455 		IMPLVENDOR(Name.IMPLEMENTATION_VENDOR, "Developer"),
456 		SEALED(Name.SEALED, "false");
457 
458 		private final Name p;
459 		private final String d;
460 		me(Name pri, String def)
461 		{
462 			p = pri;
463 			d = def;
464 		}
465 		public Name getPrimary() { return p; }
466 		public String getDefault() { return d; }
467 	}
468 
469 	public class Enum2Iterator<E> implements Iterator<E>
470 	{
471 		Enumeration<E> en;
472 		public Enum2Iterator(Enumeration<E> enumer) { en = enumer; }
473 
474 		@Override public void forEachRemaining(Consumer<? super E> action)
475 		{
476 			Objects.requireNonNull(action);
477 			while ( en.hasMoreElements() ) { action.accept(en.nextElement()); }
478 		}
479 
480 		@Override public boolean hasNext() { return en.hasMoreElements(); }
481 		@Override public E next() { return en.nextElement(); }
482 	}
483 
484 	public class Iterator2Enum<E> implements Enumeration<E>
485 	{
486 		Iterator<E> it;
487 		public Iterator2Enum(Iterator<E> iter) { it = iter; }
488 
489 		@Override public boolean hasMoreElements() { return it.hasNext(); }
490 		@Override public E nextElement() { return it.next(); }
491 	}
492 }