All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.core.internal.localstore.HistoryStore2 Maven / Gradle / Ivy

Go to download

AspectJ tools most notably contains the AspectJ compiler (AJC). AJC applies aspects to Java classes during compilation, fully replacing Javac for plain Java classes and also compiling native AspectJ or annotation-based @AspectJ syntax. Furthermore, AJC can weave aspects into existing class files in a post-compile binary weaving step. This library is a superset of AspectJ weaver and hence also of AspectJ runtime.

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2004, 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
 *     Lars Vogel  - Bug 473427
 *******************************************************************************/
package org.eclipse.core.internal.localstore;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.internal.localstore.Bucket.Entry;
import org.eclipse.core.internal.localstore.HistoryBucket.HistoryEntry;
import org.eclipse.core.internal.resources.FileState;
import org.eclipse.core.internal.resources.ResourceException;
import org.eclipse.core.internal.resources.ResourceStatus;
import org.eclipse.core.internal.resources.Workspace;
import org.eclipse.core.internal.resources.WorkspaceDescription;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.internal.utils.UniversalUniqueIdentifier;
import org.eclipse.core.resources.IFileState;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspaceDescription;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

public class HistoryStore2 implements IHistoryStore {

	class HistoryCopyVisitor extends Bucket.Visitor {
		private List changes = new ArrayList<>();
		private IPath destination;
		private IPath source;

		public HistoryCopyVisitor(IPath source, IPath destination) {
			this.source = source;
			this.destination = destination;
		}

		@Override
		public void afterSaving(Bucket bucket) throws CoreException {
			saveChanges();
			changes.clear();
		}

		private void saveChanges() throws CoreException {
			if (changes.isEmpty())
				return;
			// make effective all changes collected
			Iterator i = changes.iterator();
			HistoryEntry entry = i.next();
			tree.loadBucketFor(entry.getPath());
			HistoryBucket bucket = (HistoryBucket) tree.getCurrent();
			bucket.addBlobs(entry);
			while (i.hasNext())
				bucket.addBlobs(i.next());
			bucket.save();
		}

		@Override
		public int visit(Entry sourceEntry) {
			IPath destinationPath = destination.append(sourceEntry.getPath().removeFirstSegments(source.segmentCount()));
			HistoryEntry destinationEntry = new HistoryEntry(destinationPath, (HistoryEntry) sourceEntry);
			// we may be copying to the same source bucket, collect to make change effective later
			// since we cannot make changes to it while iterating
			changes.add(destinationEntry);
			return CONTINUE;
		}
	}

	private BlobStore blobStore;
	private Set blobsToRemove = new HashSet<>();
	final BucketTree tree;
	private Workspace workspace;

	public HistoryStore2(Workspace workspace, IFileStore store, int limit) {
		this.workspace = workspace;
		try {
			store.mkdir(EFS.NONE, null);
		} catch (CoreException e) {
			//ignore the failure here because there is no way to surface it.
			//any attempt to write to the store will throw an appropriate exception
		}
		this.blobStore = new BlobStore(store, limit);
		this.tree = new BucketTree(workspace, new HistoryBucket());
	}

