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}