View Javadoc
1   package com.atlassian.plugin.url;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.net.JarURLConnection;
6   import java.net.MalformedURLException;
7   import java.net.URL;
8   import java.net.URLDecoder;
9   import java.util.ArrayList;
10  import java.util.List;
11  import java.util.StringTokenizer;
12  import java.util.jar.JarEntry;
13  import java.util.jar.JarFile;
14  import java.util.jar.JarInputStream;
15  
16  /**
17   */
18  class InnerJarURLConnection extends JarURLConnection {
19      private URL baseResource;
20      private String[] segments;
21      private InputStream in;
22  
23      public InnerJarURLConnection(URL url) throws IOException {
24          super(url = normaliseURL(url));
25  
26          String baseText = url.getPath();
27  
28          int bangLoc = baseText.indexOf("!");
29  
30          String baseResourceText = baseText.substring(0, bangLoc);
31  
32          String extraText = "";
33  
34          if (bangLoc <= (baseText.length() - 2)
35                  &&
36                  baseText.charAt(bangLoc + 1) == '/') {
37              if (bangLoc + 2 == baseText.length()) {
38                  extraText = "";
39              } else {
40                  extraText = baseText.substring(bangLoc + 1);
41              }
42          } else {
43              throw new MalformedURLException("No !/ in url: " + url.toExternalForm());
44          }
45  
46  
47          List<String> segments = new ArrayList<String>();
48  
49          StringTokenizer tokens = new StringTokenizer(extraText, "!");
50  
51          while (tokens.hasMoreTokens()) {
52              segments.add(tokens.nextToken());
53          }
54  
55          this.segments = segments.toArray(new String[segments.size()]);
56          this.baseResource = new URL(baseResourceText);
57      }
58  
59      protected static URL normaliseURL(URL url) throws MalformedURLException {
60          String text = normalizeUrlPath(url.toString());
61  
62          if (!text.startsWith("jar:")) {
63              text = "jar:" + text;
64          }
65  
66          if (text.indexOf('!') < 0) {
67              text = text + "!/";
68          }
69  
70          return new URL(text);
71      }
72  
73      /**
74       * Retrieve the nesting path segments.
75       *
76       * @return The segments.
77       */
78      protected String[] getSegments() {
79          return this.segments;
80      }
81  
82      /**
83       * Retrieve the base resource <code>URL</code>.
84       *
85       * @return The base resource url.
86       */
87      protected URL getBaseResource() {
88          return this.baseResource;
89      }
90  
91      /**
92       * @see java.net.URLConnection
93       */
94      public void connect() throws IOException {
95          if (this.segments.length == 0) {
96              setupBaseResourceInputStream();
97          } else {
98              setupPathedInputStream();
99          }
100     }
101 
102     /**
103      * Setup the <code>InputStream</code> purely from the base resource.
104      *
105      * @throws java.io.IOException If an I/O error occurs.
106      */
107     protected void setupBaseResourceInputStream() throws IOException {
108         this.in = getBaseResource().openStream();
109     }
110 
111     /**
112      * Setup the <code>InputStream</code> for URL with nested segments.
113      *
114      * @throws java.io.IOException If an I/O error occurs.
115      */
116     protected void setupPathedInputStream() throws IOException {
117         InputStream curIn = getBaseResource().openStream();
118 
119         for (int i = 0; i < this.segments.length; ++i) {
120             curIn = getSegmentInputStream(curIn, segments[i]);
121         }
122 
123         this.in = curIn;
124     }
125 
126     /**
127      * Retrieve the <code>InputStream</code> for the nesting
128      * segment relative to a base <code>InputStream</code>.
129      *
130      * @param baseIn  The base input-stream.
131      * @param segment The nesting segment path.
132      * @return The input-stream to the segment.
133      * @throws java.io.IOException If an I/O error occurs.
134      */
135     protected InputStream getSegmentInputStream(InputStream baseIn, String segment) throws IOException {
136         JarInputStream jarIn = new JarInputStream(baseIn);
137         JarEntry entry = null;
138 
139         while (jarIn.available() != 0) {
140             entry = jarIn.getNextJarEntry();
141 
142             if (entry == null) {
143                 break;
144             }
145 
146             if (("/" + entry.getName()).equals(segment)) {
147                 return jarIn;
148             }
149         }
150 
151         throw new IOException("unable to locate segment: " + segment);
152     }
153 
154     /**
155      * @see java.net.URLConnection
156      */
157     public InputStream getInputStream() throws IOException {
158         if (this.in == null) {
159             connect();
160         }
161         return this.in;
162     }
163 
164     /**
165      * @return JarFile
166      * @throws java.io.IOException
167      * @see java.net.JarURLConnection#getJarFile()
168      */
169     public JarFile getJarFile() throws IOException {
170         String url = baseResource.toExternalForm();
171 
172         if (url.startsWith("file:/")) {
173             url = url.substring(6);
174         }
175 
176         return new JarFile(URLDecoder.decode(url, "UTF-8"));
177     }
178 
179     private static String normalizeUrlPath(String name) {
180         if (name.startsWith("/")) {
181             name = name.substring(1);
182         }
183 
184         // Looking for org/codehaus/werkflow/personality/basic/../common/core-idioms.xml
185         //                                               |    i  |
186         //                                               +-------+ remove
187         //
188         int i = name.indexOf("/..");
189 
190         // Can't be at the beginning because we have no root to refer to so
191         // we start at 1.
192         if (i > 0) {
193             int j = name.lastIndexOf("/", i - 1);
194 
195             name = name.substring(0, j) + name.substring(i + 3);
196         }
197 
198         return name;
199     }
200 
201 }