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    }