/* * 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; } }