001    /*
002     * Copyright (C) 2012 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    
020    package org.crsh.vfs.spi.url;
021    
022    import org.crsh.util.Safe;
023    
024    import java.io.ByteArrayInputStream;
025    import java.io.ByteArrayOutputStream;
026    import java.io.Closeable;
027    import java.io.File;
028    import java.io.IOException;
029    import java.io.InputStream;
030    import java.net.URISyntaxException;
031    import java.net.URL;
032    import java.util.Enumeration;
033    import java.util.NoSuchElementException;
034    import java.util.zip.ZipEntry;
035    import java.util.zip.ZipFile;
036    import java.util.zip.ZipInputStream;
037    
038    /** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
039    abstract class ZipIterator implements Closeable {
040    
041      static ZipIterator create(URL url) throws IOException, URISyntaxException {
042        if (url.getProtocol().equals("file")) {
043          return create(new java.io.File(url.toURI()));
044        } else if (url.getProtocol().equals("jar")) {
045          int pos = url.getPath().lastIndexOf("!/");
046          URL jarURL = new URL(url.getPath().substring(0, pos));
047          String path = url.getPath().substring(pos + 2);
048          final ZipIterator container = create(jarURL);
049          ZipIterator zip = null;
050          try {
051            while (container.hasNext()) {
052              ZipEntry entry = container.next();
053              if (entry.getName().equals(path)) {
054                InputStreamResolver resolved = container.open();
055                final InputStream nested = resolved.open();
056                InputStream filter = new InputStream() {
057                  @Override
058                  public int read() throws IOException {
059                    return nested.read();
060                  }
061                  @Override
062                  public int read(byte[] b) throws IOException {
063                    return nested.read(b);
064                  }
065                  @Override
066                  public int read(byte[] b, int off, int len) throws IOException {
067                    return nested.read(b, off, len);
068                  }
069                  @Override
070                  public long skip(long n) throws IOException {
071                    return nested.skip(n);
072                  }
073                  @Override
074                  public int available() throws IOException {
075                    return nested.available();
076                  }
077                  @Override
078                  public void close() throws IOException {
079                    Safe.close(nested);
080                    Safe.close(container);
081                  }
082                  @Override
083                  public synchronized void mark(int readlimit) {
084                    nested.mark(readlimit);
085                  }
086                  @Override
087                  public synchronized void reset() throws IOException {
088                    nested.reset();
089                  }
090                  @Override
091                  public boolean markSupported() {
092                    return nested.markSupported();
093                  }
094                };
095                zip = create(filter);
096                break;
097              }
098            }
099            if (zip != null) {
100              return zip;
101            } else {
102              throw new IOException("Cannot resolve " + url);
103            }
104          }
105          finally {
106            // We close the container if we return nothing
107            // otherwise it will be the responsibility of the caller to close the zip
108            // with the wrapper that will close both the container and the nested zip
109            if (zip != null) {
110              Safe.close(container);
111            }
112          }
113        } else {
114          return create(url.openStream());
115        }
116      }
117    
118      static ZipIterator create(File file) throws IOException {
119        // The fast way (but that requires a File object)
120        final ZipFile jarFile = new ZipFile(file);
121        final Enumeration<? extends ZipEntry> en = jarFile.entries();en.hasMoreElements();
122        return new ZipIterator() {
123          ZipEntry next;
124          @Override
125          boolean hasNext() throws IOException {
126            return en.hasMoreElements();
127          }
128          @Override
129          ZipEntry next() throws IOException {
130            return next = en.nextElement();
131          }
132          public void close() throws IOException {
133          }
134          @Override
135          InputStreamResolver open() throws IOException {
136            final ZipEntry capture = next;
137            return new InputStreamResolver() {
138              public InputStream open() throws IOException {
139                return jarFile.getInputStream(capture);
140              }
141            };
142          }
143        };
144      }
145    
146      static ZipIterator create(InputStream in) throws IOException {
147        final byte[] tmp = new byte[512];
148        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
149        final ZipInputStream zip = new ZipInputStream(in);
150        return new ZipIterator() {
151          ZipEntry next;
152          boolean hasNext() throws IOException {
153            if (next == null) {
154              next = zip.getNextEntry();
155            }
156            return next != null;
157          }
158          ZipEntry next() throws IOException {
159            if (!hasNext()) {
160              throw new NoSuchElementException();
161            }
162            ZipEntry tmp = next;
163            next = null;
164            return tmp;
165          }
166          @Override
167          InputStreamResolver open() throws IOException {
168            while (true) {
169              int len = zip.read(tmp, 0, tmp.length);
170              if (len == -1) {
171                break;
172              } else {
173                baos.write(tmp, 0, len);
174              }
175            }
176            final byte[] buffer = baos.toByteArray();
177            baos.reset();
178            return new InputStreamResolver() {
179              public InputStream open() throws IOException {
180                return new ByteArrayInputStream(buffer);
181              }
182            };
183          }
184          public void close() throws IOException {
185            zip.close();
186          }
187        };
188      }
189    
190      abstract boolean hasNext() throws IOException;
191    
192      abstract ZipEntry next() throws IOException;
193    
194      abstract InputStreamResolver open() throws IOException;
195    
196    }