	/**
	 * @see IHistoryStore#addState(IPath, IFileStore, IFileInfo, boolean)
	 */
	@Override
	public synchronized IFileState addState(IPath key, IFileStore localFile, IFileInfo info, boolean moveContents) {
		long lastModified = info.getLastModified();
		if (Policy.DEBUG_HISTORY)
			Policy.debug("History: Adding state for key: " + key + ", file: " + localFile + ", timestamp: " + lastModified + ", size: " + localFile.fetchInfo().getLength()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
		if (!isValid(localFile, info))
			return null;
		UniversalUniqueIdentifier uuid = null;
		try {
			uuid = blobStore.addBlob(localFile, moveContents);
			tree.loadBucketFor(key);
			HistoryBucket currentBucket = (HistoryBucket) tree.getCurrent();
			currentBucket.addBlob(key, uuid, lastModified);
			//			currentBucket.save();
		} catch (CoreException e) {
			log(e);
		}
		return new FileState(this, key, lastModified, uuid);
	}

	@Override
	public synchronized Set allFiles(IPath root, int depth, IProgressMonitor monitor) {
		final Set allFiles = new HashSet<>();
		try {
			tree.accept(new Bucket.Visitor() {
				@Override
				public int visit(Entry fileEntry) {
					allFiles.add(fileEntry.getPath());
					return CONTINUE;
				}
			}, root, depth == IResource.DEPTH_INFINITE ? BucketTree.DEPTH_INFINITE : depth);
		} catch (CoreException e) {
			log(e);
		}
		return allFiles;
	}

	/**
	 * Applies the clean-up policy to an entry.
	 */
	protected void applyPolicy(HistoryEntry fileEntry, int maxStates, long minTimeStamp) {
		for (int i = 0; i < fileEntry.getOccurrences(); i++) {
			if (i < maxStates && fileEntry.getTimestamp(i) >= minTimeStamp)
				continue;
			// "delete" the current uuid
			blobsToRemove.add(fileEntry.getUUID(i));
			fileEntry.deleteOccurrence(i);
		}
	}

	/**
	 * Applies the clean-up policy to a subtree.
	 */
	private void applyPolicy(IPath root) throws CoreException {
		IWorkspaceDescription description = workspace.internalGetDescription();
		final long minimumTimestamp = System.currentTimeMillis() - description.getFileStateLongevity();
		final int maxStates = description.getMaxFileStates();
		// apply policy to the given tree
		tree.accept(new Bucket.Visitor() {
			@Override
			public int visit(Entry entry) {
				applyPolicy((HistoryEntry) entry, maxStates, minimumTimestamp);
				return CONTINUE;
			}
		}, root, BucketTree.DEPTH_INFINITE);
		tree.getCurrent().save();
	}

	@Override
	public synchronized void clean(final IProgressMonitor monitor) {
		long start = System.currentTimeMillis();
		try {
			monitor.beginTask(Messages.resources_pruningHistory, IProgressMonitor.UNKNOWN);
			IWorkspaceDescription description = workspace.internalGetDescription();
			final long minimumTimestamp = System.currentTimeMillis() - description.getFileStateLongevity();
			final int maxStates = description.getMaxFileStates();
			final int[] entryCount = new int[1];
			if (description.isApplyFileStatePolicy()) {
				tree.accept(new Bucket.Visitor() {
					@Override
					public int visit(Entry fileEntry) {
						if (monitor.isCanceled())
							return STOP;
						entryCount[0] += fileEntry.getOccurrences();
						applyPolicy((HistoryEntry) fileEntry, maxStates, minimumTimestamp);
						// remove unreferenced blobs, when blobsToRemove size is greater than 100
						removeUnreferencedBlobs(100);
						return monitor.isCanceled() ? STOP : CONTINUE;
					}
				}, IPath.ROOT, BucketTree.DEPTH_INFINITE);
			}
			if (Policy.DEBUG_HISTORY) {
				Policy.debug("Time to apply history store policies: " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$ //$NON-NLS-2$
				Policy.debug("Total number of history store entries: " + entryCount[0]); //$NON-NLS-1$
			}
			// remove all remaining unreferenced blobs
			removeUnreferencedBlobs(0);
		} catch (Exception e) {
			String message = Messages.history_problemsCleaning;
			ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, null, message, e);
			Policy.log(status);
		} finally {
			monitor.done();
		}
	}

	/*
	 * Remove blobs from the blobStore. When the size of blobsToRemove exceeds the limit,
	 * remove the given blobs from blobStore. If the limit is zero or negative, remove blobs
	 * regardless of the limit.
	 */
	void removeUnreferencedBlobs(int limit) {
		if (limit <= 0 || limit <= blobsToRemove.size()) {
			long start = System.currentTimeMillis();
			// remove unreferenced blobs
			blobStore.deleteBlobs(blobsToRemove);
			if (Policy.DEBUG_HISTORY)
				Policy.debug("Time to remove " + blobsToRemove.size() + " unreferenced blobs: " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
			blobsToRemove = new HashSet<>();
		}
	}

	@Override
	public void closeHistoryStore(IResource resource) {
		try {
			tree.getCurrent().save();
			tree.getCurrent().flush();
		} catch (CoreException e) {
			log(e);
		}
	}

	@Override
	public synchronized void copyHistory(IResource sourceResource, IResource destinationResource, boolean moving) {
		// return early if either of the paths are null or if the source and
		// destination are the same.
		if (sourceResource == null || destinationResource == null) {
			String message = Messages.history_copyToNull;
			ResourceStatus status = new ResourceStatus(IResourceStatus.INTERNAL_ERROR, null, message, null);
			Policy.log(status);
			return;
		}
		if (sourceResource.equals(destinationResource)) {
			String message = Messages.history_copyToSelf;
			ResourceStatus status = new ResourceStatus(IResourceStatus.INTERNAL_ERROR, sourceResource.getFullPath(), message, null);
			Policy.log(status);
			return;
		}

		final IPath source = sourceResource.getFullPath();
		final IPath destination = destinationResource.getFullPath();
		Assert.isLegal(source.segmentCount() > 0);
		Assert.isLegal(destination.segmentCount() > 0);
		Assert.isLegal(source.segmentCount() > 1 || destination.segmentCount() == 1);

		try {
			// special case: we are moving a project
			if (moving && sourceResource.getType() == IResource.PROJECT) {
				// flush the tree to avoid confusion if another project is created with the same name
				final Bucket bucket = tree.getCurrent();
				bucket.save();
				bucket.flush();
				return;
			}
			// copy history by visiting the source tree
			HistoryCopyVisitor copyVisitor = new HistoryCopyVisitor(source, destination);
			tree.accept(copyVisitor, source, BucketTree.DEPTH_INFINITE);
			// apply clean-up policy to the destination tree
			applyPolicy(destinationResource.getFullPath());
		} catch (CoreException e) {
			log(e);
		}
	}

	@Override
	public boolean exists(IFileState target) {
		return blobStore.fileFor(((FileState) target).getUUID()).fetchInfo().exists();
	}

	@Override
	public InputStream getContents(IFileState target) throws CoreException {
		if (!target.exists()) {
			String message = Messages.history_notValid;
			throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, target.getFullPath(), message, null);
		}
		return blobStore.getBlob(((FileState) target).getUUID());
	}

	@Override
	public synchronized IFileState[] getStates(IPath filePath, IProgressMonitor monitor) {
		try {
			tree.loadBucketFor(filePath);
			HistoryBucket currentBucket = (HistoryBucket) tree.getCurrent();
			HistoryEntry fileEntry = currentBucket.getEntry(filePath);
			if (fileEntry == null || fileEntry.isEmpty())
				return new IFileState[0];
			IFileState[] states = new IFileState[fileEntry.getOccurrences()];
			for (int i = 0; i < states.length; i++)
				states[i] = new FileState(this, fileEntry.getPath(), fileEntry.getTimestamp(i), fileEntry.getUUID(i));
			return states;
		} catch (CoreException ce) {
			log(ce);
			return new IFileState[0];
		}
	}

	public BucketTree getTree() {
		return tree;
	}

	/**
	 * Return a boolean value indicating whether or not the given file
	 * should be added to the history store based on the current history
	 * store policies.
	 *
	 * @param localFile the file to check
	 * @return true if this file should be added to the history
	 * 	store and false otherwise
	 */
	private boolean isValid(IFileStore localFile, IFileInfo info) {
		WorkspaceDescription description = workspace.internalGetDescription();
		if (!description.isApplyFileStatePolicy())
			return true;
		long length = info.getLength();
		boolean result = length <= description.getMaxFileStateSize();
		if (Policy.DEBUG_HISTORY && !result)
			Policy.debug("History: Ignoring file (too large). File: " + localFile.toString() + //$NON-NLS-1$
					", size: " + length + //$NON-NLS-1$
					", max: " + description.getMaxFileStateSize()); //$NON-NLS-1$
		return result;
	}

	/**
	 * Logs a CoreException
	 */
	private void log(CoreException e) {
		//create a new status to wrap the exception if there is no exception in the status
		IStatus status = e.getStatus();
		if (status.getException() == null)
			status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_WRITE_METADATA, "Internal error in history store", e); //$NON-NLS-1$
		Policy.log(status);
	}

