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.Set;
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.helper.HashSet;
025    import org.maltparser.core.options.OptionManager;
026    
027    
028    /**
029    The jar class loader loads the content of a jar file that complies with a MaltParser Plugin.
030    
031    @author Johan Hall
032     */
033    public 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    }