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