	@Override
	public synchronized void remove(IPath root, IProgressMonitor monitor) {
		try {
			final Set tmpBlobsToRemove = blobsToRemove;
			tree.accept(new Bucket.Visitor() {
				@Override
				public int visit(Entry fileEntry) {
					for (int i = 0; i < fileEntry.getOccurrences(); i++)
						// remember we need to delete the files later
						tmpBlobsToRemove.add(((HistoryEntry) fileEntry).getUUID(i));
					fileEntry.delete();
					return CONTINUE;
				}
			}, root, BucketTree.DEPTH_INFINITE);
		} catch (CoreException ce) {
			log(ce);
		}
	}

	/**
	 * @see IHistoryStore#removeGarbage()
	 */
	@Override
	public synchronized void removeGarbage() {
		try {
			final Set tmpBlobsToRemove = blobsToRemove;
			tree.accept(new Bucket.Visitor() {
				@Override
				public int visit(Entry fileEntry) {
					for (int i = 0; i < fileEntry.getOccurrences(); i++)
						// remember we need to delete the files later
						tmpBlobsToRemove.remove(((HistoryEntry) fileEntry).getUUID(i));
					return CONTINUE;
				}
			}, IPath.ROOT, BucketTree.DEPTH_INFINITE);
			blobStore.deleteBlobs(blobsToRemove);
			blobsToRemove = new HashSet<>();
		} catch (Exception e) {
			String message = Messages.history_problemsCleaning;
			ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, null, message, e);
			Policy.log(status);
		}
	}

	@Override
	public synchronized void shutdown(IProgressMonitor monitor) throws CoreException {
		tree.close();
	}

	@Override
	public void startup(IProgressMonitor monitor) {
		// nothing to be done
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy