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