1 package org.codehaus.classworlds.uberjar.protocol.jar;
2
3 /*
4 $Id: JarUrlConnection.java 78 2004-07-01 13:59:13Z jvanzyl $
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 java.io.IOException;
50 import java.io.InputStream;
51 import java.net.*;
52 import java.util.*;
53 import java.util.jar.JarEntry;
54 import java.util.jar.JarFile;
55 import java.util.jar.JarInputStream;
56
57 /**
58 * This is copied from Classwords 1.1 org.codehaus.classworlds.uberjar.protocol.jar.JarURLConnection
59 * so that an additional dependency does not need to be added to plugins. The formatting is left as is to reduce
60 * the diff.
61 */
62 public class NonLockingJarUrlConnection
63 extends JarURLConnection
64 {
65 // ----------------------------------------------------------------------
66 // Instance members
67 // ----------------------------------------------------------------------
68
69 /**
70 * Base resource.
71 */
72 private URL baseResource;
73
74 /**
75 * Additional nested segments.
76 */
77 private String[] segments;
78
79 /**
80 * Terminal input-stream.
81 */
82 private InputStream in;
83
84 // ----------------------------------------------------------------------
85 // Constructors
86 // ----------------------------------------------------------------------
87
88 /**
89 * Construct.
90 *
91 * @param url Target URL of the connections.
92 * @throws java.io.IOException If an error occurs while attempting to initialize
93 * the connection.
94 */
95 NonLockingJarUrlConnection( URL url )
96 throws IOException
97 {
98 super( url = normaliseURL( url ) );
99
100 String baseText = url.getPath();
101
102 int bangLoc = baseText.indexOf( "!" );
103
104 String baseResourceText = baseText.substring( 0, bangLoc );
105
106 String extraText = "";
107
108 if ( bangLoc <= ( baseText.length() - 2 )
109 &&
110 baseText.charAt( bangLoc + 1 ) == '/' )
111 {
112 if ( bangLoc + 2 == baseText.length() )
113 {
114 extraText = "";
115 }
116 else
117 {
118 extraText = baseText.substring( bangLoc + 1 );
119 }
120 }
121 else
122 {
123 throw new MalformedURLException( "No !/ in url: " + url.toExternalForm() );
124 }
125
126
127 List segments = new ArrayList();
128
129 StringTokenizer tokens = new StringTokenizer( extraText, "!" );
130
131 while ( tokens.hasMoreTokens() )
132 {
133 segments.add( tokens.nextToken() );
134 }
135
136 this.segments = (String[]) segments.toArray( new String[segments.size()] );
137
138 this.baseResource = new URL( baseResourceText );
139 }
140
141 protected static URL normaliseURL( URL url ) throws MalformedURLException
142 {
143 String text = normalizeUrlPath( url.toString() );
144
145 if ( !text.startsWith( "jar:" ) )
146 {
147 text = "jar:" + text;
148 }
149
150 if ( text.indexOf( '!' ) < 0 )
151 {
152 text = text + "!/";
153 }
154
155 return new URL( text );
156 }
157
158 // ----------------------------------------------------------------------
159 // Instance methods
160 // ----------------------------------------------------------------------
161
162 /**
163 * Retrieve the nesting path segments.
164 *
165 * @return The segments.
166 */
167 protected String[] getSegments()
168 {
169 return this.segments;
170 }
171
172 /**
173 * Retrieve the base resource <code>URL</code>.
174 *
175 * @return The base resource url.
176 */
177 protected URL getBaseResource()
178 {
179 return this.baseResource;
180 }
181
182 /**
183 * @see java.net.URLConnection
184 */
185 public void connect()
186 throws IOException
187 {
188 if ( this.segments.length == 0 )
189 {
190 setupBaseResourceInputStream();
191 }
192 else
193 {
194 setupPathedInputStream();
195 }
196 }
197
198 /**
199 * Setup the <code>InputStream</code> purely from the base resource.
200 *
201 * @throws java.io.IOException If an I/O error occurs.
202 */
203 protected void setupBaseResourceInputStream()
204 throws IOException
205 {
206 this.in = getBaseResource().openStream();
207 }
208
209 /**
210 * Setup the <code>InputStream</code> for URL with nested segments.
211 *
212 * @throws java.io.IOException If an I/O error occurs.
213 */
214 protected void setupPathedInputStream()
215 throws IOException
216 {
217 InputStream curIn = getBaseResource().openStream();
218
219 for ( int i = 0; i < this.segments.length; ++i )
220 {
221 curIn = getSegmentInputStream( curIn,
222 segments[i] );
223 }
224
225 this.in = curIn;
226 }
227
228 /**
229 * Retrieve the <code>InputStream</code> for the nesting
230 * segment relative to a base <code>InputStream</code>.
231 *
232 * @param baseIn The base input-stream.
233 * @param segment The nesting segment path.
234 * @return The input-stream to the segment.
235 * @throws java.io.IOException If an I/O error occurs.
236 */
237 protected InputStream getSegmentInputStream( InputStream baseIn,
238 String segment )
239 throws IOException
240 {
241 JarInputStream jarIn = new JarInputStream( baseIn );
242 JarEntry entry = null;
243
244 while ( jarIn.available() != 0 )
245 {
246 entry = jarIn.getNextJarEntry();
247
248 if ( entry == null )
249 {
250 break;
251 }
252
253 if ( ( "/" + entry.getName() ).equals( segment ) )
254 {
255 return jarIn;
256 }
257 }
258
259 throw new IOException( "unable to locate segment: " + segment );
260 }
261
262 /**
263 * @see java.net.URLConnection
264 */
265 public InputStream getInputStream()
266 throws IOException
267 {
268 if ( this.in == null )
269 {
270 connect();
271 }
272 return this.in;
273 }
274
275 /**
276 * @return JarFile
277 * @throws java.io.IOException
278 * @see java.net.JarURLConnection#getJarFile()
279 */
280 public JarFile getJarFile() throws IOException
281 {
282 String url = baseResource.toExternalForm();
283
284 if ( url.startsWith( "file:/" ) )
285 {
286 url = url.substring( 6 );
287 }
288
289 return new JarFile( URLDecoder.decode( url, "UTF-8" ) );
290 }
291
292 private static String normalizeUrlPath(String name) {
293 if (name.startsWith("/")) {
294 name = name.substring(1);
295 }
296 int i = name.indexOf("/..");
297 if (i > 0) {
298 int j = name.lastIndexOf("/", i - 1);
299 name = name.substring(0, j) + name.substring(i + 3);
300 }
301
302 return name;
303 }
304 }