/** * Copyright 2013 Oak Ridge National Laboratory * Author: James Horey <horeyjl@ornl.gov> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ package gov.ornl.keva.loader; /** * Java libs. */ import java.io.FileInputStream; import java.io.BufferedInputStream; import java.io.IOException; import java.io.FileNotFoundException; import java.util.Map; import java.util.HashMap; import java.util.Enumeration; import java.util.zip.ZipFile; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * This class is responsible for dynamically loading user-defined classes. * * @author James Horey */ public class JobLoader { /** * Load a new object instance from a jar file. * * @param className Name of the class to load * @param jarName Name of the jar file. * @return New object instance */ public static Object load(String className, String jarName, String[] args) { try { JarClassLoader loader; loader = new JarClassLoader(jarName); if(args == null || args.length == 0) { return loader.loadClass(className).newInstance(); } else { // Get the class. Class c = loader.loadClass(className); // Construct the argument types. Class<?>[] argTypes = new Class<?>[args.length]; for(int i = 0; i < args.length; ++i) { argTypes[i] = args[i].getClass(); } // Construct the new instance. return c.getDeclaredConstructor(argTypes).newInstance(args); } } catch(Exception e) { e.printStackTrace(); return null; } } /** * Load classes from jar files. */ static class JarClassLoader extends ClassLoader { private JarResources jarResources = null; /** * @param jarName Name of jar file */ public JarClassLoader(String jarName) { if(jarName != null) { jarResources = new JarResources(jarName); } } /** * Load a Class from the jar file. * * @param className Name of the class * @return New Class instance */ @Override public Class loadClass(String className) throws ClassNotFoundException { Class result = null; byte[] classBytes; // Try using the system classloader. try { result = ClassLoader.getSystemClassLoader().loadClass(className); } catch(ClassNotFoundException e) { } // Load the class data from the jar file. if(result == null && jarResources != null) { classBytes = jarResources.getResource(className); if (classBytes == null) { throw new ClassNotFoundException(); } // Convert the bytes into an actual Class object. result = defineClass(className, classBytes, 0, classBytes.length); if (result == null) { throw new ClassFormatError(); } } // Finally, "resolve" the class. This actually links // the class so that it can be used. resolveClass(result); return result; } } /** * Help extract class definitions from jar files. */ static class JarResources { private Map<String, Integer> jarSizes; private Map<String, byte[]> jarContents; private String jarFileName; /** * @param jarFileName a jar or zip file */ public JarResources(String jarFileName) { this.jarFileName = jarFileName; jarSizes = new HashMap<>(); jarContents = new HashMap<>(); init(); } /** * The zip file stores names separated by "/" characters. * Transform these names to use "." characters so that users * can refer to proper package names. Also, the zip entry * stores the file extension (".class"). We should remove those. **/ private String transformName(String className) { String withoutExt = className.substring(0, className.lastIndexOf('.')); return withoutExt.replace('/', '.'); } /** * Extracts a jar resource as a blob. * * @param name a resource name. */ public byte[] getResource(String name) { return jarContents.get(name); } /** * Initializes internal hash tables with Jar file resources. **/ private void init() { try { // Jar files are just zipped files. ZipFile zip = new ZipFile(jarFileName); // Iterate over the zip entries. for(Enumeration e = zip.entries(); e.hasMoreElements(); ) { ZipEntry entry = (ZipEntry)e.nextElement(); jarSizes.put(entry.getName(), (int)entry.getSize()); } zip.close(); // Now read in the actual class definitions. BufferedInputStream input = new BufferedInputStream(new FileInputStream(jarFileName)); ZipInputStream zipInput = new ZipInputStream(input); ZipEntry entry = null; while( (entry = zipInput.getNextEntry()) != null ) { if(entry.isDirectory()) { continue; } // Try to figure out how large of a buffer we need. int size = (int)entry.getSize(); if(size == -1) { size = jarSizes.get(entry.getName()); } int index = 0; int read = 0; byte[] data = new byte[size]; // Read in the actual byte buffer. while( (size - index) > 0 ) { read = zipInput.read(data, index, size - index); if(read == -1) { break; } index += read; } jarContents.put(transformName(entry.getName()), data); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } }