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.util;
021    
022    import java.io.ByteArrayInputStream;
023    import java.io.ByteArrayOutputStream;
024    import java.io.Closeable;
025    import java.io.File;
026    import java.io.IOException;
027    import java.io.InputStream;
028    import java.net.URISyntaxException;
029    import java.net.URL;
030    import java.util.Enumeration;
031    import java.util.NoSuchElementException;
032    import java.util.zip.ZipEntry;
033    import java.util.zip.ZipFile;
034    import java.util.zip.ZipInputStream;
035    
036    /** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
037    public abstract class ZipIterator implements Closeable {
038    
039      public static ZipIterator create(URL url) throws IOException, URISyntaxException {
040        if (url.getProtocol().equals("file")) {
041          return create(Utils.toFile(url));
042        } else if (url.getProtocol().equals("jar")) {
043          int pos = url.getPath().lastIndexOf("!/");
044          URL jarURL = new URL(url.getPath().substring(0, pos));
045          String path = url.getPath().substring(pos + 2);
046          final ZipIterator container = create(jarURL);
047          ZipIterator zip = null;
048          try {
049            while (container.hasNext()) {
050              ZipEntry entry = container.next();
051              if (entry.getName().equals(path)) {
052                InputStreamFactory resolved = container.getStreamFactory();
053                final InputStream nested = resolved.open();
054                InputStream filter = new InputStream() {
055                  @Override
056                  public int read() throws IOException {
057                    return nested.read();
058                  }
059                  @Override
060                  public int read(byte[] b) throws IOException {
061                    return nested.read(b);
062                  }
063                  @Override
064                  public int read(byte[] b, int off, int len) throws IOException {
065                    return nested.read(b, off, len);
066                  }
067                  @Override
068                  public long skip(long n) throws IOException {
069                    return nested.skip(n);
070                  }
071                  @Override
072                  public int available() throws IOException {
073                    return nested.available();
074                  }
075                  @Override
076                  public void close() throws IOException {
077                    Utils.close(nested);
078                    Utils.close(container);
079                  }
080                  @Override
081                  public synchronized void mark(int readlimit) {
082                    nested.mark(readlimit);
083                  }
084                  @Override
085                  public synchronized void reset() throws IOException {
086                    nested.reset();
087                  }
088                  @Override
089                  public boolean markSupported() {
090                    return nested.markSupported();
091                  }
092                };
093                zip = create(filter);
094                break;
095              }
096            }
097            if (zip != null) {
098              return zip;
099            } else {
100              throw new IOException("Cannot resolve " + url);
101            }
102          }
103          finally {
104            // We close the container if we return nothing
105            // otherwise it will be the responsibility of the caller to close the zip
106            // with the wrapper that will close both the container and the nested zip
107            if (zip != null) {
108              Utils.close(container);
109            }
110          }
111        } else {
112          return create(url.openStream());
113        }
114      }
115    
116      static ZipIterator create(File file) throws IOException {
117        // The fast way (but that requires a File object)
118        final ZipFile jarFile = new ZipFile(file);
119        final Enumeration<? extends ZipEntry> en = jarFile.entries();en.hasMoreElements();
120        return new ZipIterator() {
121          ZipEntry next;
122          @Override
123          public boolean hasNext() throws IOException {
124            return en.hasMoreElements();
125          }
126          @Override
127          public ZipEntry next() throws IOException {
128            return next = en.nextElement();
129          }
130          public void close() throws IOException {
131          }
132          @Override
133          public InputStreamFactory getStreamFactory() throws IOException {
134            final ZipEntry capture = next;
135            return new InputStreamFactory() {
136              public InputStream open() throws IOException {
137                return jarFile.getInputStream(capture);
138              }
139            };
140          }
141        };
142      }
143    
144      static ZipIterator create(InputStream in) throws IOException {
145        final byte[] tmp = new byte[512];
146        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
147        final ZipInputStream zip = new ZipInputStream(in);
148        return new ZipIterator() {
149          ZipEntry next;
150          public boolean hasNext() throws IOException {
151            if (next == null) {
152              next = zip.getNextEntry();
153            }
154            return next != null;
155          }
156          public ZipEntry next() throws IOException {
157            if (!hasNext()) {
158              throw new NoSuchElementException();
159            }
160            ZipEntry tmp = next;
161            next = null;
162            return tmp;
163          }
164          @Override
165          public InputStreamFactory getStreamFactory() throws IOException {
166            while (true) {
167              int len = zip.read(tmp, 0, tmp.length);
168              if (len == -1) {
169                break;
170              } else {
171                baos.write(tmp, 0, len);
172              }
173            }
174            final byte[] buffer = baos.toByteArray();
175            baos.reset();
176            return new InputStreamFactory() {
177              public InputStream open() throws IOException {
178                return new ByteArrayInputStream(buffer);
179              }
180            };
181          }
182          public void close() throws IOException {
183            zip.close();
184          }
185        };
186      }
187    
188      public abstract boolean hasNext() throws IOException;
189    
190      public abstract ZipEntry next() throws IOException;
191    
192      /**
193       * Return a stream factory for the current entry.
194       *
195       * @return the stream factory
196       * @throws IOException anything that would prevent to obtain a stream factory
197       */
198      public abstract InputStreamFactory getStreamFactory() throws IOException;
199    
200    }