blob: 100230967cf77737bc9c10afb7d967cd22feee5c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2014 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support
*******************************************************************************/
package org.eclipse.core.internal.resources;
import java.net.URI;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.internal.utils.FileUtil;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
/**
* This class implements the various path, URI, and name validation methods
* in the workspace API
*/
public class LocationValidator {
private final Workspace workspace;
public LocationValidator(Workspace workspace) {
this.workspace = workspace;
}
/**
* Returns a string representation of a URI suitable for displaying to an end user.
*/
private String toString(URI uri) {
try {
return EFS.getStore(uri).toString();
} catch (CoreException e) {
//there is no store defined, so the best we can do is the URI toString.
return uri.toString();
}
}
/**
* Check that the location is absolute
*/
private IStatus validateAbsolute(URI location, boolean error) {
if (!location.isAbsolute()) {
String message;
String schemeSpecificPart = location.getSchemeSpecificPart();
if (schemeSpecificPart == null || schemeSpecificPart.isEmpty()) {
message = Messages.links_noPath;
} else {
IPath pathPart = new Path(schemeSpecificPart);
if (pathPart.segmentCount() > 0)
message = NLS.bind(Messages.pathvar_undefined, location.toString(), pathPart.segment(0));
else
message = Messages.links_noPath;
}
int code = error ? IResourceStatus.VARIABLE_NOT_DEFINED : IResourceStatus.VARIABLE_NOT_DEFINED_WARNING;
return new ResourceStatus(code, null, message);
}
return Status.OK_STATUS;
}
/* (non-Javadoc)
* @see IWorkspace#validateLinkLocation(IResource, IPath)
*/
public IStatus validateLinkLocation(IResource resource, IPath unresolvedLocation) {
IPath location = resource.getPathVariableManager().resolvePath(unresolvedLocation);
if (location.isEmpty())
return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), Messages.links_noPath);
//check that the location is absolute
if (!location.isAbsolute()) {
//we know there is at least one segment, because of previous isEmpty check
String message = NLS.bind(Messages.pathvar_undefined, location.toOSString(), location.segment(0));
return new ResourceStatus(IResourceStatus.VARIABLE_NOT_DEFINED_WARNING, resource.getFullPath(), message);
}
//if the location doesn't have a device, see if the OS will assign one
if (location.getDevice() == null)
location = new Path(location.toFile().getAbsolutePath());
return validateLinkLocationURI(resource, URIUtil.toURI(location));
}
public IStatus validateLinkLocationURI(IResource resource, URI unresolvedLocation) {
String schemeSpecificPart = unresolvedLocation.getSchemeSpecificPart();
if (schemeSpecificPart == null || schemeSpecificPart.isEmpty()) {
return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), Messages.links_noPath);
}
String message;
//check if resource linking is disabled
if (ResourcesPlugin.getPlugin().getPluginPreferences().getBoolean(ResourcesPlugin.PREF_DISABLE_LINKING)) {
message = NLS.bind(Messages.links_workspaceVeto, resource.getName());
return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
}
//check that the resource is the right type
int type = resource.getType();
if (type != IResource.FOLDER && type != IResource.FILE) {
message = NLS.bind(Messages.links_notFileFolder, resource.getName());
return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
}
IContainer parent = resource.getParent();
if (!parent.isAccessible()) {
message = NLS.bind(Messages.links_parentNotAccessible, resource.getFullPath());
return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
}
URI location = resource.getPathVariableManager().resolveURI(unresolvedLocation);
//check nature veto
String[] natureIds = ((Project) resource.getProject()).internalGetDescription().getNatureIds();
IStatus result = workspace.getNatureManager().validateLinkCreation(natureIds);
if (!result.isOK())
return result;
//check team provider veto
if (resource.getType() == IResource.FILE)
result = workspace.getTeamHook().validateCreateLink((IFile) resource, IResource.NONE, location);
else
result = workspace.getTeamHook().validateCreateLink((IFolder) resource, IResource.NONE, location);
if (!result.isOK())
return result;
//check the standard path name restrictions
result = validateSegments(location);
if (!result.isOK())
return result;
//check if the location is based on an undefined variable
result = validateAbsolute(location, false);
if (!result.isOK())
return result;
// test if the given location overlaps the platform metadata location
URI testLocation = workspace.getMetaArea().getLocation().toFile().toURI();
if (FileUtil.isOverlapping(location, testLocation)) {
message = NLS.bind(Messages.links_invalidLocation, toString(location));
return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
}
//test if the given path overlaps the location of the given project
testLocation = resource.getProject().getLocationURI();
if (testLocation != null && FileUtil.isPrefixOf(location, testLocation)) {
message = NLS.bind(Messages.links_locationOverlapsProject, toString(location));
return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
}
//warnings (all errors must be checked before all warnings)
// Iterate over each known project and ensure that the location does not
// conflict with any project locations or linked resource locations
for (IProject project : workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN)) {
// since we are iterating over the project in the workspace, we
// know that they have been created before and must have a description
IProjectDescription desc = ((Project) project).internalGetDescription();
testLocation = desc.getLocationURI();
if (testLocation != null && FileUtil.isOverlapping(location, testLocation)) {
message = NLS.bind(Messages.links_overlappingResource, toString(location));
return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, resource.getFullPath(), message);
}
//iterate over linked resources and check for overlap
if (!project.isOpen())
continue;
IResource[] children = null;
try {
children = project.members();
} catch (CoreException e) {
//ignore projects that cannot be accessed
}
if (children == null)
continue;
for (IResource child : children) {
if (child.isLinked()) {
testLocation = child.getLocationURI();
if (testLocation != null && FileUtil.isOverlapping(location, testLocation)) {
message = NLS.bind(Messages.links_overlappingResource, toString(location));
return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, resource.getFullPath(), message);
}
}
}
}
return Status.OK_STATUS;
}
/* (non-Javadoc)
* @see IWorkspace#validateName(String, int)
*/
public IStatus validateName(String segment, int type) {
String message;
/* segment must not be null */
if (segment == null) {
message = Messages.resources_nameNull;
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
// cannot be an empty string
if (segment.length() == 0) {
message = Messages.resources_nameEmpty;
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* test invalid characters */
char[] chars = OS.INVALID_RESOURCE_CHARACTERS;
for (char c : chars)
if (segment.indexOf(c) != -1) {
message = NLS.bind(Messages.resources_invalidCharInName, String.valueOf(c), segment);
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* test invalid OS names */
if (!OS.isNameValid(segment)) {
message = NLS.bind(Messages.resources_invalidName, segment);
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
return Status.OK_STATUS;
}
/**
* Validates that the given workspace path is valid for the given type. If
* <code>lastSegmentOnly</code> is true, it is assumed that all segments except
* the last one have previously been validated. This is an optimization for validating
* a leaf resource when it is known that the parent exists (and thus its parent path
* must already be valid).
*/
public IStatus validatePath(IPath path, int type, boolean lastSegmentOnly) {
String message;
/* path must not be null */
if (path == null) {
message = Messages.resources_pathNull;
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* path must not have a device separator */
if (path.getDevice() != null) {
message = NLS.bind(Messages.resources_invalidCharInPath, String.valueOf(IPath.DEVICE_SEPARATOR), path);
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* path must not be the root path */
if (path.isRoot()) {
message = Messages.resources_invalidRoot;
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* path must be absolute */
if (!path.isAbsolute()) {
message = NLS.bind(Messages.resources_mustBeAbsolute, path);
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* validate segments */
int numberOfSegments = path.segmentCount();
if ((type & IResource.PROJECT) != 0) {
if (numberOfSegments == ICoreConstants.PROJECT_SEGMENT_LENGTH) {
return validateName(path.segment(0), IResource.PROJECT);
} else if (type == IResource.PROJECT) {
message = NLS.bind(Messages.resources_projectPath, path);
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
}
if ((type & (IResource.FILE | IResource.FOLDER)) != 0) {
if (numberOfSegments < ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH) {
message = NLS.bind(Messages.resources_resourcePath, path);
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
int fileFolderType = type &= ~IResource.PROJECT;
int segmentCount = path.segmentCount();
if (lastSegmentOnly)
return validateName(path.segment(segmentCount - 1), fileFolderType);
IStatus status = validateName(path.segment(0), IResource.PROJECT);
if (!status.isOK())
return status;
// ignore first segment (the project)
for (int i = 1; i < segmentCount; i++) {
status = validateName(path.segment(i), fileFolderType);
if (!status.isOK())
return status;
}
return Status.OK_STATUS;
}
message = NLS.bind(Messages.resources_invalidPath, path);
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* (non-Javadoc)
* @see IWorkspace#validatePath(String, int)
*/
public IStatus validatePath(String path, int type) {
/* path must not be null */
if (path == null) {
String message = Messages.resources_pathNull;
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
return validatePath(Path.fromOSString(path), type, false);
}
public IStatus validateProjectLocation(IProject context, IPath unresolvedLocation) {
if (unresolvedLocation == null)
return validateProjectLocationURI(context, null);
IPath location;
if (context != null)
location = context.getPathVariableManager().resolvePath(unresolvedLocation);
else
location = workspace.getPathVariableManager().resolvePath(unresolvedLocation);
//check that the location is absolute
if (!location.isAbsolute()) {
String message;
if (location.segmentCount() > 0)
message = NLS.bind(Messages.pathvar_undefined, location.toString(), location.segment(0));
else
message = Messages.links_noPath;
return new ResourceStatus(IResourceStatus.VARIABLE_NOT_DEFINED, null, message);
}
return validateProjectLocationURI(context, URIUtil.toURI(location));
}
/* (non-Javadoc)
* @see IWorkspace#validateProjectLocationURI(IProject, URI)
*/
public IStatus validateProjectLocationURI(IProject context, URI unresolvedLocation) {
if (context == null && unresolvedLocation == null)
throw new IllegalArgumentException("Either a project or a location must be provided"); //$NON-NLS-1$
// Checks if the new location overlaps the workspace metadata location
boolean isMetadataLocation = false;
if (unresolvedLocation != null) {
if (URIUtil.equals(unresolvedLocation, URIUtil.toURI(Platform.getLocation().addTrailingSeparator().append(LocalMetaArea.F_METADATA)))) {
isMetadataLocation = true;
}
} else if (context != null && context.getName().equals(LocalMetaArea.F_METADATA)) {
isMetadataLocation = true;
}
String message;
if (isMetadataLocation) {
message = NLS.bind(Messages.resources_invalidPath, toString(URIUtil.toURI(Platform.getLocation().addTrailingSeparator().append(LocalMetaArea.F_METADATA))));
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
// the default is ok for all other projects
if (unresolvedLocation == null)
return Status.OK_STATUS;
URI location;
if (context != null)
location = context.getPathVariableManager().resolveURI(unresolvedLocation);
else
location = workspace.getPathVariableManager().resolveURI(unresolvedLocation);
//check the standard path name restrictions
IStatus result = validateSegments(location);
if (!result.isOK())
return result;
result = validateAbsolute(location, true);
if (!result.isOK())
return result;
//check that the URI has a legal scheme
try {
EFS.getFileSystem(location.getScheme());
} catch (CoreException e) {
return e.getStatus();
}
//overlaps with default location can only occur with file URIs
if (location.getScheme().equals(EFS.SCHEME_FILE)) {
IPath locationPath = URIUtil.toPath(location);
// test if the given location overlaps the default default location
IPath defaultDefaultLocation = workspace.getRoot().getLocation();
if (FileUtil.isPrefixOf(locationPath, defaultDefaultLocation)) {
message = NLS.bind(Messages.resources_overlapWorkspace, toString(location), defaultDefaultLocation.toOSString());
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
// Test if the given location is the default location for any potential project except
// the one being created.
IPath parentPath = locationPath.removeLastSegments(1);
if (FileUtil.isPrefixOf(parentPath, defaultDefaultLocation) && FileUtil.isPrefixOf(defaultDefaultLocation, parentPath) && (context == null || !locationPath.equals(defaultDefaultLocation.append(context.getName())))) {
message = NLS.bind(Messages.resources_overlapProject, toString(location), locationPath.lastSegment());
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
}
// Iterate over each known project and ensure that the location does not
// conflict with any of their already defined locations.
IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
for (IProject project : projects) {
URI testLocation = project.getLocationURI();
if (context != null && project.equals(context)) {
//tolerate locations being the same if this is the project being tested
if (URIUtil.equals(testLocation, location))
continue;
//a project cannot be moved inside of its current location
if (!FileUtil.isPrefixOf(testLocation, location))
continue;
} else if (!URIUtil.equals(testLocation, location)) {
// a project cannot have the same location as another existing project
continue;
}
//in all other cases there is illegal overlap
message = NLS.bind(Messages.resources_overlapProject, toString(location), project.getName());
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
//if this project exists and has linked resources, the project location cannot overlap
//the locations of any linked resources in that project
if (context != null && context.exists() && context.isOpen()) {
IResource[] children = null;
try {
children = context.members();
} catch (CoreException e) {
//ignore projects that cannot be accessed
}
if (children != null) {
for (IResource child : children) {
if (child.isLinked()) {
URI testLocation = child.getLocationURI();
if (testLocation != null && FileUtil.isPrefixOf(testLocation, location)) {
message = NLS.bind(Messages.links_locationOverlapsLink, toString(location));
return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, context.getFullPath(), message);
}
}
}
}
}
return Status.OK_STATUS;
}
/**
* Validates the standard path name restrictions on the segments of the provided URI.
* @param location The URI to validate
* @return A status indicating if the segments of the provided URI are valid
*/
private IStatus validateSegments(URI location) {
if (EFS.SCHEME_FILE.equals(location.getScheme())) {
IPath pathPart = new Path(location.getSchemeSpecificPart());
int segmentCount = pathPart.segmentCount();
for (int i = 0; i < segmentCount; i++) {
IStatus result = validateName(pathPart.segment(i), IResource.PROJECT);
if (!result.isOK())
return result;
}
}
return Status.OK_STATUS;
}
}