View Javadoc

1   package org.codehaus.classworlds.uberjar.protocol.jar;
2   
3   /*
4    $Id: JarUrlConnection.java,v 1.1 2005/11/16 21:13:04 schulzs Exp $
5   
6    Copyright 2002 (C) The Werken Company. All Rights Reserved.
7   
8    Redistribution and use of this software and associated documentation
9    ("Software"), with or without modification, are permitted provided
10   that the following conditions are met:
11  
12   1. Redistributions of source code must retain copyright
13   statements and notices.  Redistributions must also contain a
14   copy of this document.
15  
16   2. Redistributions in binary form must reproduce the
17   above copyright notice, this list of conditions and the
18   following disclaimer in the documentation and/or other
19   materials provided with the distribution.
20  
21   3. The name "classworlds" must not be used to endorse or promote
22   products derived from this Software without prior written
23   permission of The Werken Company.  For written permission,
24   please contact bob@werken.com.
25  
26   4. Products derived from this Software may not be called "classworlds"
27   nor may "classworlds" appear in their names without prior written
28   permission of The Werken Company. "classworlds" is a registered
29   trademark of The Werken Company.
30  
31   5. Due credit should be given to The Werken Company.
32   (http://classworlds.werken.com/).
33  
34   THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS
35   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
36   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
37   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
38   THE WERKEN COMPANY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
39   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
40   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
41   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
42   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
43   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
44   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
45   OF THE POSSIBILITY OF SUCH DAMAGE.
46  
47   */
48  
49  import org.codehaus.classworlds.UrlUtils;
50  
51  import java.io.IOException;
52  import java.io.InputStream;
53  import java.net.JarURLConnection;
54  import java.net.MalformedURLException;
55  import java.net.URL;
56  import java.net.URLDecoder;
57  import java.util.ArrayList;
58  import java.util.List;
59  import java.util.StringTokenizer;
60  import java.util.jar.JarEntry;
61  import java.util.jar.JarFile;
62  import java.util.jar.JarInputStream;
63  
64  /***
65   * <code>URLConnection</code> capable of handling multiply-nested jars.
66   * 
67   * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
68   * @version $Id: JarUrlConnection.java,v 1.1 2005/11/16 21:13:04 schulzs Exp $
69   */
70  public class JarUrlConnection extends JarURLConnection {
71  	// ----------------------------------------------------------------------
72  	// Instance members
73  	// ----------------------------------------------------------------------
74  
75  	/***
76  	 * Base resource.
77  	 */
78  	private URL baseResource;
79  
80  	/***
81  	 * Additional nested segments.
82  	 */
83  	private String[] segments;
84  
85  	/***
86  	 * Terminal input-stream.
87  	 */
88  	private InputStream in;
89  
90  	// ----------------------------------------------------------------------
91  	// Constructors
92  	// ----------------------------------------------------------------------
93  
94  	/***
95  	 * Construct.
96  	 * 
97  	 * @param url
98  	 *            Target URL of the connections.
99  	 * @throws java.io.IOException
100 	 *             If an error occurs while attempting to initialize the
101 	 *             connection.
102 	 */
103 	JarUrlConnection(URL url) throws IOException {
104 		super(url = normaliseURL(url));
105 
106 		String baseText = url.getPath();
107 
108 		int bangLoc = baseText.indexOf("!");
109 
110 		String baseResourceText = baseText.substring(0, bangLoc);
111 
112 		String extraText = "";
113 
114 		if (bangLoc <= (baseText.length() - 2)
115 				&& baseText.charAt(bangLoc + 1) == '/') {
116 			if (bangLoc + 2 == baseText.length()) {
117 				extraText = "";
118 			} else {
119 				extraText = baseText.substring(bangLoc + 1);
120 			}
121 		} else {
122 			throw new MalformedURLException("No !/ in url: "
123 					+ url.toExternalForm());
124 		}
125 
126 		List segments = new ArrayList();
127 
128 		StringTokenizer tokens = new StringTokenizer(extraText, "!");
129 
130 		while (tokens.hasMoreTokens()) {
131 			segments.add(tokens.nextToken());
132 		}
133 
134 		this.segments = (String[]) segments
135 				.toArray(new String[segments.size()]);
136 
137 		this.baseResource = new URL(baseResourceText);
138 	}
139 
140 	protected static URL normaliseURL(URL url) throws MalformedURLException {
141 		String text = UrlUtils.normalizeUrlPath(url.toString());
142 
143 		if (!text.startsWith("jar:")) {
144 			text = "jar:" + text;
145 		}
146 
147 		if (text.indexOf('!') < 0) {
148 			text = text + "!/";
149 		}
150 
151 		return new URL(text);
152 	}
153 
154 	// ----------------------------------------------------------------------
155 	// Instance methods
156 	// ----------------------------------------------------------------------
157 
158 	/***
159 	 * Retrieve the nesting path segments.
160 	 * 
161 	 * @return The segments.
162 	 */
163 	protected String[] getSegments() {
164 		return this.segments;
165 	}
166 
167 	/***
168 	 * Retrieve the base resource <code>URL</code>.
169 	 * 
170 	 * @return The base resource url.
171 	 */
172 	protected URL getBaseResource() {
173 		return this.baseResource;
174 	}
175 
176 	/***
177 	 * @see java.net.URLConnection
178 	 */
179 	public void connect() throws IOException {
180 		if (this.segments.length == 0) {
181 			setupBaseResourceInputStream();
182 		} else {
183 			setupPathedInputStream();
184 		}
185 	}
186 
187 	/***
188 	 * Setup the <code>InputStream</code> purely from the base resource.
189 	 * 
190 	 * @throws java.io.IOException
191 	 *             If an I/O error occurs.
192 	 */
193 	protected void setupBaseResourceInputStream() throws IOException {
194 		this.in = getBaseResource().openStream();
195 	}
196 
197 	/***
198 	 * Setup the <code>InputStream</code> for URL with nested segments.
199 	 * 
200 	 * @throws java.io.IOException
201 	 *             If an I/O error occurs.
202 	 */
203 	protected void setupPathedInputStream() throws IOException {
204 		InputStream curIn = getBaseResource().openStream();
205 
206 		for (int i = 0; i < this.segments.length; ++i) {
207 			curIn = getSegmentInputStream(curIn, segments[i]);
208 		}
209 
210 		this.in = curIn;
211 	}
212 
213 	/***
214 	 * Retrieve the <code>InputStream</code> for the nesting segment relative
215 	 * to a base <code>InputStream</code>.
216 	 * 
217 	 * @param baseIn
218 	 *            The base input-stream.
219 	 * @param segment
220 	 *            The nesting segment path.
221 	 * @return The input-stream to the segment.
222 	 * @throws java.io.IOException
223 	 *             If an I/O error occurs.
224 	 */
225 	protected InputStream getSegmentInputStream(InputStream baseIn,
226 			String segment) throws IOException {
227 		JarInputStream jarIn = new JarInputStream(baseIn);
228 		JarEntry entry = null;
229 
230 		while (jarIn.available() != 0) {
231 			entry = jarIn.getNextJarEntry();
232 
233 			if (entry == null) {
234 				break;
235 			}
236 
237 			if (("/" + entry.getName()).equals(segment)) {
238 				return jarIn;
239 			}
240 		}
241 
242 		throw new IOException("unable to locate segment: " + segment);
243 	}
244 
245 	/***
246 	 * @see java.net.URLConnection
247 	 */
248 	public InputStream getInputStream() throws IOException {
249 		if (this.in == null) {
250 			connect();
251 		}
252 		return this.in;
253 	}
254 
255 	/***
256 	 * @return JarFile
257 	 * @throws java.io.IOException
258 	 * @see java.net.JarURLConnection#getJarFile()
259 	 */
260 	public JarFile getJarFile() throws IOException {
261 		String url = baseResource.toExternalForm();
262 
263 		if (url.startsWith("file:/")) {
264 			if (System.getProperty("os.name").toLowerCase().contains("windows")) {
265 				url = url.substring(6);
266 			} else {
267 				url = url.substring(5);
268 			}
269 		}
270 
271 		return new JarFile(URLDecoder.decode(url, "UTF-8"));
272 	}
273 }