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