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