| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support |
| * James Blackburn (Broadcom Corp.) - ongoing development |
| * Sergey Prigogin (Google) - [464072] Refresh on Access ignored during text search |
| *******************************************************************************/ |
| package org.eclipse.core.internal.resources; |
| |
| import java.io.*; |
| import org.eclipse.core.filesystem.*; |
| import org.eclipse.core.internal.preferences.EclipsePreferences; |
| import org.eclipse.core.internal.utils.*; |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.content.IContentDescription; |
| import org.eclipse.core.runtime.content.IContentTypeManager; |
| import org.eclipse.core.runtime.jobs.ISchedulingRule; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * The standard implementation of {@link IFile}. |
| */ |
| public class File extends Resource implements IFile { |
| |
| protected File(IPath path, Workspace container) { |
| super(path, container); |
| } |
| |
| @Override |
| public void appendContents(InputStream content, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| String message = NLS.bind(Messages.resources_settingContents, getFullPath()); |
| SubMonitor subMonitor = SubMonitor.convert(monitor, message, 100); |
| try { |
| Assert.isNotNull(content, "Content cannot be null."); //$NON-NLS-1$ |
| if (workspace.shouldValidate) |
| workspace.validateSave(this); |
| final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); |
| try { |
| workspace.prepareOperation(rule, subMonitor.newChild(1)); |
| ResourceInfo info = getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| workspace.beginOperation(true); |
| IFileInfo fileInfo = getStore().fetchInfo(); |
| internalSetContents(content, fileInfo, updateFlags, true, subMonitor.newChild(99)); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| workspace.endOperation(rule, true); |
| } |
| } finally { |
| subMonitor.done(); |
| FileUtil.safeClose(content); |
| } |
| } |
| |
| @Override |
| public void appendContents(InputStream content, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { |
| // funnel all operations to central method |
| int updateFlags = force ? IResource.FORCE : IResource.NONE; |
| updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; |
| appendContents(content, updateFlags, monitor); |
| } |
| |
| /** |
| * Changes this file to be a folder in the resource tree and returns |
| * the newly created folder. All related |
| * properties are deleted. It is assumed that on disk the resource is |
| * already a folder/directory so no action is taken to delete the disk |
| * contents. |
| * <p> |
| * <b>This method is for the exclusive use of the local resource manager</b> |
| */ |
| public IFolder changeToFolder() throws CoreException { |
| getPropertyManager().deleteProperties(this, IResource.DEPTH_ZERO); |
| IFolder result = workspace.getRoot().getFolder(path); |
| if (isLinked()) { |
| IPath location = getRawLocation(); |
| delete(IResource.NONE, null); |
| result.createLink(location, IResource.ALLOW_MISSING_LOCAL, null); |
| } else { |
| workspace.deleteResource(this); |
| workspace.createResource(result, false); |
| } |
| return result; |
| } |
| |
| @Override |
| public void create(InputStream content, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| String message = NLS.bind(Messages.resources_creating, getFullPath()); |
| SubMonitor subMonitor = SubMonitor.convert(monitor, message, 100); |
| try { |
| checkValidPath(path, FILE, true); |
| final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); |
| try { |
| workspace.prepareOperation(rule, subMonitor.newChild(1)); |
| checkDoesNotExist(); |
| Container parent = (Container) getParent(); |
| ResourceInfo info = parent.getResourceInfo(false, false); |
| parent.checkAccessible(getFlags(info)); |
| checkValidGroupContainer(parent, false, false); |
| |
| workspace.beginOperation(true); |
| IFileStore store = getStore(); |
| IFileInfo localInfo = store.fetchInfo(); |
| if (BitMask.isSet(updateFlags, IResource.FORCE)) { |
| if (!Workspace.caseSensitive) { |
| if (localInfo.exists()) { |
| String name = getLocalManager().getLocalName(store); |
| if (name == null || localInfo.getName().equals(name)) { |
| delete(true, null); |
| } else { |
| // The file system is not case sensitive and there is already a file |
| // under this location. |
| message = NLS.bind(Messages.resources_existsLocalDifferentCase, new Path(store.toString()).removeLastSegments(1).append(name).toOSString()); |
| throw new ResourceException(IResourceStatus.CASE_VARIANT_EXISTS, getFullPath(), message, null); |
| } |
| } |
| } |
| } else { |
| if (localInfo.exists()) { |
| //return an appropriate error message for case variant collisions |
| if (!Workspace.caseSensitive) { |
| String name = getLocalManager().getLocalName(store); |
| if (name != null && !localInfo.getName().equals(name)) { |
| message = NLS.bind(Messages.resources_existsLocalDifferentCase, new Path(store.toString()).removeLastSegments(1).append(name).toOSString()); |
| throw new ResourceException(IResourceStatus.CASE_VARIANT_EXISTS, getFullPath(), message, null); |
| } |
| } |
| message = NLS.bind(Messages.resources_fileExists, store.toString()); |
| throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, getFullPath(), message, null); |
| } |
| } |
| subMonitor.worked(40); |
| |
| info = workspace.createResource(this, updateFlags); |
| boolean local = content != null; |
| if (local) { |
| try { |
| internalSetContents(content, localInfo, updateFlags, false, subMonitor.newChild(59)); |
| } catch (CoreException | OperationCanceledException e) { |
| // CoreException when a problem happened creating the file on disk |
| // OperationCanceledException when the operation of setting contents has been |
| // canceled |
| // In either case delete from the workspace and disk |
| workspace.deleteResource(this); |
| store.delete(EFS.NONE, null); |
| throw e; |
| } |
| } |
| internalSetLocal(local, DEPTH_ZERO); |
| if (!local) |
| getResourceInfo(true, true).clearModificationStamp(); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| workspace.endOperation(rule, true); |
| } |
| } finally { |
| subMonitor.done(); |
| FileUtil.safeClose(content); |
| } |
| } |
| |
| @Override |
| public void create(InputStream content, boolean force, IProgressMonitor monitor) throws CoreException { |
| // funnel all operations to central method |
| create(content, (force ? IResource.FORCE : IResource.NONE), monitor); |
| } |
| |
| @Override |
| public String getCharset() throws CoreException { |
| return getCharset(true); |
| } |
| |
| @Override |
| public String getCharset(boolean checkImplicit) throws CoreException { |
| // non-existing resources default to parent's charset |
| ResourceInfo info = getResourceInfo(false, false); |
| int flags = getFlags(info); |
| if (!exists(flags, false)) |
| return checkImplicit ? workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true) : null; |
| checkLocal(flags, DEPTH_ZERO); |
| try { |
| return internalGetCharset(checkImplicit, info); |
| } catch (CoreException e) { |
| if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND) { |
| return checkImplicit ? workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true) : null; |
| } |
| throw e; |
| } |
| } |
| |
| @Override |
| public String getCharsetFor(Reader contents) throws CoreException { |
| String charset; |
| ResourceInfo info = getResourceInfo(false, false); |
| int flags = getFlags(info); |
| if (exists(flags, true)) |
| // the file exists, look for user setting |
| if ((charset = workspace.getCharsetManager().getCharsetFor(getFullPath(), false)) != null) |
| // if there is a file-specific user setting, use it |
| return charset; |
| // tries to obtain a description from the contents provided |
| IContentDescription description; |
| try { |
| // TODO need to take project specific settings into account |
| IContentTypeManager contentTypeManager = Platform.getContentTypeManager(); |
| description = contentTypeManager.getDescriptionFor(contents, getName(), new QualifiedName[] {IContentDescription.CHARSET}); |
| } catch (IOException e) { |
| String message = NLS.bind(Messages.resources_errorContentDescription, getFullPath()); |
| throw new ResourceException(IResourceStatus.FAILED_DESCRIBING_CONTENTS, getFullPath(), message, e); |
| } |
| if (description != null) |
| if ((charset = description.getCharset()) != null) |
| // the description contained charset info, we are done |
| return charset; |
| // could not find out the encoding based on the contents... default to parent's |
| return workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true); |
| } |
| |
| private String internalGetCharset(boolean checkImplicit, ResourceInfo info) throws CoreException { |
| // if there is a file-specific user setting, use it |
| String charset = workspace.getCharsetManager().getCharsetFor(getFullPath(), false); |
| if (charset != null || !checkImplicit) |
| return charset; |
| // tries to obtain a description for the file contents |
| IContentDescription description = workspace.getContentDescriptionManager().getDescriptionFor(this, info, true); |
| if (description != null) { |
| String contentCharset = description.getCharset(); |
| if (contentCharset != null) |
| return contentCharset; |
| } |
| // could not find out the encoding based on the contents... default to parent's |
| return workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true); |
| } |
| |
| @Override |
| public IContentDescription getContentDescription() throws CoreException { |
| ResourceInfo info = getResourceInfo(false, false); |
| int flags = getFlags(info); |
| checkAccessible(flags); |
| checkLocal(flags, DEPTH_ZERO); |
| boolean isSynchronized = isSynchronized(IResource.DEPTH_ZERO); |
| // Throw an exception if out-of-sync and not auto-refresh enabled |
| if (!isSynchronized && !getLocalManager().isLightweightAutoRefreshEnabled()) { |
| String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, getFullPath()); |
| throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, getFullPath(), message, null); |
| } |
| return workspace.getContentDescriptionManager().getDescriptionFor(this, info, isSynchronized); |
| } |
| |
| @Override |
| public InputStream getContents() throws CoreException { |
| return getContents(getLocalManager().isLightweightAutoRefreshEnabled()); |
| } |
| |
| @Override |
| public InputStream getContents(boolean force) throws CoreException { |
| ResourceInfo info = getResourceInfo(false, false); |
| int flags = getFlags(info); |
| checkAccessible(flags); |
| checkLocal(flags, DEPTH_ZERO); |
| return getLocalManager().read(this, force, null); |
| } |
| |
| @Deprecated |
| @Override |
| public int getEncoding() throws CoreException { |
| ResourceInfo info = getResourceInfo(false, false); |
| int flags = getFlags(info); |
| checkAccessible(flags); |
| checkLocal(flags, DEPTH_ZERO); |
| return getLocalManager().getEncoding(this); |
| } |
| |
| @Override |
| public IFileState[] getHistory(IProgressMonitor monitor) { |
| return getLocalManager().getHistoryStore().getStates(getFullPath(), monitor); |
| } |
| |
| @Override |
| public int getType() { |
| return FILE; |
| } |
| |
| protected void internalSetContents(InputStream content, IFileInfo fileInfo, int updateFlags, boolean append, IProgressMonitor monitor) throws CoreException { |
| if (content == null) |
| content = new ByteArrayInputStream(new byte[0]); |
| getLocalManager().write(this, content, fileInfo, updateFlags, append, monitor); |
| updateMetadataFiles(); |
| workspace.getAliasManager().updateAliases(this, getStore(), IResource.DEPTH_ZERO, monitor); |
| } |
| |
| /** |
| * Optimized refreshLocal for files. This implementation does not block the workspace |
| * for the common case where the file exists both locally and on the file system, and |
| * is in sync. For all other cases, it defers to the super implementation. |
| */ |
| @Override |
| public void refreshLocal(int depth, IProgressMonitor monitor) throws CoreException { |
| if (!getLocalManager().fastIsSynchronized(this)) |
| super.refreshLocal(IResource.DEPTH_ZERO, monitor); |
| } |
| |
| @Override |
| public void setContents(IFileState content, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| setContents(content.getContents(), updateFlags, monitor); |
| } |
| |
| @Override |
| public void setContents(InputStream content, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| String message = NLS.bind(Messages.resources_settingContents, getFullPath()); |
| SubMonitor subMonitor = SubMonitor.convert(monitor, message, 100); |
| try { |
| if (workspace.shouldValidate) |
| workspace.validateSave(this); |
| final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); |
| try { |
| workspace.prepareOperation(rule, subMonitor.newChild(1)); |
| ResourceInfo info = getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| workspace.beginOperation(true); |
| IFileInfo fileInfo = getStore().fetchInfo(); |
| internalSetContents(content, fileInfo, updateFlags, false, subMonitor.newChild(99)); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| workspace.endOperation(rule, true); |
| } |
| } finally { |
| subMonitor.done(); |
| FileUtil.safeClose(content); |
| } |
| } |
| |
| @Override |
| public long setLocalTimeStamp(long value) throws CoreException { |
| //override to handle changing timestamp on project description file |
| long result = super.setLocalTimeStamp(value); |
| if (path.segmentCount() == 2 && path.segment(1).equals(IProjectDescription.DESCRIPTION_FILE_NAME)) { |
| //handle concurrent project deletion |
| ResourceInfo projectInfo = ((Project) getProject()).getResourceInfo(false, false); |
| if (projectInfo != null) |
| getLocalManager().updateLocalSync(projectInfo, result); |
| } |
| return result; |
| } |
| |
| /** |
| * Treat the file specially if it represents a metadata file, which includes: |
| * - project description file (.project) |
| * - project preferences files (*.prefs) |
| * |
| * This method is called whenever it is discovered that a file has |
| * been modified (added, removed, or changed). |
| */ |
| public void updateMetadataFiles() throws CoreException { |
| int count = path.segmentCount(); |
| String name = path.segment(1); |
| // is this a project description file? |
| if (count == 2 && name.equals(IProjectDescription.DESCRIPTION_FILE_NAME)) { |
| Project project = (Project) getProject(); |
| project.updateDescription(); |
| // Discard stale project natures on ProjectInfo |
| ProjectInfo projectInfo = (ProjectInfo) project.getResourceInfo(false, true); |
| projectInfo.discardNatures(); |
| return; |
| } |
| // check to see if we are in the .settings directory |
| if (count == 3 && EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(name)) { |
| ProjectPreferences.updatePreferences(this); |
| return; |
| } |
| } |
| |
| @Deprecated |
| @Override |
| public void setCharset(String newCharset) throws CoreException { |
| ResourceInfo info = getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| workspace.getCharsetManager().setCharsetFor(getFullPath(), newCharset); |
| } |
| |
| @Override |
| public void setCharset(String newCharset, IProgressMonitor monitor) throws CoreException { |
| String message = NLS.bind(Messages.resources_settingCharset, getFullPath()); |
| SubMonitor subMonitor = SubMonitor.convert(monitor, message, 100); |
| // need to get the project as a scheduling rule because we might be creating a new folder/file to |
| // hold the project settings |
| final ISchedulingRule rule = workspace.getRuleFactory().charsetRule(this); |
| try { |
| workspace.prepareOperation(rule, subMonitor.newChild(1)); |
| ResourceInfo info = getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| workspace.beginOperation(true); |
| workspace.getCharsetManager().setCharsetFor(getFullPath(), newCharset); |
| info = getResourceInfo(false, true); |
| info.incrementCharsetGenerationCount(); |
| subMonitor.worked(99); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| subMonitor.done(); |
| workspace.endOperation(rule, true); |
| } |
| } |
| |
| @Override |
| public void setContents(InputStream content, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { |
| // funnel all operations to central method |
| int updateFlags = force ? IResource.FORCE : IResource.NONE; |
| updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; |
| setContents(content, updateFlags, monitor); |
| } |
| |
| @Override |
| public void setContents(IFileState source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { |
| // funnel all operations to central method |
| int updateFlags = force ? IResource.FORCE : IResource.NONE; |
| updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; |
| setContents(source.getContents(), updateFlags, monitor); |
| } |
| } |