View Javadoc
1   package org.codehaus.mojo.javacc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file 
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   * 
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   * 
14   * Unless required by applicable law or agreed to in writing, 
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
17   * KIND, either express or implied.  See the License for the 
18   * specific language governing permissions and limitations 
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.net.MalformedURLException;
24  import java.net.URL;
25  import java.nio.ByteBuffer;
26  import java.nio.charset.Charset;
27  
28  /**
29   * Assists in handling of URLs.
30   * 
31   * @author Benjamin Bentmann
32   * @version $Id$
33   */
34  class UrlUtils
35  {
36  
37      /**
38       * The UTF-8 character set, used to decode octets in URLs.
39       */
40      private static final Charset UTF8 = Charset.forName( "UTF-8" );
41  
42      /**
43       * The protocol prefix for "jar:" URLs.
44       */
45      private static final String JAR = "jar:";
46  
47      /**
48       * The protocol prefix for "file:" URLs.
49       */
50      private static final String FILE = "file:";
51  
52      /**
53       * The protocol prefix for "jar:file:" URLs.
54       */
55      private static final String JAR_FILE = JAR + FILE;
56  
57      /**
58       * Gets the absolute filesystem path to the class path root for the specified resource. The root is either a JAR
59       * file or a directory with loose class files. If the URL does not use a supported protocol, an exception will be
60       * thrown.
61       * 
62       * @param url The URL to the resource, may be <code>null</code>.
63       * @param resource The name of the resource, must not be <code>null</code>.
64       * @return The absolute filesystem path to the class path root of the resource or <code>null</code> if the input
65       *         URL was <code>null</code>.
66       */
67      public static File getResourceRoot( URL url, String resource )
68      {
69          String path = null;
70          if ( url != null )
71          {
72              String spec = url.toExternalForm();
73              if ( ( JAR_FILE ).regionMatches( true, 0, spec, 0, JAR_FILE.length() ) )
74              {
75                  URL jar;
76                  try
77                  {
78                      jar = new URL( spec.substring( JAR.length(), spec.lastIndexOf( "!/" ) ) );
79                  }
80                  catch ( MalformedURLException e )
81                  {
82                      throw new IllegalArgumentException( "Invalid JAR URL: " + url + ", " + e.getMessage() );
83                  }
84                  path = decodeUrl( jar.getPath() );
85              }
86              else if ( FILE.regionMatches( true, 0, spec, 0, FILE.length() ) )
87              {
88                  path = decodeUrl( url.getPath() );
89                  path = path.substring( 0, path.length() - resource.length() );
90              }
91              else
92              {
93                  throw new IllegalArgumentException( "Invalid class path URL: " + url );
94              }
95          }
96          return ( path != null ) ? new File( path ) : null;
97      }
98  
99      /**
100      * Decodes the specified URL as per RFC 3986, i.e. transforms percent-encoded octets to characters by decoding with
101      * the UTF-8 character set. This function is primarily intended for usage with {@link java.net.URL} which
102      * unfortunately does not enforce proper URLs. As such, this method will leniently accept invalid characters or
103      * malformed percent-encoded octets and simply pass them literally through to the result string. Except for rare
104      * edge cases, this will make unencoded URLs pass through unaltered.
105      * 
106      * @param url The URL to decode, may be <code>null</code>.
107      * @return The decoded URL or <code>null</code> if the input was <code>null</code>.
108      */
109     public static String decodeUrl( String url )
110     {
111         String decoded = url;
112         if ( url != null && url.indexOf( '%' ) >= 0 )
113         {
114             int n = url.length();
115             StringBuffer buffer = new StringBuffer();
116             ByteBuffer bytes = ByteBuffer.allocate( n );
117             for ( int i = 0; i < n; )
118             {
119                 if ( url.charAt( i ) == '%' )
120                 {
121                     try
122                     {
123                         do
124                         {
125                             byte octet = (byte) Integer.parseInt( url.substring( i + 1, i + 3 ), 16 );
126                             bytes.put( octet );
127                             i += 3;
128                         }
129                         while ( i < n && url.charAt( i ) == '%' );
130                         continue;
131                     }
132                     catch ( RuntimeException e )
133                     {
134                         // malformed percent-encoded octet, fall through and append characters literally
135                     }
136                     finally
137                     {
138                         if ( bytes.position() > 0 )
139                         {
140                             bytes.flip();
141                             buffer.append( UTF8.decode( bytes ).toString() );
142                             bytes.clear();
143                         }
144                     }
145                 }
146                 buffer.append( url.charAt( i++ ) );
147             }
148             decoded = buffer.toString();
149         }
150         return decoded;
151     }
152 
153 }