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 }