/*
* Copyright 2003-2016 JetBrains s.r.o.
*
* 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 jetbrains.mps.vfs.path;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.annotations.Immutable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Universal path work either with usual files and folders and also with archives (jar/zip)
* Archives might be inside other archives /foo/bar/a.jar!/b.jar!/abc.xml
* Assuming no escape characters are passed to the constructor.
*
* Created by apyshkin on 6/17/16.
*/
@Immutable
public class UniPath extends AbstractPath {
@NotNull private final CommonPath myPath;// path to file (might be an archive as well)
@NotNull private final List<CommonPath> myArchivePaths = new ArrayList<>(); // always independent and relative!!!
private static String[] splitArchive(String path) {
String[] paths = path.split(ARCHIVE_SEPARATOR);
if (path.endsWith(ARCHIVE_SEPARATOR)) {
String[] result = Arrays.copyOf(paths, paths.length + 1);
result[paths.length] = "";
return result;
}
return paths;
}
/**
* @param path might contain {@link #ARCHIVE_SEPARATOR} symbolS (!)
*/
private UniPath(@NotNull String path) {
String[] archiveStrings = splitArchive(path);
if (archiveStrings.length == 0) {
myPath = CommonPath.fromString(path);
return;
}
myPath = CommonPath.fromString(archiveStrings[0]);
int index = 0;
while (++index < archiveStrings.length) {
CommonPath archivePart = CommonPath.fromString(trim(archiveStrings[index]));
myArchivePaths.add(archivePart.toIndependentPath());
}
}
private UniPath(@NotNull CommonPath path, @Nullable CommonPath... pathsInArchive) {
myPath = path.copy();
if (pathsInArchive != null) {
for (CommonPath commonPath : pathsInArchive) {
assert commonPath.isRelative();
myArchivePaths.add(commonPath.toIndependentPath());
}
}
}
private static String trim(String path) {
for (int i = 0; i < path.length(); ++i) {
if (path.charAt(i) != UNIX_SEPARATOR_CHAR && path.charAt(i) != WIN_SEPARATOR_CHAR) {
return path.substring(i);
}
}
return path;
}
/**
* the path within the archive might be system-dependent -- it will be converted to the system-independent path automatically
*/
public static UniPath fromString(@NotNull String path) {
validate(path);
return new UniPath(path);
}
private static void validate(String path) {
if (path.isEmpty()) {
return;
}
if (path.startsWith(ARCHIVE_SEPARATOR)) {
path = path.substring(ARCHIVE_SEPARATOR.length());
}
for (String part : path.split(ARCHIVE_SEPARATOR)) {
if (part.isEmpty()) {
throw new IllegalArgumentException("Path `" + path + "' contains two archive separators one after another.");
}
}
}
/**
* the path is pathToFile + ARCHIVE_SEPARATOR + pathsInArchive[0] + ARCHIVE_SEPARATOR + pathsInArchive[1] + ...
* if it is archive or just pathToFile in the case it is not
* the paths within the archive might be system-dependent -- it will be converted to the system-independent path automatically
*/
public static UniPath fromParts(CommonPath pathToFile, @Nullable CommonPath... pathsInArchive) {
validate(pathsInArchive);
return new UniPath(pathToFile, pathsInArchive);
}
private static void validate(CommonPath... pathsInArchive) {
for (CommonPath path : pathsInArchive) {
if (!path.isRelative()) {
throw new IllegalArgumentException("Given path within the archive must be relative `" + path);
}
}
}
private static UniPath fromParts(CommonPath pathToFile, @Nullable List<CommonPath> pathsInArchive) {
if (pathsInArchive == null) {
return new UniPath(pathToFile, null);
}
return UniPath.fromParts(pathToFile, pathsInArchive.toArray(new CommonPath[pathsInArchive.size()]));
}
public boolean isJar() {
return getFileName().endsWith(DOT_JAR);
}
public boolean isZip() {
return getFileName().endsWith(DOT_ZIP);
}
public boolean isInArchive() {
return myArchivePaths.size() > 0;
}
/**
* Points exactly to jar
*/
public boolean isArchive() {
return myArchivePaths.size() == 0 && myPath.endsWith(DOT_JAR);
}
@Override
@NotNull
public UniPath toIndependentPath() {
return convertPath(CommonPath::toIndependentPath, UNIX_SEPARATOR_CHAR);
}
@Override
@NotNull
public UniPath toSystemPath() {
return convertPath(CommonPath::toSystemPath, SYSTEM_SEPARATOR_CHAR);
}
@Override
public boolean endsWith(@NotNull String other) {
return endsWith(UniPath.fromString(other));
}
@Override
public boolean startsWith(@NotNull String other) {
return startsWith(UniPath.fromString(other));
}
@NotNull
@Override
public Path relativize(@NotNull Path other) {
return null;
}
@NotNull
@Override
public UniPath toAbsolute() {
if (!isRelative()) {
return copy();
} else {
return new UniPath(myPath.toAbsolute());
}
}
@NotNull
@Override
public UniPath toNormal() {
return UniPath.fromParts(myPath.toNormal(),
myArchivePaths.stream().map(CommonPath::toNormal).collect(Collectors.toList()));
}
@NotNull
@Override
public UniPath toCanonical() throws IOException {
List<CommonPath> newArchivePaths = new ArrayList<>();
for (CommonPath path : myArchivePaths) {
newArchivePaths.add(path.toCanonical());
}
return UniPath.fromParts(myPath.toCanonical(), newArchivePaths);
}
@NotNull
@Override
public Path resolve(@NotNull Path other) {
return null;
}
@NotNull
@Override
public Path resolve(@NotNull String other) {
return null;
}
private UniPath convertPath(Function<CommonPath, CommonPath> converter, char correctSeparator) {
if (myPath.getSeparatorChar() == correctSeparator) {
return this.copy();
}
List<CommonPath> newArchivePaths = new ArrayList<>(myArchivePaths.size());
newArchivePaths.addAll(myArchivePaths.stream().map(converter).collect(Collectors.toList()));
return UniPath.fromParts(converter.apply(myPath), newArchivePaths);
}
@Override
public boolean isRelative() {
return myPath.isRelative();
}
@Override
public char getSeparator() {
return myPath.getSeparatorChar();
}
@Nullable
@Override
public Path getParent() {
if (myArchivePaths.isEmpty()) {
return myPath.getParent();
}
int lastIndex = myArchivePaths.size() - 1;
CommonPath lastParent = myArchivePaths.get(lastIndex).getParent();
List<CommonPath> newArchivePaths = myArchivePaths.subList(0, lastIndex);
if (lastParent != null) {
newArchivePaths.add(lastParent);
return UniPath.fromParts(myPath, newArchivePaths);
} else {
return UniPath.fromParts(myPath, newArchivePaths);
}
}
@NotNull
@Override
public List<String> getNames() {
List<String> result = new ArrayList<>(myPath.getNames());
for (CommonPath path : myArchivePaths) {
result.addAll(path.getNames());
}
return result;
}
@Nullable
@Override
public Path getRoot() {
return myPath.getRoot();
}
@Override
public String toString() {
String res = myPath.toString();
for (Path path : myArchivePaths) {
res += ARCHIVE_SEPARATOR + path.toString();
}
return res;
}
@NotNull
@Override
public UniPath copy() {
return UniPath.fromParts(myPath, myArchivePaths);
}
// public Path toRealPath(LinkOption... options) throws IOException {
// return null;
// }
//
// public File toFile() {
// return null;
// }
//
// public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
// fixme
// throw new NotImplementedException();
// }
//
// public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
// fixme
// throw new NotImplementedException();
// }
}