1 /* 2 * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.javadoc.internal.doclets.toolkit.util; 27 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collections; 31 import java.util.List; 32 33 import javax.lang.model.element.ModuleElement; 34 import javax.lang.model.element.PackageElement; 35 import javax.lang.model.element.TypeElement; 36 37 /** 38 * Abstraction for immutable relative paths. 39 * Paths always use '/' as a separator, and never begin or end with '/'. 40 * 41 * <p><b>This is NOT part of any supported API. 42 * If you write code that depends on this, you do so at your own risk. 43 * This code and its internal interfaces are subject to change or 44 * deletion without notice.</b> 45 */ 46 public class DocPath { 47 private final String path; 48 49 /** The empty path. */ 50 public static final DocPath empty = new DocPath(""); 51 52 /** The empty path. */ 53 public static final DocPath parent = new DocPath(".."); 54 55 /** 56 * Creates a path from a string. 57 * @param p the string 58 * @return the path 59 */ 60 public static DocPath create(String p) { 61 return (p == null) || p.isEmpty() ? empty : new DocPath(p); 62 } 63 64 /** 65 * Returns the path for a class. 66 * For example, if the class is java.lang.Object, 67 * the path is java/lang/Object.html. 68 * @param utils utility class for handling type elements 69 * @param typeElement the type element 70 * @return the path 71 */ 72 public static DocPath forClass(Utils utils, TypeElement typeElement) { 73 return (typeElement == null) 74 ? empty 75 : forPackage(utils.containingPackage(typeElement)).resolve(forName(utils, typeElement)); 76 } 77 78 /** 79 * Returns the path for the simple name of a class. 80 * For example, if the class is java.lang.Object, 81 * the path is Object.html. 82 * @param utils utility class for handling type elements 83 * @param typeElement the type element 84 * @return the path 85 */ 86 public static DocPath forName(Utils utils, TypeElement typeElement) { 87 return (typeElement == null) ? empty : new DocPath(utils.getSimpleName(typeElement) + ".html"); 88 } 89 90 /** 91 * Returns the path for the name of a module. 92 * For example, if the module is java.base, 93 * the path is java.base. 94 * @param mdle the module element 95 * @return the path 96 */ 97 public static DocPath forModule(ModuleElement mdle) { 98 return mdle == null || mdle.isUnnamed() 99 ? empty 100 : DocPath.create(mdle.getQualifiedName().toString()); 101 } 102 103 /** 104 * Returns the path for the package of a class. 105 * For example, if the class is java.lang.Object, 106 * the path is java/lang. 107 * @param utils utility class for handling type elements 108 * @param typeElement the type element 109 * @return the path 110 */ 111 public static DocPath forPackage(Utils utils, TypeElement typeElement) { 112 return (typeElement == null) ? empty : forPackage(utils.containingPackage(typeElement)); 113 } 114 115 /** 116 * Returns the path for a package. 117 * For example, if the package is java.lang, 118 * the path is java/lang. 119 * @param pkgElement the package element 120 * @return the path 121 */ 122 public static DocPath forPackage(PackageElement pkgElement) { 123 return pkgElement == null || pkgElement.isUnnamed() 124 ? empty 125 : DocPath.create(pkgElement.getQualifiedName().toString().replace('.', '/')); 126 } 127 128 /** 129 * Returns the inverse path for a package. 130 * For example, if the package is java.lang, 131 * the inverse path is ../... 132 * @param pkgElement the package element 133 * @return the path 134 */ 135 public static DocPath forRoot(PackageElement pkgElement) { 136 String name = (pkgElement == null || pkgElement.isUnnamed()) 137 ? "" 138 : pkgElement.getQualifiedName().toString(); 139 return new DocPath(name.replace('.', '/').replaceAll("[^/]+", "..")); 140 } 141 142 /** 143 * Returns the relative path from one package to another. 144 * @param from the initial package 145 * @param to the target package 146 * @return the path 147 */ 148 public static DocPath relativePath(PackageElement from, PackageElement to) { 149 return forRoot(from).resolve(forPackage(to)); 150 } 151 152 protected DocPath(String p) { 153 path = (p.endsWith("/") ? p.substring(0, p.length() - 1) : p); 154 } 155 156 /** {@inheritDoc} */ 157 @Override 158 public boolean equals(Object other) { 159 return (other instanceof DocPath) && path.equals(((DocPath)other).path); 160 } 161 162 /** {@inheritDoc} */ 163 @Override 164 public int hashCode() { 165 return path.hashCode(); 166 } 167 168 public DocPath basename() { 169 int sep = path.lastIndexOf("/"); 170 return (sep == -1) ? this : new DocPath(path.substring(sep + 1)); 171 } 172 173 public DocPath parent() { 174 int sep = path.lastIndexOf("/"); 175 return (sep == -1) ? empty : new DocPath(path.substring(0, sep)); 176 } 177 178 /** 179 * Returns the path formed by appending the specified string to the current path. 180 * @param p the string 181 * @return the path 182 */ 183 public DocPath resolve(String p) { 184 if (p == null || p.isEmpty()) 185 return this; 186 if (path.isEmpty()) 187 return new DocPath(p); 188 return new DocPath(path + "/" + p); 189 } 190 191 /** 192 * Returns the path by appending the specified path to the current path. 193 * @param p the path 194 * @return the path 195 */ 196 public DocPath resolve(DocPath p) { 197 if (p == null || p.isEmpty()) 198 return this; 199 if (path.isEmpty()) 200 return p; 201 return new DocPath(path + "/" + p.getPath()); 202 } 203 204 /** 205 * Return the inverse path for this path. 206 * For example, if the path is a/b/c, the inverse path is ../../.. 207 * @return the path 208 */ 209 public DocPath invert() { 210 return new DocPath(path.replaceAll("[^/]+", "..")); 211 } 212 213 /** 214 * Returns the path formed by eliminating empty components, 215 * '.' components, and redundant name/.. components. 216 * @return the path 217 */ 218 public DocPath normalize() { 219 return path.isEmpty() 220 ? this 221 : new DocPath(String.join("/", normalize(path))); 222 } 223 224 private static List<String> normalize(String path) { 225 return normalize(Arrays.asList(path.split("/"))); 226 } 227 228 private static List<String> normalize(List<String> parts) { 229 if (parts.stream().noneMatch(s -> s.isEmpty() || s.equals(".") || s.equals(".."))) { 230 return parts; 231 } 232 List<String> normalized = new ArrayList<>(); 233 for (String part : parts) { 234 switch (part) { 235 case "": 236 case ".": 237 break; 238 case "..": 239 int n = normalized.size(); 240 if (n > 0 && !normalized.get(n - 1).equals("..")) { 241 normalized.remove(n - 1); 242 } else { 243 normalized.add(part); 244 } 245 break; 246 default: 247 normalized.add(part); 248 } 249 } 250 return normalized; 251 } 252 253 /** 254 * Normalize and relativize a path against this path, 255 * assuming that this path is for a file (not a directory), 256 * in which the other path will appear. 257 * 258 * @param other the path to be relativized. 259 * @return the simplified path 260 */ 261 public DocPath relativize(DocPath other) { 262 if (other == null || other.path.isEmpty()) { 263 return this; 264 } 265 266 if (path.isEmpty()) { 267 return other; 268 } 269 270 List<String> originParts = normalize(path); 271 int sep = path.lastIndexOf("/"); 272 List<String> destParts = sep == -1 273 ? normalize(other.path) 274 : normalize(path.substring(0, sep + 1) + other.path); 275 int common = 0; 276 while (common < originParts.size() 277 && common < destParts.size() 278 && originParts.get(common).equals(destParts.get(common))) { 279 common++; 280 } 281 282 List<String> newParts; 283 if (common == originParts.size()) { 284 newParts = destParts.subList(common, destParts.size()); 285 } else { 286 newParts = new ArrayList<>(); 287 newParts.addAll(Collections.nCopies(originParts.size() - common - 1, "..")); 288 newParts.addAll(destParts.subList(common, destParts.size())); 289 } 290 return new DocPath(String.join("/", newParts)); 291 } 292 293 /** 294 * Return true if this path is empty. 295 * @return true if this path is empty 296 */ 297 public boolean isEmpty() { 298 return path.isEmpty(); 299 } 300 301 /** 302 * Creates a DocLink formed from this path and a fragment identifier. 303 * @param fragment the fragment 304 * @return the link 305 */ 306 public DocLink fragment(String fragment) { 307 return new DocLink(path, null, fragment); 308 } 309 310 /** 311 * Creates a DocLink formed from this path and a query string. 312 * @param query the query string 313 * @return the link 314 */ 315 public DocLink query(String query) { 316 return new DocLink(path, query, null); 317 } 318 319 /** 320 * Returns this path as a string. 321 * @return the path 322 */ 323 // This is provided instead of using toString() to help catch 324 // unintended use of toString() in string concatenation sequences. 325 public String getPath() { 326 return path; 327 } 328 }