001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.xbean.finder.archive; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.net.URL; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.Comparator; 025import java.util.Enumeration; 026import java.util.Iterator; 027import java.util.List; 028import java.util.NoSuchElementException; 029import java.util.jar.JarEntry; 030import java.util.jar.JarFile; 031import java.util.jar.Manifest; 032import java.util.zip.ZipEntry; 033 034/** 035 * @version $Rev$ $Date$ 036 */ 037public class JarArchive implements Archive { 038 039 private final ClassLoader loader; 040 private final URL url; 041 private final JarFile jar; 042 private final MJarSupport mjar = new MJarSupport(); 043 044 public JarArchive(ClassLoader loader, URL url) { 045// if (!"jar".equals(url.getProtocol())) throw new IllegalArgumentException("not a jar url: " + url); 046 047 try { 048 this.loader = loader; 049 this.url = url; 050 URL u = url; 051 052 String jarPath = url.getFile(); 053 if (jarPath.contains("!")) { 054 jarPath = jarPath.substring(0, jarPath.indexOf("!")); 055 u = new URL(jarPath); 056 } 057 jar = new JarFile(FileArchive.decode(u.getFile())); // no more an url 058 } catch (IOException e) { 059 throw new IllegalStateException(e); 060 } 061 } 062 063 public URL getUrl() { 064 return url; 065 } 066 067 public InputStream getBytecode(String className) throws IOException, ClassNotFoundException { 068 int pos = className.indexOf("<"); 069 if (pos > -1) { 070 className = className.substring(0, pos); 071 } 072 pos = className.indexOf(">"); 073 if (pos > -1) { 074 className = className.substring(0, pos); 075 } 076 if (!className.endsWith(".class")) { 077 className = className.replace('.', '/') + ".class"; 078 } 079 080 ZipEntry entry = jar.getEntry(className); 081 if (entry == null) throw new ClassNotFoundException(className); 082 083 return jar.getInputStream(entry); 084 } 085 086 087 public Class<?> loadClass(String className) throws ClassNotFoundException { 088 // assume the loader knows how to handle mjar release if activated 089 return loader.loadClass(className); 090 } 091 092 public Iterator<Entry> iterator() { 093 return new JarIterator(); 094 } 095 096 private class JarIterator implements Iterator<Entry> { 097 098 private final Iterator<JarEntry> stream; 099 private Entry next; 100 101 private JarIterator() { 102 final Enumeration<JarEntry> entries = jar.entries(); 103 try { 104 final Manifest manifest = jar.getManifest(); 105 if (manifest != null) { 106 mjar.load(manifest); 107 } 108 } catch (IOException e) { 109 // no-op 110 } 111 if (mjar.isMjar()) { // sort it to ensure we browse META-INF/versions first 112 final List<JarEntry> list = new ArrayList<JarEntry>(Collections.list(entries)); 113 Collections.sort(list, new Comparator<JarEntry>() { 114 public int compare(JarEntry o1, JarEntry o2) { 115 final String n2 = o2.getName(); 116 final String n1 = o1.getName(); 117 final boolean n1v = n1.startsWith("META-INF/versions/"); 118 final boolean n2v = n2.startsWith("META-INF/versions/"); 119 if (n1v && n2v) { 120 return n1.compareTo(n2); 121 } 122 if (n1v) { 123 return -1; 124 } 125 if (n2v) { 126 return 1; 127 } 128 try { 129 return Integer.parseInt(n2) - Integer.parseInt(n1); 130 } catch (final NumberFormatException nfe) { 131 return n2.compareTo(n1); 132 } 133 } 134 }); 135 stream = list.iterator(); 136 } else { 137 stream = Collections.list(entries).iterator(); 138 } 139 } 140 141 private boolean advance() { 142 if (next != null) { 143 return true; 144 } 145 while (stream.hasNext()) { 146 final JarEntry entry = stream.next(); 147 final String entryName = entry.getName(); 148 if (entry.isDirectory() || !entryName.endsWith(".class") || entryName.endsWith("module-info.class")) { 149 continue; 150 } 151 152 String className = entryName; 153 if (entryName.endsWith(".class")) { 154 className = className.substring(0, className.length() - 6); 155 } 156 if (className.contains(".")) { 157 continue; 158 } 159 160 if (entryName.startsWith("META-INF/versions/")) { // JarFile will handle it for us 161 continue; 162 } 163 164 next = new ClassEntry(entry, className.replace('/', '.')); 165 return true; 166 } 167 return false; 168 } 169 170 public boolean hasNext() { 171 return advance(); 172 } 173 174 public Entry next() { 175 if (!hasNext()) throw new NoSuchElementException(); 176 Entry entry = next; 177 next = null; 178 return entry; 179 } 180 181 public void remove() { 182 throw new UnsupportedOperationException("remove"); 183 } 184 185 private class ClassEntry implements Entry { 186 private final String name; 187 private final JarEntry entry; 188 189 private ClassEntry(JarEntry entry, String name) { 190 this.name = name; 191 this.entry = entry; 192 } 193 194 public String getName() { 195 return name; 196 } 197 198 public InputStream getBytecode() throws IOException { 199 if (mjar.isMjar()) { 200 // JarFile handles it for us :) 201 final ZipEntry entry = jar.getJarEntry(this.entry.getName()); 202 if (entry != null) { 203 return jar.getInputStream(entry); 204 } 205 } 206 return jar.getInputStream(entry); 207 } 208 } 209 } 210} 211