/* * Copyright (C) 2014 Civilian Framework. * * Licensed under the Civilian License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.civilian-framework.org/license.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.civilian.resource; import java.io.PrintWriter; import java.io.Serializable; import org.civilian.util.Check; import org.civilian.util.StringUtil; /** * Path represents a path string. It uses the '/' character * to separate path segments.<br> * Wrapping a path string in * an object ensures that its string representation is normed * and operations on objects creates again a valid path.<br> * The root path is represented as "". * Any other path always starts with a "/" but never ends with a "/". * Therefore any concatenation of path strings would again result in a * valid path string.<br> * If you print a path object, you must ensure that the root path * is printed as "/". You can use {@link #print()} for that purpose. */ public class Path implements CharSequence, Serializable, Comparable<Path> { private static final long serialVersionUID = 5312565042029867855L; /** * A constant for the root path. */ public static final Path ROOT = new Path(null); private static final String ROOT_VALUE = ""; /** * Tests if the path starts with the given prefix and * either equals the path or ends at a segment boundary. */ public static boolean startsWith(String path, String prefix) { Check.notNull(path, "path"); if (path.startsWith(prefix)) { int prefixLen = prefix.length(); if ((path.length() == prefixLen) || ((path.length() > prefixLen) && (path.charAt(prefixLen) == '/'))) return true; } return false; } /** * Norms a string which represents a path with segments * separated by '/'. * A trailing '/' is removed from the path and a leading * '/' is added to any non-root path, whereas the root path * is represented as the string "". */ public static String norm(String path) { if ((path == null) || (path.length() == 0) || path.equals("/")) return ROOT_VALUE; else { path = StringUtil.cutRight(path, "/"); path = StringUtil.haveLeft(path, "/"); return !path.equals("/") ? path : ROOT_VALUE; } } /** * Creates a new Path from the path string. */ public Path(String value) { value_ = norm(value); } /** * Creates a new Path from an already normed string. */ private Path(Void dummy, String value) { value_ = value; } /** * Returns the path string. * If this path is the root path, then "" is returned. */ public String getValue() { return value_; } /** * Returns a printable version of the path. * If this path is the root path, "/" is returned. */ public String print() { return isRoot() ? "/" : value_; } /** * Prints this path appended by the subPath * @param out a PrintWriter * @param subPath a subpath which may or may not conform to the path formatting conventions of path. */ public void print(PrintWriter out, String subPath) { if ((subPath == null) || (subPath.length() == 0) || subPath.equals("/")) out.print(print()); else if (subPath.charAt(0) == '/') { if (!isRoot()) out.print(value_); out.print(subPath); } else { out.print(print()); if (!isRoot()) out.print('/'); out.print(subPath); } } /** * Returns the length of the path string. */ @Override public int length() { return value_.length(); } /** * Returns the character at the given index. * Implements the CharSequence interface. */ @Override public char charAt(int index) { return value_.charAt(index); } /** * Returns a character subsequence from the path string. * Implements the CharSequence interface. */ @Override public CharSequence subSequence(int start, int end) { return value_.subSequence(start, end); } /** * Returns if this path is the root path. */ public boolean isRoot() { return value_.length() == 0; } /** * Returns a path which corresponds to this path + the given path. */ public Path add(Path path) { if ((path == null) || path.isRoot()) return this; else if (isRoot()) return path; else return new Path(null, value_ + path.toString()); } /** * Returns a path which corresponds to this path + the given path. */ public Path add(String path) { String normed = norm(path); if (normed == ROOT_VALUE) return this; else if (isRoot()) return new Path(null, normed); else return new Path(null, value_ + normed); } /** * Adds the path to the StringBuilder. */ public void addTo(StringBuilder s) { int length = s.length(); if (isRoot()) { if (length == 0) s.append('/'); } else { if ((length == 0) || (s.charAt(length - 1) != '/')) s.append(value_); else s.append(value_, 1, value_.length()); } } /** * Tests if the path starts with the prefix and * either equals the path or ends at a segment boundary. */ public boolean startsWith(String prefix) { return startsWith(value_, prefix); } /** * Tests if the path starts with the other path. */ public boolean startsWith(Path path) { Check.notNull(path, "path"); return startsWith(path.value_); } /** * Tests if this path starts with the path. * If true a new path with the start path removed * is returned else null is returned. */ public Path cutStart(Path path) { Check.notNull(path, "path"); if (path.isRoot()) return this; else if (!startsWith(path)) return null; else if (length() > path.length()) return new Path(null, value_.substring(path.length())); else return Path.ROOT; } /** * Returns the extension of the last segment in the path. * @return null if the path has no extension or the part after first '.' in the last segment * E.g. If the path is "/user/view.en.html" then "en.html" is returned */ public String getExtension() { int pSlash = value_.lastIndexOf('/'); int pExt = value_.indexOf('.', pSlash + 1); return pExt < 0 ? null : value_.substring(pExt + 1); } /** * Compares the path values. */ @Override public int compareTo(Path other) { return value_.compareTo(other.value_); } /** * Returns true iif the other object is a Path and * has the same path value. */ @Override public boolean equals(Object other) { return (other instanceof Path) && ((Path)other).value_.equals(value_); } /** * Returns the hash code of the path value. */ @Override public int hashCode() { return value_.hashCode(); } /** * Returns the path string. */ @Override public String toString() { return value_; } private String value_; }