/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.repository;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.UserError;
/**
* A location in a repository. Format:
*
* //Repository/path/to/object
*
* All constructors throw IllegalArugmentExceptions if names are malformed, contain illegal
* characters etc.
*
* @author Simon Fischer, Adrian Wilke
*
*/
public class RepositoryLocation {
public static final char SEPARATOR = '/';
public static final String REPOSITORY_PREFIX = "//";
public static final String[] BLACKLISTED_STRINGS = new String[] { "/", "\\", ":", "<", ">", "*", "?", "\"", "|" };
private String repositoryName;
private String[] path;
private RepositoryAccessor accessor;
/**
* Constructs a RepositoryLocation from a string of the form //Repository/path/to/object.
*/
public RepositoryLocation(String absoluteLocation) throws MalformedRepositoryLocationException {
if (isAbsolute(absoluteLocation)) {
initializeFromAbsoluteLocation(absoluteLocation);
} else {
repositoryName = null;
initializeAbsolutePath(absoluteLocation);
}
}
/**
* Creates a RepositoryLocation for a given repository and a set of path components which will
* be concatenated by a /.
*
* @throws MalformedRepositoryLocationException
*/
public RepositoryLocation(String repositoryName, String[] pathComponents) throws MalformedRepositoryLocationException {
// actually check submitted parameters
if (repositoryName == null || repositoryName.isEmpty()) {
throw new MalformedRepositoryLocationException("repositoryName must not contain null or empty!");
}
if (pathComponents == null) {
throw new MalformedRepositoryLocationException("pathComponents must not be null!");
}
for (String pathComp : pathComponents) {
if (pathComp == null || pathComp.isEmpty()) {
throw new MalformedRepositoryLocationException("path must not contain null or empty strings!");
}
}
this.repositoryName = repositoryName;
this.path = pathComponents;
}
/**
* Appends a child entry to a given parent location. Child can be composed of subcomponents
* separated by /. Dots ("..") will resolve to the parent folder.
*
* @throws MalformedRepositoryLocationException
*/
public RepositoryLocation(RepositoryLocation parent, String childName) throws MalformedRepositoryLocationException {
this.accessor = parent.accessor;
if (isAbsolute(childName)) {
initializeFromAbsoluteLocation(childName);
} else if (childName.startsWith("" + SEPARATOR)) {
this.repositoryName = parent.repositoryName;
initializeAbsolutePath(childName);
} else {
this.repositoryName = parent.repositoryName;
String[] components = childName.split("" + SEPARATOR);
// skip empty path components
LinkedList<String> newComponents = new LinkedList<>();
for (String pathComp : parent.path) {
if (pathComp != null && !pathComp.isEmpty()) {
newComponents.add(pathComp);
}
}
for (String component : components) {
if (".".equals(component)) {
// do nothing
} else if ("..".equals(component)) {
if (!newComponents.isEmpty()) {
newComponents.removeLast();
} else {
throw new IllegalArgumentException("Cannot resolve relative location '" + childName
+ "' with respect to '" + parent + "': Too many '..'");
}
} else {
newComponents.add(component);
}
}
this.path = newComponents.toArray(new String[newComponents.size()]);
}
}
private void initializeFromAbsoluteLocation(String absoluteLocation) throws MalformedRepositoryLocationException {
if (!isAbsolute(absoluteLocation)) {
throw new MalformedRepositoryLocationException(
"Repository location '"
+ absoluteLocation
+ "' is not absolute! Absolute repository locations look for example like this: '//Repository/path/to/object'.");
}
String tmp = absoluteLocation.substring(2);
int nextSlash = tmp.indexOf(RepositoryLocation.SEPARATOR);
if (nextSlash != -1) {
repositoryName = tmp.substring(0, nextSlash);
} else {
throw new MalformedRepositoryLocationException("Malformed repositoy location '" + absoluteLocation
+ "': path component missing.");
}
initializeAbsolutePath(tmp.substring(nextSlash));
}
private void initializeAbsolutePath(String path) throws MalformedRepositoryLocationException {
if (!path.startsWith("" + SEPARATOR)) {
throw new MalformedRepositoryLocationException("No absolute path: '" + path
+ "'. Absolute paths look e.g. like this: '/path/to/object'.");
}
path = path.substring(1);
this.path = path.split("" + SEPARATOR);
}
/** Returns the absolute location of this RepoositoryLocation. */
public String getAbsoluteLocation() {
StringBuilder b = new StringBuilder();
b.append(REPOSITORY_PREFIX);
b.append(repositoryName);
for (String component : path) {
b.append(SEPARATOR);
b.append(component);
}
return b.toString();
}
/**
* Returns the repository associated with this location.
*
* @throws RepositoryException
*/
public Repository getRepository() throws RepositoryException {
return RepositoryManager.getInstance(getAccessor()).getRepository(repositoryName);
}
/** Returns the name of the repository associated with this location. */
public String getRepositoryName() {
return repositoryName;
}
/** Returns the path within the repository. */
public String getPath() {
StringBuilder b = new StringBuilder();
for (String component : path) {
b.append(SEPARATOR);
b.append(component);
}
return b.toString();
}
/**
* Locates the corresponding entry in the repository. It returns null if it doesn't exist yet.
* An exception is thrown if this location is invalid.
*
* @throws RepositoryException
* If repository can not be found or entry can not be located.
* */
public Entry locateEntry() throws RepositoryException {
Repository repos = getRepository();
if (repos != null) {
Entry entry = repos.locate(getPath());
return entry;
} else {
return null;
}
}
/** Returns the last path component. */
public String getName() {
if (path.length > 0) {
return path[path.length - 1];
} else {
return null;
}
}
public RepositoryLocation parent() {
if (path.length == 0) {
// we are at a root
return null;
} else {
String[] pathCopy = new String[path.length - 1];
System.arraycopy(path, 0, pathCopy, 0, path.length - 1);
RepositoryLocation parent;
try {
parent = new RepositoryLocation(this.repositoryName, pathCopy);
} catch (MalformedRepositoryLocationException e) {
throw new RuntimeException(e);
}
parent.setAccessor(accessor);
return parent;
}
}
@Override
public String toString() {
return getAbsoluteLocation();
}
/**
* Assume absoluteLocation == "//MyRepos/foo/bar/object" and
* relativeToFolder=//MyRepos/foo/baz/, then this method will return ../bar/object.
*/
public String makeRelative(RepositoryLocation relativeToFolder) {
// can only do something if repositories match.
if (!this.repositoryName.equals(relativeToFolder.repositoryName)) {
return getAbsoluteLocation();
}
int min = Math.min(this.path.length, relativeToFolder.path.length);
// find common prefix
int i = 0;
while (i < min && this.path[i].equals(relativeToFolder.path[i])) {
i++;
}
StringBuilder result = new StringBuilder();
// add one ../ for each excess component in relativeComponent which we have to leave
for (int j = i; j < relativeToFolder.path.length; j++) {
result.append("..");
result.append(RepositoryLocation.SEPARATOR);
}
// add components from each excess absoluteComponent
for (int j = i; j < this.path.length; j++) {
result.append(this.path[j]);
if (j < this.path.length - 1) {
result.append(RepositoryLocation.SEPARATOR);
}
}
return result.toString();
}
public static boolean isAbsolute(String loc) {
return loc.startsWith(RepositoryLocation.REPOSITORY_PREFIX);
}
/**
* Creates this folder and its parents.
*
* @throws RepositoryException
*/
public Folder createFoldersRecursively() throws RepositoryException {
Entry entry = locateEntry();
if (entry == null) {
Folder folder = parent().createFoldersRecursively();
Folder child = folder.createFolder(getName());
return child;
} else {
if (entry instanceof Folder) {
return (Folder) entry;
} else {
throw new RepositoryException(toString() + " is not a folder.");
}
}
}
@Override
public boolean equals(Object obj) {
return obj != null && this.toString().equals(obj.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
public void setAccessor(RepositoryAccessor accessor) {
this.accessor = accessor;
}
public RepositoryAccessor getAccessor() {
return accessor;
}
/**
* Checks if the given name is valid as a repository entry. Checks against a blacklist of
* characters.
*
* @param name
* @return
*/
public static boolean isNameValid(String name) {
if (name == null) {
throw new IllegalArgumentException("name must not be null!");
}
if ("".equals(name.trim())) {
return false;
}
for (String forbiddenString : BLACKLISTED_STRINGS) {
if (name.contains(forbiddenString)) {
return false;
}
}
return true;
}
/**
* Returns the sub{@link String} in the given name which is invalid or <code>null</code> if
* there are no illegal characters.
*
* @return
*/
public static String getIllegalCharacterInName(String name) {
if (name == null || "".equals(name.trim())) {
return null;
}
for (String forbiddenString : BLACKLISTED_STRINGS) {
if (name.contains(forbiddenString)) {
return forbiddenString;
}
}
return null;
}
/**
* Removes locations from list, which are already included in others.
*
* If there are any problems requesting a repository, the input is returned.
*
* Example: [/1/2/3, /1, /1/2] becomes [/1]
*/
public static List<RepositoryLocation> removeIntersectedLocations(List<RepositoryLocation> repositoryLocations) {
List<RepositoryLocation> locations = new LinkedList<>(repositoryLocations);
try {
Set<RepositoryLocation> removeSet = new HashSet<>();
for (RepositoryLocation locationA : locations) {
for (RepositoryLocation locationB : locations) {
if (!locationA.equals(locationB) && locationA.getRepository().equals(locationB.getRepository())) {
String pathA = locationA.getPath();
String pathB = locationB.getPath();
if (pathA.startsWith(pathB)
&& pathA.substring(pathB.length(), pathB.length() + 1).equals(String.valueOf(SEPARATOR))) {
removeSet.add(locationA);
}
}
}
}
locations.removeAll(removeSet);
} catch (RepositoryException e) {
return repositoryLocations;
}
return locations;
}
/**
* Returns the repository location for the provided path and operator. In case it is relative
* the operators process is used base path.
*
* @param loc
* the relative or absolute repository location path as String
* @param op
* the operator for which should be used as base path in case the location is
* relative
* @return the repository location for the specified path
* @throws UserError
* in case the location is malformed
*/
public static RepositoryLocation getRepositoryLocation(String loc, Operator op) throws UserError {
com.rapidminer.Process process = op.getProcess();
if (process != null) {
RepositoryLocation result;
try {
result = process.resolveRepositoryLocation(loc);
} catch (MalformedRepositoryLocationException e) {
throw new UserError(op, e, 319, e.getMessage());
}
result.setAccessor(process.getRepositoryAccessor());
return result;
} else {
if (RepositoryLocation.isAbsolute(loc)) {
RepositoryLocation result;
try {
result = new RepositoryLocation(loc);
} catch (MalformedRepositoryLocationException e) {
throw new UserError(op, e, 319, e.getMessage());
}
return result;
} else {
throw new UserError(op, 320, loc);
}
}
}
}