/*
* Copyright 2000-2013 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 com.intellij.util;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xmlb.XmlSerializer;
import com.intellij.util.xmlb.annotations.Attribute;
import com.intellij.util.xmlb.annotations.Tag;
import org.jdom.Element;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author traff
*/
public class PathMappingSettings extends AbstractPathMapper implements Cloneable {
@NotNull
private List<PathMapping> myPathMappings;
public PathMappingSettings(@Nullable final List<PathMapping> pathMappings) {
myPathMappings = create(pathMappings);
}
@NotNull
private static List<PathMapping> create(@Nullable final List<PathMapping> mappings) {
List<PathMapping> result = ContainerUtil.newArrayList();
if (mappings != null) {
for (PathMapping m : mappings) {
if (m != null && !isAnyEmpty(m.getLocalRoot(), m.getRemoteRoot())) {
result.add(m);
}
}
}
return result;
}
public PathMappingSettings() {
myPathMappings = ContainerUtil.newArrayList();
}
@NotNull
static String norm(@NotNull String path) {
return FileUtil.toSystemIndependentName(path);
}
@NotNull
private static String normLocal(@NotNull String path) {
if (SystemInfo.isWindows) {
path = path.toLowerCase();
}
return norm(path);
}
@Override
public boolean isEmpty() {
return myPathMappings.isEmpty();
}
/**
* @deprecated use {@code !isEmpty()} instead
* @see #isEmpty()
*/
@Deprecated
public boolean isUseMapping() {
return !isEmpty();
}
public static class BestMappingSelector {
private int myBestWeight = -1;
private PathMapping myBest = null;
public void consider(PathMapping mapping, int weight) {
if (weight > myBestWeight) {
myBestWeight = weight;
myBest = mapping;
}
}
@Nullable
public PathMapping get() {
return myBest;
}
}
@NotNull
@Override
public String convertToLocal(@NotNull String remotePath) {
String localPath = convertToLocal(remotePath, myPathMappings);
return localPath != null ? localPath : remotePath;
}
@NotNull
@Override
public String convertToRemote(@NotNull String localPath) {
String remotePath = convertToRemote(localPath, myPathMappings);
return remotePath != null ? remotePath : localPath;
}
public void add(@NotNull PathMapping mapping) {
if (isAnyEmpty(mapping.getLocalRoot(), mapping.getRemoteRoot())) {
return;
}
myPathMappings.add(mapping);
}
public void addMapping(@Nullable String local, @Nullable String remote) {
PathMapping mapping = new PathMapping(local, remote);
add(mapping);
}
public void addMappingCheckUnique(@NotNull String local, @NotNull String remote) {
for (PathMapping mapping : myPathMappings) {
if (pathEquals(local, mapping.getLocalRoot()) && pathEquals(remote, mapping.getRemoteRoot())) {
return;
}
}
addMapping(local, remote);
}
private static boolean pathEquals(@NotNull String path1, @NotNull String path2) {
return norm(path1).equals(norm(path2));
}
@Override
@NotNull
protected final Collection<PathMapping> getAvailablePathMappings() {
return Collections.unmodifiableCollection(myPathMappings);
}
@NotNull
public List<PathMapping> getPathMappings() {
return myPathMappings;
}
public void setPathMappings(@Nullable final List<PathMapping> pathMappings) {
myPathMappings = create(pathMappings);
}
@NotNull
public static String mapToLocal(@NotNull String path, @Nullable String remoteRoot, @Nullable String localRoot) {
if (isAnyEmpty(localRoot, remoteRoot)) {
return path;
}
path = norm(path);
String remotePrefix = norm(remoteRoot);
if (canReplaceRemote(path, remotePrefix)) {
path = norm(localRoot) + path.substring(remotePrefix.length());
}
return path;
}
@Contract(value = "null, _ -> true; _, null -> true", pure = true)
public static boolean isAnyEmpty(@Nullable String localRoot, @Nullable String remoteRoot) {
return StringUtil.isEmpty(localRoot) || StringUtil.isEmpty(remoteRoot);
}
@Nullable
public static PathMappingSettings readExternal(@Nullable final Element element) {
if (element == null) {
return null;
}
final Element settingsElement = element.getChild(PathMappingSettings.class.getSimpleName());
if (settingsElement == null) {
return null;
}
return XmlSerializer.deserialize(settingsElement, PathMappingSettings.class);
}
public static void writeExternal(@Nullable final Element element, @Nullable final PathMappingSettings mappings) {
if (element == null || mappings == null || mappings.isEmpty()) {
return;
}
element.addContent(XmlSerializer.serialize(mappings));
}
public void addAll(@NotNull PathMappingSettings settings) {
myPathMappings.addAll(settings.getPathMappings());
}
public void addAll(@NotNull List<PathMapping> mappings) {
myPathMappings.addAll(mappings);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PathMappingSettings settings = (PathMappingSettings)o;
if (!myPathMappings.equals(settings.myPathMappings)) return false;
return true;
}
@Override
public int hashCode() {
return myPathMappings.hashCode();
}
@Tag("mapping")
public static class PathMapping {
private String myLocalRoot;
private String myRemoteRoot;
public PathMapping() {
}
public PathMapping(@Nullable String localRoot, @Nullable String remoteRoot) {
myLocalRoot = normalize(localRoot);
myRemoteRoot = normalize(remoteRoot);
}
@Nullable
private static String normalize(@Nullable String path) {
if (path == null) {
return null;
}
else {
return trimSlash(FileUtil.toSystemIndependentName(path));
}
}
@Attribute("local-root")
public String getLocalRoot() {
return myLocalRoot;
}
@Attribute("remote-root")
public String getRemoteRoot() {
return myRemoteRoot;
}
public int getLocalLen() {
return myLocalRoot != null ? myLocalRoot.length() : -1;
}
public int getRemoteLen() {
return myRemoteRoot != null ? myRemoteRoot.length() : -1;
}
public void setLocalRoot(@Nullable String localRoot) {
myLocalRoot = normalize(localRoot);
}
public void setRemoteRoot(@Nullable String remoteRoot) {
myRemoteRoot = normalize(remoteRoot);
}
@NotNull
public String mapToLocal(@NotNull String path) {
return PathMappingSettings.mapToLocal(path, myRemoteRoot, myLocalRoot);
}
public boolean canReplaceLocal(@NotNull String path) {
if (isEmpty()) {
return false;
}
String localPrefix = normLocal(myLocalRoot);
return !localPrefix.isEmpty() && normLocal(path).startsWith(localPrefix);
}
public String mapToRemote(@NotNull String path) {
if (isEmpty()) {
return path;
}
if (canReplaceLocal(path)) {
return norm(myRemoteRoot) + norm(path).substring(normLocal(myLocalRoot).length());
}
return path;
}
private boolean isEmpty() {
return isAnyEmpty(myLocalRoot, myRemoteRoot);
}
private static String trimSlash(@NotNull String s) {
if (s.equals("/")) {
return s;
}
return StringUtil.trimEnd(s, "/");
}
public boolean canReplaceRemote(@NotNull String path) {
if (isEmpty()) {
return false;
}
return PathMappingSettings.canReplaceRemote(path, myRemoteRoot);
}
@Override
public PathMapping clone() {
return new PathMapping(myLocalRoot, myRemoteRoot);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PathMapping mapping = (PathMapping)o;
if (myLocalRoot != null ? !myLocalRoot.equals(mapping.myLocalRoot) : mapping.myLocalRoot != null) return false;
if (myRemoteRoot != null ? !myRemoteRoot.equals(mapping.myRemoteRoot) : mapping.myRemoteRoot != null) return false;
return true;
}
@Override
public int hashCode() {
int result = myLocalRoot != null ? myLocalRoot.hashCode() : 0;
result = 31 * result + (myRemoteRoot != null ? myRemoteRoot.hashCode() : 0);
return result;
}
}
private static boolean canReplaceRemote(@NotNull String path, @NotNull String remotePrefix) {
path = norm(path);
remotePrefix = norm(remotePrefix);
return path.startsWith(remotePrefix) &&
(path.length() == remotePrefix.length() || remotePrefix.endsWith("/") || path.substring(remotePrefix.length()).startsWith("/"));
}
}