// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
//
// 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 com.google.collide.client.util;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.shared.util.JsonCollections;
import com.google.collide.shared.util.StringUtils;
import com.google.common.base.Preconditions;
/**
* Utility class for dealing with File paths on the client.
*
* <p>This class is immutable.
*
* TODO: We may need the equivalent of this on the server as well. If we do,
* might want to drop the use of native collections and put it in shared.
*/
public class PathUtil implements Comparable<PathUtil> {
public static final String SEP = "/";
public static final PathUtil EMPTY_PATH = new PathUtil("");
public static final PathUtil WORKSPACE_ROOT = new PathUtil(SEP);
/**
* Creates a PathUtil composed of the components of <code>first</code>
* followed by the components of <code>second>.
*/
public static PathUtil concatenate(PathUtil first, PathUtil second) {
JsonArray<String> components = first.pathComponentsList.copy();
components.addAll(second.pathComponentsList);
return new PathUtil(components);
}
/**
* Creates a PathUtil that has all the components of the supplied from
* PathUtil, excluding some number of path components from the end.
*/
public static PathUtil createExcludingLastN(PathUtil from, int componentsToExclude) {
JsonArray<String> result = JsonCollections.createArray();
for (int i = 0, n = from.getPathComponentsCount() - componentsToExclude; i < n; ++i) {
result.add(from.getPathComponent(i));
}
return new PathUtil(result);
}
/**
* Creates a PathUtil that has all the components of the supplied from
* PathUtil, excluding some number of path components from the beginning.
*/
public static PathUtil createExcludingFirstN(PathUtil from, int componentsToExclude) {
JsonArray<String> result = JsonCollections.createArray();
for (int i = componentsToExclude, n = from.getPathComponentsCount(); i < n; ++i) {
result.add(from.getPathComponent(i));
}
return new PathUtil(result);
}
public static PathUtil createFromPathComponents(JsonArray<String> pathComponentsList) {
return new PathUtil(pathComponentsList);
}
private final JsonArray<String> pathComponentsList;
private PathUtil(JsonArray<String> pathComponentsList) {
this.pathComponentsList = pathComponentsList;
}
public PathUtil(String path) {
pathComponentsList = JsonCollections.createArray();
if (path != null && !path.isEmpty()) {
JsonArray<String> pieces = StringUtils.split(path, SEP);
for (int i = 0, n = pieces.size(); i < n; i++) {
String piece = pieces.get(i);
/*
* Ignore empty string components or "." which stands for current directory
*/
if (!StringUtils.isNullOrEmpty(piece) && !piece.equals(".")) {
pathComponentsList.add(piece);
}
}
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof PathUtil) {
PathUtil other = (PathUtil) obj;
return getPathString().equals(other.getPathString());
}
return false;
}
@Override
public int hashCode() {
return getPathString().hashCode();
}
public String getBaseName() {
int numComponents = getPathComponentsCount();
String baseName = "";
if (numComponents > 0) {
baseName = getPathComponent(numComponents - 1);
}
return baseName;
}
/**
* @return the String representation of the path. Path Strings, except for the
* EMPTY_PATH, always start with a leading "/", implying that the path
* is relative to the workspace root.
*/
public String getPathString() {
if (this == EMPTY_PATH || pathComponentsList.size() == 0) {
return "";
}
return SEP + pathComponentsList.join(SEP);
}
public int getPathComponentsCount() {
return pathComponentsList.size();
}
public String getPathComponent(int index) {
return pathComponentsList.get(index);
}
/**
* Returns whether the given {@code path} is a child of this path (or is
* the same as this path). For example, a path "/tmp" contains "/tmp" and
* it also contains "/tmp/something".
*/
public boolean containsPath(PathUtil path) {
Preconditions.checkNotNull(path, "Containing path must not be null");
JsonArray<String> otherPathComponents = path.pathComponentsList;
if (otherPathComponents.size() < pathComponentsList.size()) {
// The path given has less components than ours, it cannot be a child
return false;
}
for (int i = 0; i < pathComponentsList.size(); i++) {
if (!pathComponentsList.get(i).equals(otherPathComponents.get(i))) {
return false;
}
}
return true;
}
/**
* Returns a new path util that is relative to the given parent path. For this to work parent must
* contain the current path. i.e. if the path is /tmp/alex.txt and you pass in a path of /tmp then
* you will get /alex.txt.
*
* @return null if parent is invalid
*/
public PathUtil makeRelativeToParent(PathUtil parent) {
Preconditions.checkNotNull(parent, "Parent path cannot be null");
JsonArray<String> parentPathComponents = parent.pathComponentsList;
if (parentPathComponents.size() > pathComponentsList.size()) {
// The path given has the same or more components, it can't be a parent
return null;
}
for (int i = 0; i < parentPathComponents.size(); i++) {
// this means that this is not our parent i.e. /a/b/t.txt vs /a/c/
if (!parentPathComponents.get(i).equals(pathComponentsList.get(i))) {
return null;
}
}
return new PathUtil(pathComponentsList.slice(
parentPathComponents.size(), pathComponentsList.size()));
}
/**
* Delegates to {@link #getPathString()} to return the string representation
* of this object.
*/
@Override
public String toString() {
return getPathString();
}
/**
* Compares two {@link PathUtil}s lexicographically on each path component.
*/
@Override
public int compareTo(PathUtil that) {
JsonArray<String> components1 = this.pathComponentsList;
JsonArray<String> components2 = that.pathComponentsList;
// First compare path components except the last one (file's name).
for (int i = 0; i + 1 < components1.size() && i + 1 < components2.size(); ++i) {
int result = components1.get(i).compareTo(components2.get(i));
if (result != 0) {
return result;
}
}
// Sub-folders go after the parent folder.
int lengthDiff = components1.size() - components2.size();
if (lengthDiff != 0) {
return lengthDiff;
}
int lastComponent = components1.size() - 1;
if (lastComponent < 0) {
return 0;
}
// Finally, compare the file names.
return components1.get(lastComponent).compareTo(components2.get(lastComponent));
}
/**
* @return file extension or {@code null} if file has no extension
*/
public String getFileExtension() {
return getFileExtension(getPathString());
}
/**
* Returns the file extension of a given file path.
*
* @param filePath the file path
* @return file extension or {@code null} if file has no extension
*/
public static String getFileExtension(String filePath) {
int lastSlashPos = filePath.lastIndexOf('/');
int lastDotPos = filePath.lastIndexOf('.');
// If (lastSlashPos > lastDotPos) then dot is somewhere in parent directory and file has
// no extension.
if (lastDotPos < 0 || lastSlashPos > lastDotPos) {
return null;
}
return filePath.substring(lastDotPos + 1);
}
/**
* Builder for {@link PathUtil}.
*/
public static class Builder {
private final JsonArray<String> pathComponentsList;
public Builder() {
pathComponentsList = JsonCollections.createArray();
}
public Builder addPathComponent(String component) {
pathComponentsList.add(component);
return this;
}
public Builder addPathComponents(JsonArray<String> components) {
pathComponentsList.addAll(components);
return this;
}
public Builder addPath(PathUtil path) {
pathComponentsList.addAll(path.pathComponentsList);
return this;
}
public PathUtil build() {
return new PathUtil(pathComponentsList);
}
}
}