001/* 002 * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.toolkit.util; 027 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Collections; 031import java.util.List; 032 033/** 034 * Abstraction for immutable relative paths. 035 * Paths always use '/' as a separator, and never begin or end with '/'. 036 */ 037public class DocPath { 038 private final String path; 039 040 /** The empty path. */ 041 public static final DocPath empty = new DocPath(""); 042 043 /** The empty path. */ 044 public static final DocPath parent = new DocPath(".."); 045 046 /** 047 * Creates a path from a string. 048 * @param p the string 049 * @return the path 050 */ 051 public static DocPath create(String p) { 052 return (p == null) || p.isEmpty() ? empty : new DocPath(p); 053 } 054 055 protected DocPath(String p) { 056 path = (p.endsWith("/") ? p.substring(0, p.length() - 1) : p); 057 } 058 059 @Override 060 public boolean equals(Object other) { 061 return (other instanceof DocPath dp) && path.equals(dp.path); 062 } 063 064 @Override 065 public int hashCode() { 066 return path.hashCode(); 067 } 068 069 public DocPath basename() { 070 int sep = path.lastIndexOf("/"); 071 return (sep == -1) ? this : new DocPath(path.substring(sep + 1)); 072 } 073 074 public DocPath parent() { 075 int sep = path.lastIndexOf("/"); 076 return (sep == -1) ? empty : new DocPath(path.substring(0, sep)); 077 } 078 079 /** 080 * Returns the path formed by appending the specified string to the current path. 081 * @param p the string 082 * @return the path 083 */ 084 public DocPath resolve(String p) { 085 if (p == null || p.isEmpty()) 086 return this; 087 if (path.isEmpty()) 088 return new DocPath(p); 089 return new DocPath(path + "/" + p); 090 } 091 092 /** 093 * Returns the path by appending the specified path to the current path. 094 * @param p the path 095 * @return the path 096 */ 097 public DocPath resolve(DocPath p) { 098 if (p == null || p.isEmpty()) 099 return this; 100 if (path.isEmpty()) 101 return p; 102 return new DocPath(path + "/" + p.getPath()); 103 } 104 105 /** 106 * Return the inverse path for this path. 107 * For example, if the path is a/b/c, the inverse path is ../../.. 108 * @return the path 109 */ 110 public DocPath invert() { 111 return new DocPath(path.replaceAll("[^/]+", "..")); 112 } 113 114 /** 115 * Returns the path formed by eliminating empty components, 116 * '.' components, and redundant name/.. components. 117 * @return the path 118 */ 119 public DocPath normalize() { 120 return path.isEmpty() 121 ? this 122 : new DocPath(String.join("/", normalize(path))); 123 } 124 125 private static List<String> normalize(String path) { 126 return normalize(Arrays.asList(path.split("/"))); 127 } 128 129 private static List<String> normalize(List<String> parts) { 130 if (parts.stream() 131 .noneMatch(s -> s.isEmpty() || s.equals(".") || s.equals(".."))) { 132 return parts; 133 } 134 List<String> normalized = new ArrayList<>(); 135 for (String part : parts) { 136 switch (part) { 137 case "": 138 case ".": 139 break; 140 case "..": 141 int n = normalized.size(); 142 if (n > 0 && !normalized.get(n - 1).equals("..")) { 143 normalized.remove(n - 1); 144 } else { 145 normalized.add(part); 146 } 147 break; 148 default: 149 normalized.add(part); 150 } 151 } 152 return normalized; 153 } 154 155 /** 156 * Normalize and relativize a path against this path, 157 * assuming that this path is for a file (not a directory), 158 * in which the other path will appear. 159 * 160 * @param other the path to be relativized. 161 * @return the simplified path 162 */ 163 public DocPath relativize(DocPath other) { 164 if (other == null || other.path.isEmpty()) { 165 return this; 166 } 167 168 if (path.isEmpty()) { 169 return other; 170 } 171 172 List<String> originParts = normalize(path); 173 int sep = path.lastIndexOf("/"); 174 List<String> destParts = sep == -1 175 ? normalize(other.path) 176 : normalize(path.substring(0, sep + 1) + other.path); 177 int common = 0; 178 while (common < originParts.size() 179 && common < destParts.size() 180 && originParts.get(common).equals(destParts.get(common))) { 181 common++; 182 } 183 184 List<String> newParts; 185 if (common == originParts.size()) { 186 newParts = destParts.subList(common, destParts.size()); 187 } else { 188 newParts = new ArrayList<>(); 189 newParts.addAll( 190 Collections.nCopies(originParts.size() - common - 1, "..")); 191 newParts.addAll(destParts.subList(common, destParts.size())); 192 } 193 return new DocPath(String.join("/", newParts)); 194 } 195 196 /** 197 * Return true if this path is empty. 198 * @return true if this path is empty 199 */ 200 public boolean isEmpty() { 201 return path.isEmpty(); 202 } 203 204 /** 205 * Creates a DocLink formed from this path and a fragment identifier. 206 * @param fragment the fragment 207 * @return the link 208 */ 209 public DocLink fragment(String fragment) { 210 return new DocLink(path, fragment); 211 } 212 213 /** 214 * Returns this path as a string. 215 * @return the path 216 */ 217 // This is provided instead of using toString() to help catch 218 // unintended use of toString() in string concatenation sequences. 219 public String getPath() { 220 return path; 221 } 222}