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 }