/*
* Copyright 2014 the original author or authors.
*
* 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 org.gradle.internal.resource;
import com.google.common.base.Objects;
import org.gradle.internal.UncheckedException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* An immutable resource name. Resources are arranged in a hierarchy. Names may be relative, or absolute with some opaque root resource.
*/
public class ExternalResourceName {
private final String encodedRoot;
private final String path;
public ExternalResourceName(URI uri) {
if (uri.getPath() == null) {
throw new IllegalArgumentException(String.format("Cannot create resource name from non-hierarchical URI '%s'.", uri.toString()));
}
this.encodedRoot = encodeRoot(uri);
this.path = uri.getPath();
}
public ExternalResourceName(String path) {
encodedRoot = null;
this.path = path;
}
private ExternalResourceName(String encodedRoot, String path) {
this.encodedRoot = encodedRoot;
this.path = path;
}
public ExternalResourceName(URI parent, String path) {
if (parent.getPath() == null) {
throw new IllegalArgumentException(String.format("Cannot create resource name from non-hierarchical URI '%s'.", parent.toString()));
}
String newPath;
if (path.startsWith("/")) {
path = path.substring(1);
}
if (path.length() == 0) {
newPath = parent.getPath();
} else if (parent.getPath().endsWith("/")) {
newPath = parent.getPath() + path;
} else {
newPath = parent.getPath() + "/" + path;
}
this.encodedRoot = encodeRoot(parent);
this.path = newPath;
}
private String encodeRoot(URI uri) {
StringBuilder builder = new StringBuilder();
if (uri.getScheme() != null) {
builder.append(uri.getScheme());
builder.append(":");
if(uri.getScheme().equals("file")) {
if (uri.getPath().startsWith("//")) {
builder.append("//");
}
}
}
if (uri.getHost() != null) {
builder.append("//");
builder.append(uri.getHost());
}
if (uri.getPort() > 0) {
builder.append(":");
builder.append(uri.getPort());
}
return builder.toString();
}
public String getDisplayName() {
return getDecoded();
}
@Override
public String toString() {
return getDisplayName();
}
/**
* Returns a URI that represents this resource.
*/
public URI getUri() {
try {
if (encodedRoot == null) {
return new URI(encode(path, false));
}
return new URI(encodedRoot + encode(path, true));
} catch (URISyntaxException e) {
throw UncheckedException.throwAsUncheckedException(e);
}
}
private String encode(String path, boolean isPathSeg) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < path.length(); i++) {
char ch = path.charAt(i);
if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9') {
builder.append(ch);
} else if (ch == '/' || ch == '@' || isPathSeg && ch == ':' || ch == '.' || ch == '-' || ch == '_' || ch == '~'
|| ch == '!' || ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '+'
|| ch == ',' || ch == ';' || ch == '=') {
builder.append(ch);
} else {
if (ch <= 0x7F) {
escapeByte(ch, builder);
} else if (ch <= 0x7FF) {
escapeByte(0xC0 | (ch >> 6) & 0x1F, builder);
escapeByte(0x80 | ch & 0x3F, builder);
} else {
escapeByte(0xE0 | (ch >> 12) & 0x1F, builder);
escapeByte(0x80 | (ch >> 6) & 0x3F, builder);
escapeByte(0x80 | ch & 0x3F, builder);
}
}
}
return builder.toString();
}
private void escapeByte(int ch, StringBuilder builder) {
builder.append('%');
builder.append(Character.toUpperCase(Character.forDigit(ch >> 4 & 0xFF, 16)));
builder.append(Character.toUpperCase(Character.forDigit(ch & 0xF, 16)));
}
/**
* Returns the 'decoded' name, which is the opaque root + the path of the name.
*/
public String getDecoded() {
if (encodedRoot == null) {
return path;
}
return encodedRoot + path;
}
/**
* Returns the root name for this name.
*/
public ExternalResourceName getRoot() {
return new ExternalResourceName(encodedRoot, path.startsWith("/") ? "/" : "");
}
/**
* Returns the path for this resource. The '/' character is used to separate the elements of the path.
*/
public String getPath() {
return path;
}
/**
* Resolves the given path relative to this name. The path can be a relative path or an absolute path. The '/' character is used to separate the elements of the path.
*/
public ExternalResourceName resolve(String path) {
String newPath;
if (path.startsWith("/")) {
newPath = path;
} else if (this.path.endsWith("/") || this.path.length() == 0) {
newPath = this.path + path;
} else {
newPath = this.path + "/" + path;
}
return new ExternalResourceName(encodedRoot, newPath);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || !obj.getClass().equals(getClass())) {
return false;
}
ExternalResourceName other = (ExternalResourceName) obj;
return Objects.equal(encodedRoot, other.encodedRoot) && path.equals(other.path);
}
@Override
public int hashCode() {
return (encodedRoot == null ? 0 : encodedRoot.hashCode()) ^ path.hashCode();
}
}