001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019
020package icy.plugin.classloader;
021
022import icy.network.NetworkUtil;
023import icy.network.URLUtil;
024import icy.plugin.classloader.exception.JclException;
025import icy.system.IcyExceptionHandler;
026
027import java.io.BufferedInputStream;
028import java.io.File;
029import java.io.IOException;
030import java.io.InputStream;
031import java.net.JarURLConnection;
032import java.net.URISyntaxException;
033import java.net.URL;
034import java.util.Collections;
035import java.util.Enumeration;
036import java.util.HashMap;
037import java.util.Map;
038import java.util.Set;
039import java.util.jar.JarEntry;
040import java.util.logging.Level;
041import java.util.logging.Logger;
042import java.util.zip.ZipEntry;
043import java.util.zip.ZipFile;
044import java.util.zip.ZipInputStream;
045
046/**
047 * JarResources reads jar files and loads the class content/bytes in a HashMap
048 * 
049 * @author Kamran Zafar
050 * @author Stephane Dallongeville
051 */
052public class JarResources
053{
054    // <resourceName, content> map
055    protected Map<String, byte[]> entryContents;
056    // <resourceName, fileName> map
057    protected Map<String, URL> entryUrls;
058
059    protected boolean collisionAllowed;
060    // keep trace of loaded resource size
061    protected int loadedSize;
062
063    private static Logger logger = Logger.getLogger(JarResources.class.getName());
064
065    /**
066     * Default constructor
067     */
068    public JarResources()
069    {
070        entryContents = new HashMap<String, byte[]>();
071        entryUrls = new HashMap<String, URL>();
072        collisionAllowed = Configuration.suppressCollisionException();
073        loadedSize = 0;
074    }
075
076    public URL getResource(String name)
077    {
078        return entryUrls.get(name);
079    }
080
081    public byte[] getResourceContent(String name) throws IOException
082    {
083        byte content[] = entryContents.get(name);
084
085        // we load the content
086        if (content == null)
087        {
088            final URL url = entryUrls.get(name);
089
090            if (url != null)
091            {
092                // load content and return it
093                loadContent(name, url);
094                content = entryContents.get(name);
095
096                // try
097                // {
098                // // load content and return it
099                // loadContent(name, url);
100                // content = entryContents.get(name);
101                // }
102                // catch (IOException e)
103                // {
104                // // content cannot be loaded, remove the URL entry
105                // // better to not remove it after all, so we know why it failed next time
106                // // entryUrls.remove(name);
107                //
108                // // and throw exception
109                // throw e;
110                // }
111            }
112        }
113
114        return content;
115    }
116
117    protected void loadContent(String name, URL url) throws IOException
118    {
119        // only support JAR resource here
120        final byte[] content = loadJarContent(url);
121        setResourceContent(name, content);
122    }
123
124    /**
125     * Returns an immutable Set of all resources names
126     */
127    public Set<String> getResourcesName()
128    {
129        return Collections.unmodifiableSet(entryUrls.keySet());
130    }
131
132    /**
133     * Returns an immutable Map of all resources
134     */
135    public Map<String, URL> getResources()
136    {
137        return Collections.unmodifiableMap(entryUrls);
138    }
139
140    /**
141     * Returns an immutable Map of all loaded jar resources
142     */
143    public Map<String, byte[]> getLoadedResources()
144    {
145        return Collections.unmodifiableMap(entryContents);
146    }
147
148    /**
149     * Loads the jar file from a specified File.<br>
150     * This method actually stores all contained URL in the specified JAR file.
151     * 
152     * @throws IOException
153     */
154    public void loadJar(File file) throws IOException
155    {
156        final String filePath = file.getAbsolutePath();
157        final String urlPrefix = "jar:" + file.toURI().toString() + "!/";
158
159        if (logger.isLoggable(Level.FINEST))
160            logger.finest("Loading jar: " + filePath);
161
162        // we don't care about JAR specific information so just use ZipFile here
163        final ZipFile zipFile = new ZipFile(file);
164
165        try
166        {
167            final Enumeration<? extends ZipEntry> entries = zipFile.entries();
168
169            while (entries.hasMoreElements())
170            {
171                final ZipEntry entry = entries.nextElement();
172
173                // ignore folder entries
174                if (entry.isDirectory())
175                    continue;
176
177                final String name = entry.getName();
178
179                // ignore manifest file
180                if (name.equals("META-INF/MANIFEST.MF"))
181                    continue;
182
183                if (entryUrls.containsKey(name))
184                {
185                    if (!collisionAllowed)
186                        throw new JclException("Class/Resource " + name + " already loaded");
187
188                    if (logger.isLoggable(Level.FINEST))
189                        logger.finest("Class/Resource " + name + " already loaded; ignoring entry...");
190                    continue;
191                }
192
193                // add to internal resource HashMap
194                entryUrls.put(name, new URL(urlPrefix + name));
195            }
196        }
197        finally
198        {
199            try
200            {
201                zipFile.close();
202            }
203            catch (IOException e)
204            {
205                // not important
206                System.err.println("JarResources.loadJar(" + filePath + ") error:");
207                IcyExceptionHandler.showErrorMessage(e, false, true);
208            }
209        }
210
211    }
212
213    /**
214     * Loads the jar file from a specified URL.<br>
215     * This method actually stores all contained URL in the specified JAR archive.
216     * 
217     * @throws IOException
218     */
219    public void loadJar(URL url) throws IOException
220    {
221        // use file loading if possible
222        if (URLUtil.isFileURL(url))
223        {
224            File f;
225
226            try
227            {
228                f = new File(url.toURI());
229            }
230            catch (URISyntaxException e)
231            {
232                f = new File(url.getPath());
233            }
234
235            loadJar(f);
236            return;
237        }
238
239        if (logger.isLoggable(Level.FINEST))
240            logger.finest("Loading jar: " + url.toString());
241
242        final String urlPrefix = "jar:" + url.toString() + "!/";
243        BufferedInputStream bis = null;
244        // we don't care about JAR specific information so just use ZipInputStream here
245        ZipInputStream zis = null;
246
247        try
248        {
249            final InputStream in = url.openStream();
250
251            if (in instanceof BufferedInputStream)
252                bis = (BufferedInputStream) in;
253            else
254                bis = new BufferedInputStream(in);
255
256            zis = new ZipInputStream(bis);
257
258            ZipEntry entry = null;
259            while ((entry = zis.getNextEntry()) != null)
260            {
261                if (logger.isLoggable(Level.FINEST))
262                    logger.finest(dump(entry));
263
264                if (entry.isDirectory())
265                    continue;
266
267                final String name = entry.getName();
268
269                // ignore manifest file
270                if (name.equals("META-INF/MANIFEST.MF"))
271                    continue;
272
273                if (entryUrls.containsKey(name))
274                {
275                    if (!collisionAllowed)
276                        throw new JclException("Class/Resource " + name + " already loaded");
277
278                    if (logger.isLoggable(Level.FINEST))
279                        logger.finest("Class/Resource " + name + " already loaded; ignoring entry...");
280                    continue;
281                }
282
283                // add to internal resource HashMap
284                entryUrls.put(name, new URL(urlPrefix + name));
285            }
286        }
287        // catch (NullPointerException e)
288        // {
289        // if (logger.isLoggable(Level.FINEST))
290        // logger.finest("Done loading.");
291        // }
292        finally
293        {
294            if (zis != null)
295            {
296                try
297                {
298                    zis.close();
299                }
300                catch (IOException e)
301                {
302                    // not important
303                    System.err.println("JarResources.loadJar(" + url + ") error:");
304                    IcyExceptionHandler.showErrorMessage(e, false, true);
305                }
306            }
307
308            if (bis != null)
309            {
310                try
311                {
312                    bis.close();
313                }
314                catch (IOException e)
315                {
316                    // not important
317                    System.err.println("JarResources.loadJar(" + url + ") error:");
318                    IcyExceptionHandler.showErrorMessage(e, false, true);
319                }
320            }
321        }
322    }
323
324    /**
325     * Load the jar contents from InputStream
326     * 
327     * @throws IOException
328     */
329    protected byte[] loadJarContent(URL url) throws IOException
330    {
331        final JarURLConnection uc = (JarURLConnection) url.openConnection();
332        final JarEntry jarEntry = uc.getJarEntry();
333
334        if (jarEntry != null)
335        {
336            if (logger.isLoggable(Level.FINEST))
337                logger.finest(dump(jarEntry));
338
339            return NetworkUtil.download(uc.getInputStream(), jarEntry.getSize(), null);
340        }
341
342        throw new IOException("JarResources.loadJarContent(" + url.toString() + ") error:\nEntry not found !");
343    }
344
345    protected void setResourceContent(String name, byte content[])
346    {
347        if (entryContents.containsKey(name))
348        {
349            if (!collisionAllowed)
350                throw new JclException("Class/Resource " + name + " already loaded");
351
352            if (logger.isLoggable(Level.FINEST))
353                logger.finest("Class/Resource " + name + " already loaded; ignoring entry...");
354            return;
355        }
356
357        if (logger.isLoggable(Level.FINEST))
358            logger.finest("Entry Name: " + name + ", " + "Entry Size: " + content.length);
359
360        // add to internal resource HashMap
361        entryContents.put(name, content);
362    }
363
364    /**
365     * For debugging
366     * 
367     * @param je
368     * @return String
369     */
370    private String dump(ZipEntry ze)
371    {
372        StringBuffer sb = new StringBuffer();
373        if (ze.isDirectory())
374            sb.append("d ");
375        else
376            sb.append("f ");
377
378        if (ze.getMethod() == ZipEntry.STORED)
379            sb.append("stored   ");
380        else
381            sb.append("deflated ");
382
383        sb.append(ze.getName());
384        sb.append("\t");
385        sb.append("" + ze.getSize());
386        if (ze.getMethod() == ZipEntry.DEFLATED)
387            sb.append("/" + ze.getCompressedSize());
388
389        return (sb.toString());
390    }
391}