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}