/******************************************************************************* * 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.FileUtil; 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(); StringBuffer locationBuffer = new StringBuffer(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(); FileOutputStream stream = null; try { stream = new FileOutputStream(versionFile); stream.write(current.getVersion()); stream.close(); } catch (IOException e) { String message = NLS.bind(Messages.resources_writeWorkspaceMeta, versionFile.getAbsolutePath()); throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, e); } finally { FileUtil.safeClose(stream); } } private char[] translateSegment(String segment) { // String.hashCode algorithm is API return HEX_STRINGS[Math.abs(segment.hashCode()) % SEGMENT_QUOTA]; } }