001package org.maltparser.core.plugin; 002 003import java.io.BufferedInputStream; 004import java.io.ByteArrayOutputStream; 005import java.io.File; 006import java.io.IOException; 007import java.lang.reflect.InvocationTargetException; 008import java.lang.reflect.Method; 009import java.net.MalformedURLException; 010import java.net.URL; 011import java.net.URLClassLoader; 012import java.security.SecureClassLoader; 013import java.util.HashMap; 014import java.util.Set; 015import java.util.TreeSet; 016import java.util.jar.Attributes; 017import java.util.jar.JarEntry; 018import java.util.jar.JarFile; 019import java.util.jar.JarInputStream; 020import java.util.jar.Manifest; 021import java.util.regex.PatternSyntaxException; 022 023import org.maltparser.core.exception.MaltChainedException; 024import org.maltparser.core.helper.HashSet; 025import org.maltparser.core.options.OptionManager; 026 027 028/** 029The jar class loader loads the content of a jar file that complies with a MaltParser Plugin. 030 031@author Johan Hall 032 */ 033public class JarLoader extends SecureClassLoader { 034 private HashMap<String, byte[]> classByteArrays; 035 private HashMap<String, Class<?>> classes; 036 037 /** 038 * Creates a new class loader that is specialized for loading jar files. 039 * 040 * @param parent The parent class loader 041 */ 042 public JarLoader(ClassLoader parent) { 043 super(parent); 044 classByteArrays = new HashMap<String, byte[]>(); 045 classes = new HashMap<String, Class<?>>(); 046 } 047 048 /* (non-Javadoc) 049 * @see java.lang.ClassLoader#findClass(java.lang.String) 050 */ 051 protected Class<?> findClass(String name) { 052 String urlName = name.replace('.', '/'); 053 byte buf[]; 054 055 SecurityManager sm = System.getSecurityManager(); 056 if (sm != null) { 057 int i = name.lastIndexOf('.'); 058 if (i >= 0) { 059 sm.checkPackageDefinition(name.substring(0, i)); 060 } 061 } 062 063 buf = (byte[]) classByteArrays.get(urlName); 064 if (buf != null) { 065 return defineClass(null, buf, 0, buf.length); 066 } 067 return null; 068 } 069 070 /** 071 * Loads the content of a jar file that comply with a MaltParser Plugin 072 * 073 * @param jarUrl The URL to the jar file 074 * @throws PluginException 075 */ 076 public boolean readJarFile(URL jarUrl) throws MaltChainedException { 077 JarInputStream jis; 078 JarEntry je; 079 Set<URL> pluginXMLs = new HashSet<URL>(); 080 081 /*if (logger.isDebugEnabled()) { 082 logger.debug("Loading jar " + jarUrl+"\n"); 083 }*/ 084 JarFile jarFile; 085 try { 086 jarFile = new JarFile(jarUrl.getFile()); 087 } catch (IOException e) { 088 throw new PluginException("Could not open jar file " + jarUrl+". ", e); 089 } 090 try { 091 Manifest manifest = jarFile.getManifest(); 092 if (manifest != null) { 093 Attributes manifestAttributes = manifest.getMainAttributes(); 094 if (!(manifestAttributes.getValue("MaltParser-Plugin") != null && manifestAttributes.getValue("MaltParser-Plugin").equals("true"))) { 095 return false; 096 } 097 if (manifestAttributes.getValue("Class-Path") != null) { 098 String[] classPathItems = manifestAttributes.getValue("Class-Path").split(" "); 099 for (int i=0; i < classPathItems.length; i++) { 100 URL u; 101 try { 102 u = new URL(jarUrl.getProtocol()+":"+new File(jarFile.getName()).getParentFile().getPath()+"/"+classPathItems[i]); 103 } catch (MalformedURLException e) { 104 throw new PluginException("The URL to the plugin jar-class-path '"+jarUrl.getProtocol()+":"+new File(jarFile.getName()).getParentFile().getPath()+"/"+classPathItems[i]+"' is wrong. ", e); 105 } 106 URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader(); 107 Class<?> sysclass = URLClassLoader.class; 108 Method method = sysclass.getDeclaredMethod("addURL",new Class[]{URL.class}); 109 method.setAccessible(true); 110 method.invoke(sysloader,new Object[]{u }); 111 } 112 } 113 } 114 } catch (PatternSyntaxException e) { 115 throw new PluginException("Could not split jar-class-path entries in the jar-file '"+jarFile.getName()+"'. ", e); 116 } catch (IOException e) { 117 throw new PluginException("Could not read the manifest file in the jar-file '"+jarFile.getName()+"'. ", e); 118 } catch (NoSuchMethodException e) { 119 throw new PluginException("", e); 120 } catch (IllegalAccessException e) { 121 throw new PluginException("", e); 122 } catch (InvocationTargetException e) { 123 throw new PluginException("", e); 124 } 125 126 try { 127 jis = new JarInputStream(jarUrl.openConnection().getInputStream()); 128 129 while ((je = jis.getNextJarEntry()) != null) { 130 String jarName = je.getName(); 131 if (jarName.endsWith(".class")) { 132 /* if (logger.isDebugEnabled()) { 133 logger.debug(" Loading class: " + jarName+"\n"); 134 }*/ 135 loadClassBytes(jis, jarName); 136 Class<?> clazz = findClass(jarName.substring(0, jarName.length() - 6)); 137 classes.put(jarName.substring(0, jarName.length() - 6).replace('/','.'), clazz); 138 loadClass(jarName.substring(0, jarName.length() - 6).replace('/', '.')); 139 } 140 if (jarName.endsWith("plugin.xml")) { 141 pluginXMLs.add(new URL("jar:"+jarUrl.getProtocol()+":"+jarUrl.getPath()+"!/"+jarName)); 142 } 143 jis.closeEntry(); 144 } 145 for (URL url : pluginXMLs) { 146 /* if (logger.isDebugEnabled()) { 147 logger.debug(" Loading "+url+"\n"); 148 }*/ 149 OptionManager.instance().loadOptionDescriptionFile(url); 150 } 151 } catch (MalformedURLException e) { 152 throw new PluginException("The URL to the plugin.xml is wrong. ", e); 153 } catch (IOException e) { 154 throw new PluginException("cannot open jar file " + jarUrl+". ", e); 155 } catch (ClassNotFoundException e) { 156 throw new PluginException("The class "+e.getMessage() +" can't be found. ", e); 157 } 158 return true; 159 } 160 161 /** 162 * Returns the Class object for the class with the specified name. 163 * 164 * @param classname the fully qualified name of the desired class 165 * @return the Class object for the class with the specified name. 166 */ 167 public Class<?> getClass(String classname) { 168 return (Class<?>)classes.get(classname); 169 } 170 171 /** 172 * Reads a jar file entry into a byte array. 173 * 174 * @param jis The jar input stream 175 * @param jarName The name of a jar file entry 176 * @throws PluginException 177 */ 178 private void loadClassBytes(JarInputStream jis, String jarName) throws MaltChainedException { 179 BufferedInputStream jarBuf = new BufferedInputStream(jis); 180 ByteArrayOutputStream jarOut = new ByteArrayOutputStream(); 181 int b; 182 try { 183 while ((b = jarBuf.read()) != -1) { 184 jarOut.write(b); 185 } 186 classByteArrays.put(jarName.substring(0, jarName.length() - 6), jarOut.toByteArray()); 187 } catch (IOException e) { 188 throw new PluginException("Error reading entry " + jarName+". ", e); 189 } 190 } 191 192 /** 193 * Checks package access 194 * 195 * @param name the package name 196 */ 197 protected void checkPackageAccess(String name) { 198 SecurityManager sm = System.getSecurityManager(); 199 if (sm != null) { 200 sm.checkPackageAccess(name); 201 } 202 } 203 204 /* (non-Javadoc) 205 * @see java.lang.Object#toString() 206 */ 207 public String toString() { 208 StringBuilder sb = new StringBuilder(); 209 210 sb.append("The MaltParser Plugin Loader (JarLoader)\n"); 211 sb.append("---------------------------------------------------------------------\n"); 212 for (String entry : new TreeSet<String>(classes.keySet())) { 213 sb.append(" "+entry+"\n"); 214 } 215 return sb.toString(); 216 } 217}