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
020package org.crsh.util;
021
022import java.io.ByteArrayInputStream;
023import java.io.ByteArrayOutputStream;
024import java.io.Closeable;
025import java.io.File;
026import java.io.IOException;
027import java.io.InputStream;
028import java.net.URISyntaxException;
029import java.net.URL;
030import java.util.Enumeration;
031import java.util.NoSuchElementException;
032import java.util.zip.ZipEntry;
033import java.util.zip.ZipFile;
034import java.util.zip.ZipInputStream;
035
036/** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
037public 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}