/**
* Copyright 2005-2016 Red Hat, Inc.
*
* Red Hat 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 io.fabric8.tooling.migration.profile;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.ProviderMismatchException;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class FabricProfilePath implements Path {
private final FabricProfileFileSystem fileSystem;
private final byte[] path;
private volatile int[] offsets;
private volatile int hash = 0;
private volatile byte[] resolved = null;
public FabricProfilePath(FabricProfileFileSystem fileSystem, byte[] path) {
this(fileSystem, path, false);
}
public FabricProfilePath(FabricProfileFileSystem fileSystem, byte[] path, boolean normalized) {
this.fileSystem = fileSystem;
if (normalized) {
this.path = path;
} else {
this.path = normalize(path);
}
}
public FabricProfileFileSystem getFileSystem() {
return fileSystem;
}
public boolean isAbsolute() {
return (path.length > 0) && (path[0] == '/');
}
public FabricProfilePath getRoot() {
if (isAbsolute()) {
return new FabricProfilePath(fileSystem, new byte[] { this.path[0] });
}
return null;
}
public FabricProfilePath getFileName() {
initOffsets();
int nbOffsets = offsets.length;
if (nbOffsets == 0) {
return null;
}
if (nbOffsets == 1 && path[0] != '/') {
return this;
}
int offset = offsets[nbOffsets - 1];
int length = path.length - offset;
byte[] path = new byte[length];
System.arraycopy(this.path, offset, path, 0, length);
return new FabricProfilePath(fileSystem, path);
}
public FabricProfilePath getParent() {
initOffsets();
int nbOffsets = offsets.length;
if (nbOffsets == 0) {
return null;
}
int length = offsets[nbOffsets - 1] - 1;
if (length <= 0) {
return getRoot();
}
byte[] path = new byte[length];
System.arraycopy(this.path, 0, path, 0, length);
return new FabricProfilePath(fileSystem, path);
}
public int getNameCount() {
initOffsets();
return offsets.length;
}
public FabricProfilePath getName(int index) {
initOffsets();
if (index < 0 || index >= offsets.length) {
throw new IllegalArgumentException();
}
int offset = this.offsets[index];
int length;
if (index == offsets.length - 1) {
length = path.length - offset;
} else {
length = offsets[index + 1] - offset - 1;
}
byte[] path = new byte[length];
System.arraycopy(this.path, offset, path, 0, length);
return new FabricProfilePath(fileSystem, path);
}
public FabricProfilePath subpath(int beginIndex, int endIndex) {
initOffsets();
if ((beginIndex < 0) || (beginIndex >= this.offsets.length) || (endIndex > this.offsets.length) || (beginIndex >= endIndex)) {
throw new IllegalArgumentException();
}
int offset = this.offsets[beginIndex];
int length;
if (endIndex == this.offsets.length) {
length = this.path.length - offset;
} else {
length = this.offsets[endIndex] - offset - 1;
}
byte[] path = new byte[length];
System.arraycopy(this.path, offset, path, 0, length);
return new FabricProfilePath(fileSystem, path);
}
public boolean startsWith(Path other) {
FabricProfilePath p1 = this;
FabricProfilePath p2 = checkPath(other);
if (p1.isAbsolute() != p2.isAbsolute() || p1.path.length < p2.path.length) {
return false;
}
int length = p2.path.length;
for (int idx = 0; idx < length; idx++) {
if (p1.path[idx] != p2.path[idx]) {
return false;
}
}
return p1.path.length == p2.path.length
|| p2.path[length - 1] == '/'
|| p1.path[length] == '/';
}
public boolean startsWith(String other) {
return startsWith(getFileSystem().getPath(other));
}
public boolean endsWith(Path other) {
FabricProfilePath p1 = this;
FabricProfilePath p2 = checkPath(other);
int i1 = p1.path.length - 1;
if (i1 > 0 && p1.path[i1] == '/') {
i1--;
}
int i2 = p2.path.length - 1;
if (i2 > 0 && p2.path[i2] == '/') {
i2--;
}
if (i2 == -1) {
return i1 == -1;
}
if ((p2.isAbsolute() && (!isAbsolute() || i2 != i1)) || (i1 < i2)) {
return false;
}
for (; i2 >= 0; i1--)
{
if (p2.path[i2] != p1.path[i1]) {
return false;
}
i2--;
}
return (p2.path[i2 + 1] == '/') || (i1 == -1) || (p1.path[i1] == '/');
}
public boolean endsWith(String other) {
return endsWith(getFileSystem().getPath(other));
}
public FabricProfilePath normalize() {
byte[] p = getResolved();
if (p == this.path) {
return this;
}
return new FabricProfilePath(fileSystem, p, true);
}
public FabricProfilePath resolve(Path other) {
FabricProfilePath p1 = this;
FabricProfilePath p2 = checkPath(other);
if (p2.isAbsolute()) {
return p2;
}
byte[] result;
if (p1.path[p1.path.length - 1] == '/')
{
result = new byte[p1.path.length + p2.path.length];
System.arraycopy(p1.path, 0, result, 0, p1.path.length);
System.arraycopy(p2.path, 0, result, p1.path.length, p2.path.length);
}
else
{
result = new byte[p1.path.length + 1 + p2.path.length];
System.arraycopy(p1.path, 0, result, 0, p1.path.length);
result[p1.path.length] = '/';
System.arraycopy(p2.path, 0, result, p1.path.length + 1, p2.path.length);
}
return new FabricProfilePath(fileSystem, result);
}
public FabricProfilePath resolve(String other) {
return resolve(getFileSystem().getPath(other));
}
public Path resolveSibling(Path other) {
if (other == null) {
throw new NullPointerException();
}
FabricProfilePath parent = getParent();
return parent == null ? other : parent.resolve(other);
}
public Path resolveSibling(String other) {
return resolveSibling(getFileSystem().getPath(other));
}
public FabricProfilePath relativize(Path other) {
FabricProfilePath p1 = this;
FabricProfilePath p2 = checkPath(other);
if (p2.equals(p1)) {
return new FabricProfilePath(fileSystem, new byte[0], true);
}
if (p1.isAbsolute() != p2.isAbsolute()) {
throw new IllegalArgumentException();
}
// Check how many segments are common
int nbNames1 = p1.getNameCount();
int nbNames2 = p2.getNameCount();
int l = Math.min(nbNames1, nbNames2);
int nbCommon = 0;
while (nbCommon < l && equalsNameAt(p1, p2, nbCommon)) {
nbCommon++;
}
int nbUp = nbNames1 - nbCommon;
// Compute the resulting length
int length = nbUp * 3 - 1;
if (nbCommon < nbNames2) {
length += p2.path.length - p2.offsets[nbCommon] + 1;
}
// Compute result
byte[] result = new byte[length];
int idx = 0;
while (nbUp-- > 0) {
result[idx++] = '.';
result[idx++] = '.';
if (idx < length) {
result[idx++] = '/';
}
}
// Copy remaining segments
if (nbCommon < nbNames2) {
System.arraycopy(p2.path, p2.offsets[nbCommon], result, idx, p2.path.length - p2.offsets[nbCommon]);
}
return new FabricProfilePath(fileSystem, result);
}
public URI toUri() {
// TODO
return null;
}
public FabricProfilePath toAbsolutePath() {
if (isAbsolute()) {
return this;
}
byte[] result = new byte[path.length + 1];
result[0] = '/';
System.arraycopy(path, 0, result, 1, path.length);
return new FabricProfilePath(fileSystem, result, true);
}
public FabricProfilePath toRealPath(LinkOption... options) throws IOException {
FabricProfilePath absolute = new FabricProfilePath(fileSystem, getResolvedPath()).toAbsolutePath();
fileSystem.provider().checkAccess(absolute);
return absolute;
}
public File toFile() {
throw new UnsupportedOperationException();
}
public WatchKey register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers) throws IOException {
throw new UnsupportedOperationException();
}
public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) throws IOException {
throw new UnsupportedOperationException();
}
public Iterator<Path> iterator() {
return new Iterator<Path>() {
private int index = 0;
public boolean hasNext() {
return index < getNameCount();
}
public Path next() {
if (index < getNameCount()) {
FabricProfilePath name = getName(index);
index++;
return name;
}
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public int compareTo(Path paramPath) {
FabricProfilePath p1 = this;
FabricProfilePath p2 = checkPath(paramPath);
byte[] a1 = p1.path;
byte[] a2 = p2.path;
int l1 = a1.length;
int l2 = a2.length;
for (int i = 0, l = Math.min(l1, l2); i < l; i++) {
int b1 = a1[i] & 0xFF;
int b2 = a2[i] & 0xFF;
if (b1 != b2) {
return b1 - b2;
}
}
return l1 - l2;
}
private FabricProfilePath checkPath(Path paramPath) {
if (paramPath == null) {
throw new NullPointerException();
}
if (!(paramPath instanceof FabricProfilePath)) {
throw new ProviderMismatchException();
}
return (FabricProfilePath) paramPath;
}
@Override
public int hashCode() {
int h = hash;
if (h == 0) {
h = hash = Arrays.hashCode(path);
}
return h;
}
@Override
public boolean equals(Object obj) {
return obj instanceof FabricProfilePath
&& ((FabricProfilePath) obj).fileSystem == fileSystem
&& compareTo((FabricProfilePath) obj) == 0;
}
@Override
public String toString() {
return new String(path, StandardCharsets.UTF_8);
}
private void initOffsets() {
if (this.offsets == null) {
int count = 0;
int index = 0;
while (index < path.length) {
byte c = path[index++];
if (c != '/') {
count++;
while (index < path.length && path[index] != '/') {
index++;
}
}
}
int[] result = new int[count];
count = 0;
index = 0;
while (index < path.length) {
int m = path[index];
if (m == '/') {
index++;
} else {
result[count++] = index++;
while (index < path.length && path[index] != '/') {
index++;
}
}
}
synchronized (this) {
if (offsets == null) {
offsets = result;
}
}
}
}
byte[] getResolvedPath() {
byte[] r = resolved;
if (r == null) {
r = resolved = isAbsolute() ? getResolved() : toAbsolutePath().getResolvedPath();
}
return r;
}
private byte[] normalize(byte[] path)
{
if (path.length == 0) {
return path;
}
int i = 0;
for (int j = 0; j < path.length; j++)
{
int k = path[j];
if (k == '\\') {
return normalize(path, j);
}
if ((k == '/') && (i == '/')) {
return normalize(path, j - 1);
}
if (k == 0) {
throw new InvalidPathException(new String(path, StandardCharsets.UTF_8), "Path: nul character not allowed");
}
i = k;
}
return path;
}
private byte[] normalize(byte[] path, int index)
{
byte[] arrayOfByte = new byte[path.length];
int i = 0;
while (i < index)
{
arrayOfByte[i] = path[i];
i++;
}
int j = i;
int k = 0;
while (i < path.length)
{
int m = path[i++];
if (m == '\\') {
m = '/';
}
if ((m != '/') || (k != '/'))
{
if (m == 0) {
throw new InvalidPathException(new String(path, StandardCharsets.UTF_8), "Path: nul character not allowed");
}
arrayOfByte[j++] = (byte) m;
k = m;
}
}
if ((j > 1) && (arrayOfByte[j - 1] == '/')) {
j--;
}
return j == arrayOfByte.length ? arrayOfByte : Arrays.copyOf(arrayOfByte, j);
}
private byte[] getResolved() {
if (path.length == 0) {
return path;
}
for (byte c : path) {
if (c == '.') {
return doGetResolved(this);
}
}
return path;
}
private static byte[] doGetResolved(FabricProfilePath p) {
int nc = p.getNameCount();
byte[] path = p.path;
int[] offsets = p.offsets;
byte[] to = new byte[path.length];
int[] lastM = new int[nc];
int lastMOff = -1;
int m = 0;
for (int i = 0; i < nc; i++) {
int n = offsets[i];
int len = (i == offsets.length - 1) ? (path.length - n) : (offsets[i + 1] - n - 1);
if (len == 1 && path[n] == (byte) '.') {
if (m == 0 && path[0] == '/') // absolute path
to[m++] = '/';
continue;
}
if (len == 2 && path[n] == '.' && path[n + 1] == '.') {
if (lastMOff >= 0) {
m = lastM[lastMOff--]; // retreat
continue;
}
if (path[0] == '/') { // "/../xyz" skip
if (m == 0)
to[m++] = '/';
} else { // "../xyz" -> "../xyz"
if (m != 0 && to[m - 1] != '/')
to[m++] = '/';
while (len-- > 0)
to[m++] = path[n++];
}
continue;
}
if (m == 0 && path[0] == '/' || // absolute path
m != 0 && to[m - 1] != '/') { // not the first name
to[m++] = '/';
}
lastM[++lastMOff] = m;
while (len-- > 0)
to[m++] = path[n++];
}
if (m > 1 && to[m - 1] == '/')
m--;
return (m == to.length) ? to : Arrays.copyOf(to, m);
}
private static boolean equalsNameAt(FabricProfilePath p1, FabricProfilePath p2, int index)
{
int beg1 = p1.offsets[index];
int len1;
if (index == p1.offsets.length - 1) {
len1 = p1.path.length - beg1;
} else {
len1 = p1.offsets[index + 1] - beg1 - 1;
}
int beg2 = p2.offsets[index];
int len2;
if (index == p2.offsets.length - 1) {
len2 = p2.path.length - beg2;
} else {
len2 = p2.offsets[index + 1] - beg2 - 1;
}
if (len1 != len2) {
return false;
}
for (int n = 0; n < len1; n++) {
if (p1.path[beg1 + n] != p2.path[beg2 + n]) {
return false;
}
}
return true;
}
}