blob: a15741a53f03f1af3189ef2b1b074995ace2fe30 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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.android.sched.vfs;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterators;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
/**
* A path relative to a VFS. Two instances of {@link VPath} can be compared regardless of the
* separator used as initialization.
*/
public final class VPath implements Cloneable {
@Nonnull
public static final VPath ROOT = new VPath("", '/');
private static final char INTERNAL_SEPARATOR = '/';
private static final VPathFragment INTERNAL_SEPARATOR_FRAGMENT =
new VPathFragment(String.valueOf(INTERNAL_SEPARATOR), INTERNAL_SEPARATOR);
@Nonnull
private static final Splitter splitter = Splitter.on(INTERNAL_SEPARATOR).omitEmptyStrings();
@Nonnull
ArrayList<VPathFragment> pathFragments;
/**
* Creates an instance of VFS-relative path. The {@link CharSequence} is evaluated lazily at each
* usage. The separator must not be contained twice consecutively, nor be at the start or the
* beginning of the path.
* @param path the relative path
* @param separator the separator used as file separator in the path
*/
public VPath(@Nonnull CharSequence path, char separator) {
pathFragments = new ArrayList<VPathFragment>(1);
if (path.length() > 0) {
VPathFragment pe = new VPathFragment(path, separator);
assert pe.isValidPath();
pathFragments.add(pe);
}
}
private VPath(ArrayList<VPathFragment> pathFragments) {
this.pathFragments = pathFragments;
}
/**
* Inserts a path before an existing path. The resulting path is evaluated lazily at each usage.
* There is an implicit separator between the prepended path and the existing path.
* @param path the path to insert before the existing path
* @return the current path
*/
public VPath prependPath(@Nonnull VPath path) {
assert !path.isRoot();
if (!this.isRoot()) {
pathFragments.add(0, INTERNAL_SEPARATOR_FRAGMENT);
}
pathFragments.addAll(0, path.getPathFragments());
return this;
}
/**
* Inserts a path after an existing path. The resulting path is evaluated lazily at each usage.
* There is an implicit separator between the existing path and the appended path.
* @param path the path to insert after the existing path
* @return the current path
*/
public VPath appendPath(@Nonnull VPath path) {
assert !path.isRoot();
if (!this.isRoot()) {
pathFragments.add(INTERNAL_SEPARATOR_FRAGMENT);
}
pathFragments.addAll(path.getPathFragments());
return this;
}
/**
* Adds a suffix to the existing path. The resulting path is evaluated lazily at each usage.
* It may be identical or different from the previous path.
* No implicit separator will be added between the suffix and the previous path.
* @param suffix the suffix to add to the path
* @return the current path
*/
public VPath addSuffix(@Nonnull CharSequence suffix) {
VPathFragment pe = new VPathFragment(suffix, INTERNAL_SEPARATOR);
assert pe.isValidSuffix();
pathFragments.add(pe);
return this;
}
@Override
public VPath clone() {
// no need to clone path fragments, they should be immutable
@SuppressWarnings("unchecked")
ArrayList<VPathFragment> clonedList = (ArrayList<VPathFragment>) pathFragments.clone();
return new VPath(clonedList);
}
/**
* Returns the VFS-relative path as an {@link Iterable} of path elements.
*/
@Nonnull
public Iterable<String> split() {
return splitter.split(getInternalPath());
}
/**
* Returns the VFS-relative path using the given separator.
* @param separator the file separator wanted
* @return the relative path
*/
@Nonnull
public String getPathAsString(char separator) {
StringBuilder buffer = new StringBuilder();
for (VPathFragment pathElement : pathFragments) {
buffer.append(pathElement.getPathElementAsString(separator));
}
return buffer.toString();
}
@Override
public final boolean equals(Object obj) {
if (!(obj instanceof VPath)) {
return false;
}
return getInternalPath().equals(((VPath) obj).getInternalPath());
}
@Override
public final int hashCode() {
return getInternalPath().hashCode();
}
@Nonnull
private List<VPathFragment> getPathFragments() {
return pathFragments;
}
@Nonnull
private String getInternalPath() {
return getPathAsString(INTERNAL_SEPARATOR);
}
public boolean isRoot() {
return pathFragments.isEmpty();
}
/**
* A portion of path that should be immutable.
*/
private static class VPathFragment {
@Nonnull
private final CharSequence path;
private final char separator;
public VPathFragment(@Nonnull CharSequence path, char separator) {
this.path = path;
this.separator = separator;
}
@Nonnull
public String getPathElementAsString(char separator) {
return path.toString().replace(this.separator, separator);
}
private boolean isValidPath() {
String toString = path.toString();
String stringSeparator = String.valueOf(separator);
String doubleSeparator = stringSeparator + separator;
if (toString.contains(doubleSeparator)) {
throw new AssertionError("Path: " + toString);
}
if (toString.startsWith(stringSeparator)) {
throw new AssertionError("Path: " + toString);
}
if (toString.endsWith(stringSeparator)) {
throw new AssertionError("Path: " + toString);
}
return true;
}
private boolean isValidSuffix() {
return path.toString().indexOf(separator) == -1;
}
}
/**
* Returns the last element of a VFS-relative path as a {@link String}.
*/
@Nonnull
public String getLastName() {
return Iterators.getLast(split().iterator(), "");
}
@Override
public String toString() {
return "_" + getInternalPath() + "_";
}
}