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 }