| /*=============================================================================# |
| # 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; |
| } |
| |
| } |