| /******************************************************************************* |
| * Copyright (c) 2004, 2011 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 |
| * James Blackburn (Broadcom Corp.) - ongoing development |
| *******************************************************************************/ |
| package org.eclipse.core.internal.localstore; |
| |
| import java.io.*; |
| import org.eclipse.core.internal.localstore.Bucket.Visitor; |
| import org.eclipse.core.internal.resources.ResourceException; |
| import org.eclipse.core.internal.resources.Workspace; |
| import org.eclipse.core.internal.utils.Messages; |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * @since 3,1 |
| */ |
| public class BucketTree { |
| public static final int DEPTH_INFINITE = Integer.MAX_VALUE; |
| public static final int DEPTH_ONE = 1; |
| public static final int DEPTH_ZERO = 0; |
| |
| private final static int SEGMENT_QUOTA = 256; //two hex characters |
| |
| /** |
| * Store all bucket names to avoid creating garbage when traversing the tree |
| */ |
| private static final char[][] HEX_STRINGS; |
| |
| static { |
| HEX_STRINGS = new char[SEGMENT_QUOTA][]; |
| for (int i = 0; i < HEX_STRINGS.length; i++) |
| HEX_STRINGS[i] = Integer.toHexString(i).toCharArray(); |
| } |
| |
| protected Bucket current; |
| |
| private Workspace workspace; |
| |
| public BucketTree(Workspace workspace, Bucket bucket) { |
| this.current = bucket; |
| this.workspace = workspace; |
| } |
| |
| /** |
| * From a starting point in the tree, visit all nodes under it. |
| * @param visitor |
| * @param base |
| * @param depth |
| */ |
| public void accept(Bucket.Visitor visitor, IPath base, int depth) throws CoreException { |
| if (Path.ROOT.equals(base)) { |
| current.load(null, locationFor(Path.ROOT)); |
| if (current.accept(visitor, base, DEPTH_ZERO) != Visitor.CONTINUE) |
| return; |
| if (depth == DEPTH_ZERO) |
| return; |
| boolean keepVisiting = true; |
| depth--; |
| IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); |
| for (int i = 0; keepVisiting && i < projects.length; i++) { |
| IPath projectPath = projects[i].getFullPath(); |
| keepVisiting = internalAccept(visitor, projectPath, locationFor(projectPath), depth, 1); |
| } |
| } else |
| internalAccept(visitor, base, locationFor(base), depth, 0); |
| } |
| |
| public void close() throws CoreException { |
| current.save(); |
| saveVersion(); |
| } |
| |
| public Bucket getCurrent() { |
| return current; |
| } |
| |
| public File getVersionFile() { |
| return new File(locationFor(Path.ROOT), current.getVersionFileName()); |
| } |
| |
| /** |
| * This will never be called for a bucket for the workspace root. |
| * |
| * @return whether to continue visiting other branches |
| */ |
| private boolean internalAccept(Bucket.Visitor visitor, IPath base, File bucketDir, int depthRequested, int currentDepth) throws CoreException { |
| current.load(base.segment(0), bucketDir); |
| int outcome = current.accept(visitor, base, depthRequested); |
| if (outcome != Visitor.CONTINUE) |
| return outcome == Visitor.RETURN; |
| if (depthRequested <= currentDepth) |
| return true; |
| File[] subDirs = bucketDir.listFiles(); |
| if (subDirs == null) |
| return true; |
| for (int i = 0; i < subDirs.length; i++) |
| if (subDirs[i].isDirectory()) |
| if (!internalAccept(visitor, base, subDirs[i], depthRequested, currentDepth + 1)) |
| return false; |
| return true; |
| } |
| |
| public void loadBucketFor(IPath path) throws CoreException { |
| current.load(Path.ROOT.equals(path) ? null : path.segment(0), locationFor(path)); |
| } |
| |
| private File locationFor(IPath resourcePath) { |
| //optimized to avoid string and path creations |
| IPath baseLocation = workspace.getMetaArea().locationFor(resourcePath).removeTrailingSeparator(); |
| int segmentCount = resourcePath.segmentCount(); |
| String locationString = baseLocation.toOSString(); |
| StringBuilder locationBuffer = new StringBuilder(locationString.length() + Bucket.INDEXES_DIR_NAME.length() + 16); |
| locationBuffer.append(locationString); |
| locationBuffer.append(File.separatorChar); |
| locationBuffer.append(Bucket.INDEXES_DIR_NAME); |
| // the last segment is ignored |
| for (int i = 1; i < segmentCount - 1; i++) { |
| // translate all segments except the first one (project name) |
| locationBuffer.append(File.separatorChar); |
| locationBuffer.append(translateSegment(resourcePath.segment(i))); |
| } |
| return new File(locationBuffer.toString()); |
| } |
| |
| /** |
| * Writes the version tag to a file on disk. |
| */ |
| private void saveVersion() throws CoreException { |
| File versionFile = getVersionFile(); |
| if (!versionFile.getParentFile().exists()) |
| versionFile.getParentFile().mkdirs(); |
| try (FileOutputStream stream = new FileOutputStream(versionFile)) { |
| stream.write(current.getVersion()); |
| } catch (IOException e) { |
| String message = NLS.bind(Messages.resources_writeWorkspaceMeta, versionFile.getAbsolutePath()); |
| throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, e); |
| } |
| } |
| |
| private char[] translateSegment(String segment) { |
| // String.hashCode algorithm is API |
| return HEX_STRINGS[Math.abs(segment.hashCode()) % SEGMENT_QUOTA]; |
| } |
| } |