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 }