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 }