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