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 protected DocPath(String p) { 65 path = (p.endsWith("/") ? p.substring(0, p.length() - 1) : p); 66 } 67 68 /** {@inheritDoc} */ 69 @Override 70 public boolean equals(Object other) { 71 return (other instanceof DocPath) && path.equals(((DocPath)other).path); 72 } 73 74 /** {@inheritDoc} */ 75 @Override 76 public int hashCode() { 77 return path.hashCode(); 78 } 79 80 public DocPath basename() { 81 int sep = path.lastIndexOf("/"); 82 return (sep == -1) ? this : new DocPath(path.substring(sep + 1)); 83 } 84 85 public DocPath parent() { 86 int sep = path.lastIndexOf("/"); 87 return (sep == -1) ? empty : new DocPath(path.substring(0, sep)); 88 } 89 90 /** 91 * Returns the path formed by appending the specified string to the current path. 92 * @param p the string 93 * @return the path 94 */ 95 public DocPath resolve(String p) { 96 if (p == null || p.isEmpty()) 97 return this; 98 if (path.isEmpty()) 99 return new DocPath(p); 100 return new DocPath(path + "/" + p); 101 } 102 103 /** 104 * Returns the path by appending the specified path to the current path. 105 * @param p the path 106 * @return the path 107 */ 108 public DocPath resolve(DocPath p) { 109 if (p == null || p.isEmpty()) 110 return this; 111 if (path.isEmpty()) 112 return p; 113 return new DocPath(path + "/" + p.getPath()); 114 } 115 116 /** 117 * Return the inverse path for this path. 118 * For example, if the path is a/b/c, the inverse path is ../../.. 119 * @return the path 120 */ 121 public DocPath invert() { 122 return new DocPath(path.replaceAll("[^/]+", "..")); 123 } 124 125 /** 126 * Returns the path formed by eliminating empty components, 127 * '.' components, and redundant name/.. components. 128 * @return the path 129 */ 130 public DocPath normalize() { 131 return path.isEmpty() 132 ? this 133 : new DocPath(String.join("/", normalize(path))); 134 } 135 136 private static List<String> normalize(String path) { 137 return normalize(Arrays.asList(path.split("/"))); 138 } 139 140 private static List<String> normalize(List<String> parts) { 141 if (parts.stream().noneMatch(s -> s.isEmpty() || s.equals(".") || s.equals(".."))) { 142 return parts; 143 } 144 List<String> normalized = new ArrayList<>(); 145 for (String part : parts) { 146 switch (part) { 147 case "": 148 case ".": 149 break; 150 case "..": 151 int n = normalized.size(); 152 if (n > 0 && !normalized.get(n - 1).equals("..")) { 153 normalized.remove(n - 1); 154 } else { 155 normalized.add(part); 156 } 157 break; 158 default: 159 normalized.add(part); 160 } 161 } 162 return normalized; 163 } 164 165 /** 166 * Normalize and relativize a path against this path, 167 * assuming that this path is for a file (not a directory), 168 * in which the other path will appear. 169 * 170 * @param other the path to be relativized. 171 * @return the simplified path 172 */ 173 public DocPath relativize(DocPath other) { 174 if (other == null || other.path.isEmpty()) { 175 return this; 176 } 177 178 if (path.isEmpty()) { 179 return other; 180 } 181 182 List<String> originParts = normalize(path); 183 int sep = path.lastIndexOf("/"); 184 List<String> destParts = sep == -1 185 ? normalize(other.path) 186 : normalize(path.substring(0, sep + 1) + other.path); 187 int common = 0; 188 while (common < originParts.size() 189 && common < destParts.size() 190 && originParts.get(common).equals(destParts.get(common))) { 191 common++; 192 } 193 194 List<String> newParts; 195 if (common == originParts.size()) { 196 newParts = destParts.subList(common, destParts.size()); 197 } else { 198 newParts = new ArrayList<>(); 199 newParts.addAll(Collections.nCopies(originParts.size() - common - 1, "..")); 200 newParts.addAll(destParts.subList(common, destParts.size())); 201 } 202 return new DocPath(String.join("/", newParts)); 203 } 204 205 /** 206 * Return true if this path is empty. 207 * @return true if this path is empty 208 */ 209 public boolean isEmpty() { 210 return path.isEmpty(); 211 } 212 213 /** 214 * Creates a DocLink formed from this path and a fragment identifier. 215 * @param fragment the fragment 216 * @return the link 217 */ 218 public DocLink fragment(String fragment) { 219 return new DocLink(path, null, fragment); 220 } 221 222 /** 223 * Creates a DocLink formed from this path and a query string. 224 * @param query the query string 225 * @return the link 226 */ 227 public DocLink query(String query) { 228 return new DocLink(path, query, null); 229 } 230 231 /** 232 * Returns this path as a string. 233 * @return the path 234 */ 235 // This is provided instead of using toString() to help catch 236 // unintended use of toString() in string concatenation sequences. 237 public String getPath() { 238 return path; 239 } 240 }