View Javadoc
1   package org.apache.maven.doxia.site.decoration.inheritance;
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.net.URI;
23  import java.net.URISyntaxException;
24  
25  import org.codehaus.plexus.util.PathTool;
26  
27  /**
28   * Describes a link that may be absolute or relative, and that is anchored to an absolute URI.
29   *
30   * @author ltheussl
31   *
32   * @since 1.2
33   */
34  public class URIPathDescriptor
35  {
36      private final URI baseURI;
37      private final URI link;
38  
39      /**
40       * A URIPathDescriptor consists of a base URI and a link.
41       * Both arguments to this constructor have to be parsable to URIs.
42       * The baseURI parameter has to be absolute in the sense of {@link URI#isAbsolute()}.
43       *
44       * Before being parsed to {@link URI}s, the arguments are modified to catch
45       * some common bad practices: first all Windows-style backslashes '\' are replaced by
46       * forward slashes '/'.
47       * If the baseURI does not end with '/', a slash is appended.
48       * If the link starts with a '/', the first character is stripped.
49       *
50       * @param baseURI The base URI. Has to be a valid absolute URI.
51       *      In addition, the path of the URI should not have any file part,
52       *      ie <code>http://maven.apache.org/</code> is valid,
53       *      <code>http://maven.apache.org/index.html</code> is not.
54       *      Even though the latter form is accepted without warning,
55       *      the methods in this class will not return what is probably expected,
56       *      because a slash is appended during construction, as noted above.
57       * @param link the link. This may be a relative link or an absolute link.
58       *      Note that URIs that start with a "/", ie don't specify a scheme, are considered relative.
59       *
60       * @throws IllegalArgumentException if either argument is not parsable as a URI,
61       *      or if baseURI is not absolute.
62       */
63      public URIPathDescriptor( final String baseURI, final String link )
64      {
65          final String llink = sanitizeLink( link );
66          final String bbase = sanitizeBase( baseURI );
67  
68          this.baseURI = URI.create( bbase ).normalize();
69          this.link = URI.create( llink ).normalize();
70  
71          if ( !this.baseURI.isAbsolute() )
72          {
73              throw new IllegalArgumentException( "Base URI is not absolute: " + baseURI );
74          }
75      }
76  
77      /**
78       * Return the base of this URIPathDescriptor as a URI.
79       * This is always {@link URI#normalize() normalized}.
80       *
81       * @return the normalized base URI.
82       */
83      public URI getBaseURI()
84      {
85          return baseURI;
86      }
87  
88      /**
89       * Return the link of this URIPathDescriptor as a URI.
90       * This is always {@link URI#normalize() normalized}.
91       *
92       * @return the normalized link URI.
93       */
94      public URI getLink()
95      {
96          return link;
97      }
98  
99      /**
100      * Resolve the link to the base.
101      * This always returns an absolute URI. If link is absolute, link is returned.
102      *
103      * @return the resolved link. This is equivalent to calling
104      *      {@link #getBaseURI()}.{@link URI#resolve(java.net.URI) resolve}( {@link #getLink()} ).
105      */
106     public URI resolveLink()
107     {
108         return baseURI.resolve( link );
109     }
110 
111     /**
112      * Calculate the relative link with respect to the base.
113      * The original link is returned if either
114      *      link is relative;
115      *      or link and base do not share the {@link #sameSite(java.net.URI) same site}.
116      *
117      * @return the link as a relative URI.
118      */
119     public URI relativizeLink()
120     {
121         return relativizeLink( baseURI.toString(), link );
122     }
123 
124     // NOTE: URI.relativize does not work as expected, see
125     // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6226081
126     private static URI relativizeLink( final String base, final URI link )
127     {
128         if ( !link.isAbsolute() )
129         {
130             return link;
131         }
132 
133         final URI newBaseURI = URI.create( base );
134 
135         if ( !sameSite( link, newBaseURI ) )
136         {
137             return link;
138         }
139 
140         final String relativePath = PathTool.getRelativeWebPath( newBaseURI.toString(), link.toString() );
141 
142         return URI.create( correctRelativePath( relativePath ) );
143     }
144 
145     /**
146      * Calculate the link as viewed from a different base.
147      * This returns the original link if link is absolute.
148      * This returns {@link #resolveLink()} if either
149      *      newBase == null,
150      *      or newBase is not parsable as a URI,
151      *      or newBase and this {@link #getBaseURI()} do not share the
152      *      {@link #sameSite(java.net.URI) same site}.
153      *
154      * @param newBase the new base URI. Has to be parsable as a URI.
155      *.
156      * @return a new relative link or the original link {@link #resolveLink() resolved},
157      *      i.e. as an absolute link, if the link cannot be re-based.
158      */
159     public URI rebaseLink( final String newBase )
160     {
161         if ( link.isAbsolute() )
162         {
163             return link;
164         }
165 
166         if ( newBase == null )
167         {
168             return resolveLink();
169         }
170 
171         final URI newBaseURI;
172 
173         try
174         {
175             newBaseURI = new URI( newBase );
176         }
177         catch ( URISyntaxException ex )
178         {
179             return resolveLink();
180         }
181 
182         if ( !sameSite( newBaseURI ) )
183         {
184             return resolveLink();
185         }
186 
187         final String relativeBasePath = PathTool.getRelativeWebPath( newBaseURI.getPath(), baseURI.getPath() );
188 
189         return URI.create( correctRelativePath( relativeBasePath ) ).resolve( link );
190     }
191 
192     private static String correctRelativePath( final String relativePath )
193     {
194         if ( "".equals( relativePath ) || "/".equals( relativePath ) )
195         {
196             return "./";
197         }
198         else
199         {
200             return relativePath;
201         }
202     }
203 
204     /**
205      * Check if this URIPathDescriptor lives on the same site as the given URI.
206      *
207      * @param uri a URI to compare with.
208      *      May be null, in which case false is returned.
209      *
210      * @return true if {@link #getBaseURI()} shares the same scheme, host and port with the given URI
211      *      where null values are allowed.
212      */
213     public boolean sameSite( final URI uri )
214     {
215         return ( uri != null ) && sameSite( this.baseURI, uri );
216     }
217 
218     private static boolean sameSite( final URI baseURI, final URI newBaseURI )
219     {
220         final boolean sameScheme =
221             ( newBaseURI.getScheme() == null ? false : baseURI.getScheme().equalsIgnoreCase( newBaseURI.getScheme() ) );
222         final boolean sameHost =
223             ( baseURI.getHost() == null ? newBaseURI.getHost() == null
224                             : baseURI.getHost().equalsIgnoreCase( newBaseURI.getHost() ) );
225         final boolean samePort = ( baseURI.getPort() == newBaseURI.getPort() );
226 
227         return ( sameScheme && samePort && sameHost );
228     }
229 
230     /**
231      * Construct a string representation of this URIPathDescriptor.
232      * This is equivalent to calling {@link #resolveLink()}.toString().
233      *
234      * @return this URIPathDescriptor as a String.
235      */
236     @Override
237     public String toString()
238     {
239         return resolveLink().toString();
240     }
241 
242     private static String sanitizeBase( final String base )
243     {
244         String sane = base.replace( '\\', '/' );
245 
246         if ( !sane.endsWith( "/" ) )
247         {
248             sane += "/";
249         }
250 
251         return sane;
252     }
253 
254     private static String sanitizeLink( final String link )
255     {
256         String sane = link.replace( '\\', '/' );
257 
258         if ( sane.startsWith( "/" ) )
259         {
260             sane = sane.substring( 1 );
261         }
262 
263         return sane;
264     }
265 }