blob: eb3c3f68026c7bad817bc92633aef3fb868121c8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.igfs;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.ignite.binary.BinaryObjectException;
import org.apache.ignite.binary.BinaryRawReader;
import org.apache.ignite.binary.BinaryRawWriter;
import org.apache.ignite.binary.BinaryReader;
import org.apache.ignite.binary.BinaryWriter;
import org.apache.ignite.binary.Binarylizable;
import org.apache.ignite.internal.util.io.GridFilenameUtils;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.jetbrains.annotations.Nullable;
/**
* {@code IGFS} path to file in the file system. For example, to get information about
* a file you would use the following code:
* <pre name="code" class="java">
* IgfsPath dirPath = new IgfsPath("/my/working/dir");
* IgfsPath filePath = new IgfsPath(dirPath, "file.txt");
*
* // Get metadata about file.
* IgfsFile file = igfs.info(filePath);
* </pre>
*/
public final class IgfsPath implements Comparable<IgfsPath>, Externalizable, Binarylizable {
/** */
private static final long serialVersionUID = 0L;
/** The directory separator character. */
private static final char SLASH_CHAR = '/';
/** The directory separator. */
public static final String SLASH = "/";
/** URI representing this path. Should never change after object creation or de-serialization. */
private String path;
/** Root path. */
public static final IgfsPath ROOT = new IgfsPath(SLASH);
/**
* Default constructor.
*/
public IgfsPath() {
path = SLASH;
}
/**
* Constructs a path from an URI
*
* @param uri URI to create path from.
*/
public IgfsPath(URI uri) {
A.notNull(uri, "uri");
path = normalizePath(uri.getPath());
}
/**
* Constructs a path from the URI string.
*
* @param path URI string.
*/
public IgfsPath(String path) {
A.ensure(!F.isEmpty(path), "'path' is null or empty");
this.path = normalizePath(path);
}
/**
* Resolve a child path against a parent path.
*
* @param parentPath Parent path.
* @param childPath Child path.
*/
public IgfsPath(IgfsPath parentPath, String childPath) {
A.notNull(parentPath, "parentPath");
String path = GridFilenameUtils.concat(parentPath.path, childPath);
if (F.isEmpty(path))
throw new IllegalArgumentException("Failed to parse path" +
" [parent=" + parentPath + ", childPath=" + childPath + ']');
this.path = normalizePath(path);
}
/**
* Initialize path with (1) not-null, (2) normalized, (3) absolute and (4) unix-format path component.
*
* @param path Path.
* @return Normalized path.
*/
private static String normalizePath(String path) {
assert path != null;
String normalizedPath = GridFilenameUtils.normalizeNoEndSeparator(path, true);
if (F.isEmpty(normalizedPath))
throw new IllegalArgumentException("Failed to normalize path: " + path);
if (!SLASH.equals(GridFilenameUtils.getPrefix(normalizedPath)))
throw new IllegalArgumentException("Path should be absolute: " + path);
assert !normalizedPath.isEmpty() : "Expects normalized path is not empty.";
assert normalizedPath.length() == 1 || !normalizedPath.endsWith(SLASH) :
"Expects normalized path is root or don't ends with '/' symbol.";
return normalizedPath;
}
/**
* Returns the final component of this path.
*
* @return The final component of this path.
*/
public String name() {
return GridFilenameUtils.getName(path);
}
/**
* Split full path on components.
*
* @return Path components.
*/
public List<String> components() {
String path = this.path;
assert path.length() >= 1 : "Path expected to be absolute: " + path;
// Path is short-living object, so we don't need to cache component's resolution result.
return path.length() == 1 ? Collections.<String>emptyList() : Arrays.asList(path.substring(1).split(SLASH));
}
/**
* Get components in array form.
*
* @return Components array.
*/
public String[] componentsArray() {
return path.length() == 1 ? new String[0] : path.substring(1).split(SLASH);
}
/**
* Returns the parent of a path or {@code null} if at root.
*
* @return The parent of a path or {@code null} if at root.
*/
@Nullable public IgfsPath parent() {
String path = this.path;
if (path.length() == 1)
return null; // Current path is root.
path = GridFilenameUtils.getFullPathNoEndSeparator(path);
return new IgfsPath(path);
}
/**
* Adds a suffix to the final name in the path.
*
* @param suffix Suffix.
* @return Path with suffix.
*/
public IgfsPath suffix(String suffix) {
A.ensure(!F.isEmpty(suffix), "'suffix' is null or empty.");
A.ensure(!suffix.contains(SLASH), "'suffix' contains file's separator '" + SLASH + "'");
return new IgfsPath(path + suffix);
}
/**
* Return the number of elements in this path.
*
* @return The number of elements in this path, zero depth means root directory.
*/
public int depth() {
final String path = this.path;
final int size = path.length();
assert size >= 1 && path.charAt(0) == SLASH_CHAR : "Expects absolute path: " + path;
if (size == 1)
return 0;
int depth = 1;
// Ignore the first character.
for (int i = 1; i < size; i++)
if (path.charAt(i) == SLASH_CHAR)
depth++;
return depth;
}
/**
* Checks whether this path is a sub-directory of argument.
*
* @param path Path to check.
* @return {@code True} if argument is same or a sub-directory of this object.
*/
public boolean isSubDirectoryOf(IgfsPath path) {
A.notNull(path, "path");
return this.path.startsWith(path.path.endsWith(SLASH) ? path.path : path.path + SLASH);
}
/** {@inheritDoc} */
@SuppressWarnings("NullableProblems")
@Override public int compareTo(IgfsPath o) {
return path.compareTo(o.path);
}
/** {@inheritDoc} */
@Override public void writeExternal(ObjectOutput out) throws IOException {
U.writeString(out, path);
}
/** {@inheritDoc} */
@Override public void readExternal(ObjectInput in) throws IOException {
path = U.readString(in);
}
/** {@inheritDoc} */
@Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
writeRawBinary(writer.rawWriter());
}
/** {@inheritDoc} */
@Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
readRawBinary(reader.rawReader());
}
/**
* Write raw binary.
*
* @param writer Raw writer.
* @throws BinaryObjectException If failed.
*/
public void writeRawBinary(BinaryRawWriter writer) throws BinaryObjectException {
writer.writeString(path);
}
/**
* Read raw binary.
*
* @param reader Raw reader.
* @throws BinaryObjectException If failed.
*/
public void readRawBinary(BinaryRawReader reader) throws BinaryObjectException {
path = reader.readString();
}
/** {@inheritDoc} */
@Override public int hashCode() {
return path.hashCode();
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
return o == this || o != null && getClass() == o.getClass() && path.equals(((IgfsPath)o).path);
}
/** {@inheritDoc} */
@Override public String toString() {
return path;
}
}