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 }