blob: 28dfda9bc5282376a8fdfb6ab8b9caad31c977b8 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ecommons.io;
import java.net.URI;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.variables.IStringVariable;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.osgi.util.NLS;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ecommons.io.internal.Messages;
import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils;
import org.eclipse.statet.ecommons.variables.core.VariableText2;
import org.eclipse.statet.ecommons.variables.core.VariableUtils;
import org.eclipse.statet.internal.ecommons.coreutils.CoreMiscellanyPlugin;
/**
* A configurable resource validator.
*
* Validates <code>String</code> (with variables) representing a local file path
* or a URI and file handles of type <code>IFileStore</code> and
* <code>IResource</code> (for Workspace resources).
*/
public class FileValidator implements IValidator<Object> {
private Object explicitObject;
private boolean inCheck= false;
private IResource workspaceResource;
private IFileStore fileStore;
private IStatus status;
private String resourceLabel;
private VariableText2 variableResolver;
private int onEmpty;
private int onNotExisting;
private int onExisting;
private VariableText2.Severities onVariableProblems;
private int onFile;
private int onDirectory;
private int onNotLocal;
private boolean ignoreRelative;
private IStringVariable relativePrefix;
private int relativeMax= -1;
private IPath relativePath;
private boolean requireWorkspace;
private boolean asWorkspacePath;
private Map<Pattern, Integer> onPattern;
private IValidator<? super IFileStore> fileStoreValidator;
private int currentMax;
/**
*
*/
public FileValidator() {
this.onNotExisting= IStatus.OK;
this.onExisting= IStatus.OK;
this.onEmpty= IStatus.ERROR;
this.onVariableProblems= VariableText2.Severities.RESOLVE;
this.onFile= IStatus.OK;
this.onDirectory= IStatus.OK;
this.onNotLocal= IStatus.ERROR;
this.ignoreRelative= false;
}
/**
* New validator initialized with specified default mode
* ({@link #setDefaultMode(boolean)})
*/
public FileValidator(final boolean existingResource) {
this();
setDefaultMode(existingResource);
}
public void setDefaultMode(final boolean existingResource) {
this.onNotExisting= (existingResource) ? IStatus.ERROR : IStatus.OK;
this.onExisting= (existingResource) ? IStatus.OK : IStatus.WARNING;
}
void checkVariable(final IStringVariable variable) {
}
public void setOnEmpty(final int severity) {
this.onEmpty= severity;
resetResolution();
}
public int getOnEmpty() {
return this.onEmpty;
}
public void setOnExisting(final int severity) {
this.onExisting= severity;
resetResolution();
}
public int getOnExisting() {
return this.onExisting;
}
public void setOnNotExisting(final int severity) {
this.onNotExisting= severity;
resetResolution();
}
public int getOnNotExisting() {
return this.onNotExisting;
}
public void setOnLateResolve(final int severity) {
if (severity != this.onVariableProblems.getUnresolved()) {
this.onVariableProblems= new VariableText2.Severities(IStatus.ERROR, severity);
}
resetResolution();
}
public int getOnLateResolve() {
return this.onVariableProblems.getUnresolved();
}
public void setOnFile(final int severity) {
this.onFile= severity;
resetResolution();
}
public int getOnFile() {
return this.onFile;
}
public void setOnDirectory(final int severity) {
this.onDirectory= severity;
resetResolution();
}
public int getOnDirectory() {
return this.onDirectory;
}
public void setOnNotLocal(final int severity) {
this.onNotLocal= severity;
resetResolution();
}
public int getOnNotLocal() {
return this.onNotLocal;
}
public void setIgnoreRelative(final boolean ignore) {
this.relativeMax= -1;
this.ignoreRelative= ignore;
resetResolution();
}
public void setRelative(final IStringVariable prefix, final int maxSeverity) {
this.relativePrefix= prefix;
this.relativeMax= maxSeverity;
this.ignoreRelative= false;
checkVariable(prefix);
resetResolution();
}
protected String getRelativePrefix() {
String prefix= null;
if (this.relativePrefix != null) {
try {
prefix= VariableUtils.getValue(this.relativePrefix);
}
catch (final CoreException e) {
}
}
if (prefix != null && !prefix.endsWith("/") && !prefix.endsWith("\\")) { //$NON-NLS-1$ //$NON-NLS-2$
prefix+= '/';
}
return prefix;
}
public void setRequireWorkspace(final boolean require, final boolean wsPath) {
this.requireWorkspace= require;
if (require) {
this.asWorkspacePath= wsPath;
}
resetResolution();
}
public void setVariableResolver(final VariableText2 variableResolver) {
this.variableResolver= variableResolver;
if (variableResolver.getExtraVariables() != null) {
for (final IStringVariable aVariable : variableResolver.getExtraVariables().values()) {
checkVariable(aVariable);
}
}
updateVariableResolution();
}
public VariableText2 getVariableResolver() {
return this.variableResolver;
}
public void updateVariableResolution() {
if (this.explicitObject instanceof String || this.relativePrefix != null) {
resetResolution();
}
}
/**
* @param pattern pattern
* @param severity at moment only OK_STATUS or -1
*/
public void setOnPattern(final Pattern pattern, final int severity) {
if (this.onPattern == null) {
this.onPattern= new LinkedHashMap<>();
}
if (severity >= 0) {
this.onPattern.put(pattern, severity);
}
else {
this.onPattern.remove(pattern);
}
}
public int getOnPattern(final Pattern pattern) {
if (this.onPattern != null) {
final Integer integer= this.onPattern.get(pattern);
if (integer != null) {
return integer.intValue();
}
}
return -1;
}
public void setFileStoreValidator(final IValidator<? super IFileStore> validator) {
this.fileStoreValidator= validator;
}
public void setResourceLabel(final String label) {
this.resourceLabel= label;
}
/**
* Sets explicitly the object to validate.
* A <code>null</code> value stops the explicit mode. If the value is set
* explicitly, the value specified in the validate(...) methods is ignored.
* @param value the resource to validate or <code>null</code>.
*/
public void setExplicit(final Object value) {
this.fileStore= null;
this.workspaceResource= null;
this.explicitObject= value;
setStatus(null);
}
private void resetResolution() {
this.fileStore= null;
this.workspaceResource= null;
setStatus(null);
}
protected void setStatus(final @Nullable IStatus status) {
this.status= status;
}
@Override
public IStatus validate(final Object value) {
if (!checkExplicit()) {
doValidateChecked(value);
}
return this.status;
}
boolean checkExplicit() {
if (this.explicitObject != null) {
if (this.status == null) {
doValidateChecked(this.explicitObject);
}
return true;
}
return false;
}
private void doValidateChecked(final Object value) {
if (!this.inCheck) {
this.inCheck= true;
try {
IStatus status= doValidate1(value);
if (status.getSeverity() < IStatus.ERROR && this.fileStoreValidator != null) {
final IStatus status2= this.fileStoreValidator.validate(getFileStore());
if (status2 != null && status2.getSeverity() > status.getSeverity()) {
status= status2;
}
}
setStatus(status);
}
catch (final Exception e) {
CoreMiscellanyPlugin.log(new Status(IStatus.ERROR, CoreMiscellanyPlugin.BUNDLE_ID,
NLS.bind("An error occurred when validating resource path ({0}).", value),
e ));
}
finally {
this.inCheck= false;
}
}
}
private IStatus doValidate1(Object value) {
this.fileStore= null;
this.workspaceResource= null;
this.relativePath= null;
this.currentMax= Integer.MAX_VALUE;
// Resolve string
if (value instanceof IPath) {
value= ((IPath) value).toOSString();
}
if (value instanceof String) {
String s= (String) value;
if (s.length() == 0) {
return createStatus(this.onEmpty,
Messages.Resource_error_NoInput_message, Messages.Resource_error_NoInput_message_0,
null );
}
if (this.onPattern != null && !this.onPattern.isEmpty()) {
for (final Entry<Pattern, Integer> entry : this.onPattern.entrySet()) {
if (entry.getKey().matcher(s).find()) {
return Status.OK_STATUS;
}
}
}
try {
s= resolveExpression(s);
} catch (final CoreException e) {
return createStatus(e.getStatus().getSeverity(),
Messages.Resource_error_Other_message, Messages.Resource_error_Other_message_0,
e.getStatus().getMessage() );
}
if (s.length() == 0) {
return createStatus(this.onEmpty,
Messages.Resource_error_NoInput_message, Messages.Resource_error_NoInput_message_0,
null );
}
{ final IPath path= new Path(s);
if (!path.isAbsolute()) {
this.relativePath= path;
if (this.relativeMax >= 0 && this.relativeMax < this.currentMax) {
this.currentMax= this.relativeMax;
}
final String prefix= getRelativePrefix();
if (prefix != null) {
s= prefix + s;
}
else if (this.ignoreRelative) {
return Status.OK_STATUS;
}
}
}
final IWorkspace workspace= ResourcesPlugin.getWorkspace();
if (this.asWorkspacePath) {
final int typeMask= ((this.onFile < IStatus.ERROR) ? IResource.FILE : 0) |
((this.onDirectory < IStatus.ERROR) ? (IResource.PROJECT | IResource.FOLDER) : 0);
final IStatus status= workspace.validatePath(s, typeMask);
if (!status.isOK()) {
return createStatus(status.getSeverity(),
Messages.Resource_error_Other_message, Messages.Resource_error_Other_message_0,
status.getMessage() );
}
final IPath path= new Path(s);
this.workspaceResource= workspace.getRoot().findMember(path, true);
if (this.workspaceResource == null) {
final IResource project= workspace.getRoot().findMember(path.segment(0), true);
if (project == null) {
return createStatus(IStatus.ERROR,
Messages.Resource_error_NotInWorkspace_message, Messages.Resource_error_NotInWorkspace_message_0,
null );
}
if (path.segmentCount() == 1 && this.onDirectory < IStatus.ERROR) {
this.workspaceResource= project;
}
else if (this.onDirectory < this.onFile){
this.workspaceResource= workspace.getRoot().getFolder(path);
}
else {
this.workspaceResource= workspace.getRoot().getFile(path);
}
}
}
else {
// search efs reference
try {
this.fileStore= FileUtil.getFileStore(s);
if (this.fileStore == null) {
return createStatus(IStatus.ERROR,
Messages.Resource_error_NoValidSpecification_message, Messages.Resource_error_NoValidSpecification_message_0,
null );
}
}
catch (final CoreException e) {
return createStatus(IStatus.ERROR,
Messages.Resource_error_NoValidSpecification_message, Messages.Resource_error_NoValidSpecification_message_0,
e.getStatus().getMessage() );
}
// search file in workspace
if (this.fileStore != null) {
final IResource[] resources= (this.fileStore.fetchInfo().isDirectory()) ?
workspace.getRoot().findContainersForLocationURI(this.fileStore.toURI()) :
workspace.getRoot().findFilesForLocationURI(this.fileStore.toURI());
if (resources.length > 0) {
this.workspaceResource= resources[0];
}
}
}
}
if (value instanceof IFileStore) {
this.fileStore= (IFileStore) value;
}
else if (value instanceof IResource) {
this.workspaceResource= (IResource) value;
}
if (!this.requireWorkspace && this.fileStore != null) {
return validateFileStore();
}
else if (this.workspaceResource != null) {
return validateWorkspaceResource();
}
else if (this.requireWorkspace) {
return createStatus(IStatus.ERROR,
Messages.Resource_error_NotInWorkspace_message, Messages.Resource_error_NotInWorkspace_message_0,
null );
}
else {
throw new IllegalArgumentException();
}
}
protected String resolveExpression(final String expression) throws CoreException {
if (this.variableResolver != null) {
return this.variableResolver.performStringSubstitution(expression,
this.onVariableProblems );
}
final IStringVariableManager manager= VariablesPlugin.getDefault().getStringVariableManager();
try {
return manager.performStringSubstitution(expression);
}
catch (final CoreException e) {
manager.validateStringVariables(expression); // throws invalid variable
throw new CoreException(new Status(getOnLateResolve(), e.getStatus().getPlugin(), e.getStatus().getMessage())); // throws runtime variable
}
}
private IResource findWorkspaceResource(final URI location) {
final IWorkspaceRoot root= ResourcesPlugin.getWorkspace().getRoot();
IResource[] found= null;
if (this.onFile != IStatus.ERROR) {
found= root.findFilesForLocationURI(location);
}
if ((found == null || found.length == 0)
&& this.onDirectory != IStatus.ERROR) {
found= root.findContainersForLocationURI(location);
}
if (found != null && found.length > 0) {
return found[0];
}
return null;
}
protected IStatus validateWorkspaceResource() {
IStatus status= Status.OK_STATUS;
if (this.onNotLocal != IStatus.OK) {
if (!isLocalFile()) {
status= createStatus(this.onNotLocal,
Messages.Resource_error_NotLocal_message, Messages.Resource_error_NotLocal_message_0,
null );
}
if (status.getSeverity() == IStatus.ERROR) {
return status;
}
}
if (this.onExisting != IStatus.OK || this.onNotExisting != IStatus.OK || this.onFile != IStatus.OK || this.onDirectory != IStatus.OK) {
status= StatusUtils.getMoreSevere(status,
createExistsStatus(this.workspaceResource.exists(), (this.workspaceResource instanceof IContainer)) );
}
return status;
}
protected IStatus validateFileStore() {
IStatus status= Status.OK_STATUS;
if (this.onNotLocal != IStatus.OK) {
if (!isLocalFile()) {
status= createStatus(this.onNotLocal,
Messages.Resource_error_NotLocal_message, Messages.Resource_error_NotLocal_message_0,
null );
}
if (status.getSeverity() == IStatus.ERROR) {
return status;
}
}
if (this.onExisting != IStatus.OK || this.onNotExisting != IStatus.OK) {
final IFileInfo info= this.fileStore.fetchInfo();
status= StatusUtils.getMoreSevere(status,
createExistsStatus(info.exists(), info.isDirectory()) );
}
return status;
}
private IStatus createExistsStatus(final boolean exists, final boolean isDirectory) {
if (exists) {
IStatus status= createStatus(this.onExisting,
Messages.Resource_error_AlreadyExists_message, Messages.Resource_error_AlreadyExists_message_0,
null );
if (status.getSeverity() < this.onDirectory && isDirectory) {
status= createStatus(this.onDirectory,
Messages.Resource_error_IsDirectory_message, Messages.Resource_error_IsDirectory_message_0,
null );
}
if (status.getSeverity() < this.onFile && !isDirectory) {
status= createStatus(this.onFile,
Messages.Resource_error_IsFile_message, Messages.Resource_error_IsFile_message_0,
null );
}
return status;
}
else {
return createStatus(this.onNotExisting,
Messages.Resource_error_DoesNotExists_message, Messages.Resource_error_DoesNotExists_message_0,
null );
}
}
protected IStatus createStatus(int severity, final String message, final String message0,
String detail) {
if (severity == IStatus.OK) {
return Status.OK_STATUS;
}
if (severity > this.currentMax) {
severity= this.currentMax;
}
if (detail == null) {
detail= ""; //$NON-NLS-1$
}
return new Status(severity, CoreMiscellanyPlugin.BUNDLE_ID, (this.resourceLabel != null) ?
NLS.bind(message, this.resourceLabel, detail) :
NLS.bind(message0, detail) );
}
public @Nullable IFileStore getFileStore() {
checkExplicit();
if (this.fileStore == null && this.workspaceResource != null) {
try {
this.fileStore= EFS.getStore(this.workspaceResource.getLocationURI());
} catch (final CoreException e) {
}
}
return this.fileStore;
}
public IResource getWorkspaceResource() {
checkExplicit();
if (this.workspaceResource == null && this.fileStore != null) {
this.workspaceResource= findWorkspaceResource(this.fileStore.toURI());
}
return this.workspaceResource;
}
public boolean isLocalFile() {
final IFileStore fileStore= getFileStore();
if (fileStore != null) {
return fileStore.getFileSystem().equals(EFS.getLocalFileSystem());
}
return false;
}
public boolean isRelativeFile() {
return (this.relativePath != null);
}
public IPath getRelativeFile() {
return this.relativePath;
}
public IStatus getStatus() {
checkExplicit();
return this.status;
}
}