/*******************************************************************************
* Copyright (c) 2010 Arapiki Solutions Inc.
* 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:
* "Peter Smith <psmith@arapiki.com>" - initial API and
* implementation and/or initial documentation
*******************************************************************************/
package com.buildml.model.impl;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.buildml.model.FatalBuildStoreError;
import com.buildml.model.IActionMgr;
import com.buildml.model.IActionMgr.OperationType;
import com.buildml.model.IActionTypeMgr;
import com.buildml.model.IBuildStore;
import com.buildml.model.IFileAttributeMgr;
import com.buildml.model.IFileGroupMgrListener;
import com.buildml.model.IFileIncludeMgr;
import com.buildml.model.IFileMgr;
import com.buildml.model.IFileMgrListener;
import com.buildml.model.IPackageMemberMgr;
import com.buildml.model.IPackageMemberMgr.PackageDesc;
import com.buildml.model.IPackageMgr;
import com.buildml.model.IPackageRootMgr;
import com.buildml.model.ISlotTypes.SlotDetails;
import com.buildml.model.types.PathNameCache;
import com.buildml.model.types.PathNameCache.PathNameCacheValue;
import com.buildml.utils.errors.ErrorCode;
import com.buildml.utils.string.PathUtils;
/**
* A manager class (that supports the BuildStore class) that manages all BuildStore
* information about paths (files, directories, etc), as well as path roots.
* <p>
* There should be exactly one FileMgr object per BuildStore object. Use the
* BuildStore's getFileMgr() method to obtain that one instance.
*
* @author "Peter Smith <psmith@arapiki.com>"
*/
public class FileMgr implements IFileMgr {
/*=====================================================================================*
* TYPES/FIELDS
*=====================================================================================*/
/** The BuildStore object that "owns" this FileMgr object. */
private IBuildStore buildStore;
/**
* Our database manager object, used to access the database content. This is provided
* to us when the FileMgr is first instantiated.
*/
private BuildStoreDB db = null;
/**
* A cache for recording the most recently accessed file name mappings. This
* helps to speed up file access.
*/
PathNameCache fileNameCache;
/**
* Other BuildStore managers we need to communicate with
*/
private IActionMgr actionMgr;
private IActionTypeMgr actionTypeMgr;
private IFileAttributeMgr fileAttrMgr;
private IFileIncludeMgr fileIncludeMgr;
/** The slotID for the "Directory" slot */
private int dirSlotId;
/**
* Various prepared statement for database access.
*/
private PreparedStatement
findChildPrepStmt = null,
insertChildPrepStmt = null,
findPathDetailsPrepStmt = null,
findPathIdFromParentPrepStmt = null,
trashPathPrepStmt = null,
pathIsTrashPrepStmt = null,
insertPackageMemberPrepStmt = null;
/** The event listeners who are registered to learn about path changes */
List<IFileMgrListener> listeners = new ArrayList<IFileMgrListener>();
/*=====================================================================================*
* CONSTRUCTORS
*=====================================================================================*/
/**
* Create a new FileMgr object.
*
* @param buildStore The BuildStore that "owns" this FileMgr manager object.
*/
public FileMgr(BuildStore buildStore) {
this.buildStore = buildStore;
this.db = buildStore.getBuildStoreDB();
/* initialize prepared database statements */
findChildPrepStmt = db.prepareStatement("select id, pathType from files where parentId = ? and name = ? " +
"and trashed = 0");
insertChildPrepStmt = db.prepareStatement("insert into files values (null, ?, 0, ?, ?)");
findPathDetailsPrepStmt = db.prepareStatement(
"select parentId, pathType, files.name from files where files.id = ?");
findPathIdFromParentPrepStmt = db.prepareStatement(
"select id from files where parentId = ? and trashed = 0 and name != \"/\" order by name");
trashPathPrepStmt = db.prepareStatement("update files set trashed = ? where id = ?");
pathIsTrashPrepStmt = db.prepareStatement("select trashed from files where id = ?");
insertPackageMemberPrepStmt = db.prepareStatement("insert into packageMembers values (?, ?, ?, ?, -1, -1)");
/*
* Create an empty cache to record the most-recently accessed file name mapping, to save us from
* querying the database all the time.
*/
fileNameCache = new PathNameCache(40960);
}
/*=====================================================================================*
* PUBLIC METHODS
*=====================================================================================*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#addFile(java.lang.String)
*/
@Override
public int addFile(String fullPathName) {
return addPath(PathType.TYPE_FILE, fullPathName);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#addDirectory(java.lang.String)
*/
@Override
public int addDirectory(String fullPathName) {
return addPath(PathType.TYPE_DIR, fullPathName);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#addSymlink(java.lang.String)
*/
@Override
public int addSymlink(String fullPath) {
// TODO: implement this
return ErrorCode.BAD_VALUE;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#addChildOfPath(int, com.buildml.model.IFileMgr.PathType, java.lang.String)
*/
@Override
public int addChildOfPath(int parentId, PathType pathType, String childName) {
/*
* Validate that the parent path exists, and that it's TYPE_DIR.
*/
if (getPathType(parentId) != PathType.TYPE_DIR){
return ErrorCode.NOT_A_DIRECTORY;
}
/* delegate to our helper function, which does the rest of the work */
return addChildOfPathHelper(parentId, pathType, childName);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getPath(java.lang.String)
*/
@Override
public int getPath(String fullPathName) {
/* parse the path name and separate it into root and path components */
String rootAndPath[] = getRootAndPath(fullPathName);
String rootName = rootAndPath[0];
String pathName = rootAndPath[1];
/* split the path into components, separated by / */
String components[] = PathUtils.tokenizePath(pathName);
IPackageRootMgr pkgRootMgr = buildStore.getPackageRootMgr();
int parentID = pkgRootMgr.getRootPath(rootName);
for (int i = 0; i < components.length; i++) {
/* get the next child ID, if it's missing, then the full path is missing */
int childID = getChildOfPath(parentID, components[i]);
if (childID == ErrorCode.NOT_FOUND) {
return ErrorCode.BAD_PATH;
}
/* this component was found - move to the next */
parentID = childID;
}
/* all path components exist, so return the last one */
return parentID;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getPathName(int, int)
*/
@Override
public String getPathName(int pathId, int pkgId) {
/* delegate to the common method */
return getPathNameCommon(pathId, true, pkgId);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getPathName(int, boolean)
*/
@Override
public String getPathName(int pathId, boolean showRoots) {
/* determine the path's current package */
IPackageMgr pkgMgr = buildStore.getPackageMgr();
IPackageMemberMgr pkgMemberMgr = buildStore.getPackageMemberMgr();
/* automatically determine the path's package - we should default to <import> */
PackageDesc pathPackage = pkgMemberMgr.getPackageOfMember(IPackageMemberMgr.TYPE_FILE, pathId);
int pkgId = pkgMgr.getImportPackage();
if (pathPackage != null) {
pkgId = pathPackage.pkgId;
}
/* delegate to the common method */
return getPathNameCommon(pathId, showRoots, pkgId);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getPathName(int)
*/
@Override
public String getPathName(int pathId) {
return getPathName(pathId, false);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getNativePath(int)
*/
@Override
public String getNativePathName(int pathId) {
/* get the path, relative to its root */
String pathWithRoot = getPathName(pathId, true);
int slashIndex = pathWithRoot.indexOf('/');
if (slashIndex == -1){
return null;
}
String rootName = pathWithRoot.substring(1, slashIndex);
/* get the native path of the root (possibly with overrides) */
IPackageRootMgr pkgRootMgr = buildStore.getPackageRootMgr();
String nativeRootPath = pkgRootMgr.getRootNative(rootName);
if (nativeRootPath == null) {
return null;
}
/* return the concatenation of the two */
return nativeRootPath + pathWithRoot.substring(slashIndex);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getBaseName(int)
*/
@Override
public String getBaseName(int pathId) {
Object pathDetails[] = getPathDetails(pathId);
if (pathDetails == null) {
return null;
}
return (String)pathDetails[2];
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getParentPath(int)
*/
@Override
public int getParentPath(int pathId) {
Object pathDetails[] = getPathDetails(pathId);
if (pathDetails == null) {
return ErrorCode.NOT_FOUND;
}
return (Integer)pathDetails[0];
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getPathType(int)
*/
@Override
public PathType getPathType(int pathId) {
Object pathDetails[] = getPathDetails(pathId);
if (pathDetails == null) {
return PathType.TYPE_INVALID;
}
return (PathType)pathDetails[1];
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getChildOfPath(int, java.lang.String)
*/
@Override
public int getChildOfPath(int parentId, String childName)
{
Object [] result = getChildOfPathWithType(parentId, childName);
if (result == null) {
return ErrorCode.NOT_FOUND;
} else {
return (Integer)result[0];
}
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getChildPaths(int)
*/
@Override
public Integer[] getChildPaths(int pathId) {
/*
* Fetch the records from the file table where the parentID is
* set to 'pathId'. The records will be returned in alphabetical
* order.
*
* The corner case (built into the prepared statement
* query) is that / (the root) is always a child of itself, although
* we don't want to report it. The query already excludes /.
*/
Integer results[] = null;
try {
findPathIdFromParentPrepStmt.setInt(1, pathId);
results = db.executePrepSelectIntegerColumn(findPathIdFromParentPrepStmt);
} catch (SQLException e) {
throw new FatalBuildStoreError("Error in SQL: " + e);
}
return results;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#removePath(int)
*/
@Override
public int movePathToTrash(int pathId)
{
/* check that this path doesn't have children */
if (getChildPaths(pathId).length != 0) {
return ErrorCode.CANT_REMOVE;
}
/* check that it's not used as the directory for any shell actions */
Integer dirResult[] = actionMgr.getActionsWhereSlotEquals(dirSlotId, pathId);
if ((dirResult == null) || (dirResult.length != 0)) {
return ErrorCode.CANT_REMOVE;
}
/* check that it's not accessed by any actions */
if (actionMgr.getActionsThatAccess(pathId,
OperationType.OP_UNSPECIFIED).length != 0) {
return ErrorCode.CANT_REMOVE;
}
/* check that it's not associated with a root */
IPackageRootMgr pkgRootMgr = buildStore.getPackageRootMgr();
if (pkgRootMgr.getRootsAtPath(pathId).length != 0) {
return ErrorCode.CANT_REMOVE;
}
/* check that it's not included by another file */
if (fileIncludeMgr.getFilesThatInclude(pathId).length != 0) {
return ErrorCode.CANT_REMOVE;
}
/* check that it's not including another file */
if (fileIncludeMgr.getFilesIncludedBy(pathId).length != 0) {
return ErrorCode.CANT_REMOVE;
}
/*
* All checks have passed, so remove from the file name cache, in
* case it's there.
*/
fileNameCache.remove(getParentPath(pathId), getBaseName(pathId));
/* now remove the entry from the "files" table (marking it as trashed) */
try {
trashPathPrepStmt.setInt(1, 1);
trashPathPrepStmt.setInt(2, pathId);
db.executePrepUpdate(trashPathPrepStmt);
} catch (SQLException e) {
throw new FatalBuildStoreError("Error in SQL: " + e);
}
/* notify listeners */
notifyListeners(pathId, IFileMgrListener.PATH_REMOVED);
/* success, the path has been removed */
return ErrorCode.OK;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#revivePath(int)
*/
@Override
public int revivePathFromTrash(int pathId) {
/* untrash the file record */
try {
trashPathPrepStmt.setInt(1, 0);
trashPathPrepStmt.setInt(2, pathId);
db.executePrepUpdate(trashPathPrepStmt);
} catch (SQLException e) {
throw new FatalBuildStoreError("Error in SQL: " + e);
}
/* notify listeners */
notifyListeners(pathId, IFileMgrListener.NEW_PATH);
return ErrorCode.OK;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#isPathTrashed(int)
*/
@Override
public boolean isPathTrashed(int pathId) {
Integer results[] = null;
try {
pathIsTrashPrepStmt.setInt(1, pathId);
results = db.executePrepSelectIntegerColumn(pathIsTrashPrepStmt);
} catch (SQLException e) {
throw new FatalBuildStoreError("Error in SQL: " + e);
}
/* file isn't even known - let's assume it's trashed */
if (results.length != 1) {
return true;
}
/* if "trashed" field is 1, then the path is trashed */
return results[0] == 1;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#isAncestorOf(int, int)
*/
@Override
public boolean isAncestorOf(int directoryId, int pathId) {
/* iterate upwards from pathId to @root, looking for directoryId */
while (true) {
int parentId = getParentPath(pathId);
if (parentId == directoryId) {
return true;
}
/* did we reach the @root without finding the ancestor? */
if (parentId == pathId) {
return false;
}
pathId = parentId;
}
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#getBuildStore()
*/
@Override
public IBuildStore getBuildStore() {
return buildStore;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#addListener(com.buildml.model.IFileMgrListener)
*/
@Override
public void addListener(IFileMgrListener listener) {
listeners.add(listener);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IFileMgr#removeListener(com.buildml.model.IFileMgrListener)
*/
@Override
public void removeListener(IFileMgrListener listener) {
listeners.remove(listener);
}
/*=====================================================================================*
* PACKAGE METHODS
*=====================================================================================*/
/**
* Extra initialization that can only happen all other managers are initialized.
*/
/* package */ void initPass2() {
/*
* We need to refer to all these helper objects, to see if they
* use the path we're trying to delete.
*/
actionMgr = buildStore.getActionMgr();
actionTypeMgr = buildStore.getActionTypeMgr();
fileAttrMgr = buildStore.getFileAttributeMgr();
fileIncludeMgr = buildStore.getFileIncludeMgr();
/* fetch the slot ID for "Directory" */
SlotDetails slotDetails =
actionTypeMgr.getSlotByName(ActionTypeMgr.BUILTIN_SHELL_COMMAND_ID, "Directory");
dirSlotId = slotDetails.slotId;
}
/*=====================================================================================*
* PRIVATE METHODS
*=====================================================================================*/
/**
* Notify any registered listeners about our change in state.
* @param pathId The path that has changed.
* @param how The way in which the path changed (see {@link IFileMgrListener}).
*/
private void notifyListeners(int pathId, int how) {
/*
* Make a copy of the listeners list, otherwise a registered listener can't remove
* itself from the list within the pathChangeNotification() method.
*/
IFileMgrListener listenerCopy[] = listeners.toArray(new IFileMgrListener[listeners.size()]);
for (int i = 0; i < listenerCopy.length; i++) {
listenerCopy[i].pathChangeNotification(pathId, how);
}
}
/*-------------------------------------------------------------------------------------*/
/**
* Helper method for addDirectory, addFile and addSymlink. Adds a new path, of the
* specified type (pathType) to the database. If the path already exists in
* the FileMgr object, return the ID of the existing path rather than adding
* a new entry.
*
* @param pathType The type of the path to be added (TYPE_DIR, TYPE_FILE, TYPE_SYMLINK).
* @param fullPathName The full absolute path name to be added.
* @return The new (or existing) path's ID, or ErrorCode.BAD_PATH if for some reason
* the path isn't valid.
*/
private int addPath(PathType pathType, String fullPathName) {
/* parse the path name and separate it into root and path components */
String rootAndPath[] = getRootAndPath(fullPathName);
String rootName = rootAndPath[0];
String pathName = rootAndPath[1];
/* path's must be absolute (relative to the root of this name space */
if (!PathUtils.isAbsolutePath(pathName)) {
return ErrorCode.BAD_PATH;
}
/* convert the absolute path in /-separated path components */
String components[] = PathUtils.tokenizePath(pathName);
IPackageRootMgr pkgRootMgr = buildStore.getPackageRootMgr();
int parentId = pkgRootMgr.getRootPath(rootName);
if (parentId == ErrorCode.NOT_FOUND) {
return ErrorCode.BAD_PATH;
}
/* for each path name component, make sure it's added, then move to the next */
int len = components.length - 1;
for (int i = 0; i <= len; i++) {
PathType thisType = (i == len) ? pathType : PathType.TYPE_DIR;
int childId = addChildOfPathHelper(parentId, thisType, components[i]);
parentId = childId;
/*
* If the path we just added didn't have the correct type, that's a problem.
* For example, if we thought we added a directory, but it was already added
* as a file, return ErrorCode.BAD_PATH;
*/
if (childId < 0) {
return ErrorCode.BAD_PATH;
}
}
/* notify all listeners */
notifyListeners(parentId, IFileMgrListener.NEW_PATH);
return parentId;
}
/*-------------------------------------------------------------------------------------*/
/**
* Return the given path's parent path ID, and the path's own name.
*
* @param pathId The ID of the path to query.
* @return An array of three Objects:
* <ul>
* <li>The first is the parent's path ID (Integer)</li>
* <li>The second is true if this is a directory, else false.</li>
* <li>The third is the path's own name (String).</li>
* </ul>
* Return null if there's no matching record.
*/
private Object[] getPathDetails(int pathId) {
Object result[] = new Object[3];
try {
findPathDetailsPrepStmt.setInt(1, pathId);
ResultSet rs = db.executePrepSelectResultSet(findPathDetailsPrepStmt);
if (rs.next()){
result[0] = rs.getInt(1);
result[1] = intToPathType(rs.getInt(2));
result[2] = rs.getString(3);
rs.close();
} else {
/* error - there was no record, so the pathId must be invalid */
return null;
}
} catch (SQLException e) {
throw new FatalBuildStoreError("SQL error", e);
}
return result;
}
/*-------------------------------------------------------------------------------------*/
/**
* Helper function for translating from an ordinal integer to a PathType. This is the
* opposite of PathType.ordinal().
*
* @param pathTypeNum The ordinal value of a PathType value.
* @return The corresponding PathType value.
* @throws FatalBuildStoreError if the ordinal value is out of range.
*/
private PathType intToPathType(int pathTypeNum)
throws FatalBuildStoreError {
switch (pathTypeNum) {
case 0: return PathType.TYPE_INVALID;
case 1: return PathType.TYPE_DIR;
case 2: return PathType.TYPE_FILE;
case 3: return PathType.TYPE_SYMLINK;
default:
throw new FatalBuildStoreError("Invalid value found in pathType field: " + pathTypeNum);
}
}
/*-------------------------------------------------------------------------------------*/
/**
* A common method used by all getPathName() variants.
*
* @param pathId ID of the path to display.
* @param showRoots True if we should show roots, else False.
* @param pkgId ID of the package that this file belongs to (or should be considered as part of).
* @return The returned path name, possibly with roots.
*/
private String getPathNameCommon(int pathId, boolean showRoots, int pkgId) {
/* is pathId a valid path? */
if (getPathType(pathId) == PathType.TYPE_INVALID) {
return null;
}
/* we accumulate the path string in a string builder */
StringBuilder sb = new StringBuilder();
/* if we're at the root, simply return /, else recurse */
if (pathId == 0) {
if (showRoots) {
sb.append("@root");
} else {
sb.append('/');
}
} else {
/* determine which package this file is in */
IPackageRootMgr pkgRootMgr = buildStore.getPackageRootMgr();
int workspaceRootPathId = pkgRootMgr.getWorkspaceRoot();
int pkgRootPathId = 0;
String pkgRootName = null;
/* error case: package not available - disable showRoots */
if (workspaceRootPathId == ErrorCode.NOT_FOUND) {
showRoots = false;
}
/* else, determine path of package root */
else {
pkgRootPathId = pkgRootMgr.getPackageRoot(pkgId, IPackageRootMgr.SOURCE_ROOT);
pkgRootName = pkgRootMgr.getPackageRootName(pkgId, IPackageRootMgr.SOURCE_ROOT);
}
getPathNameHelper(sb, pathId, showRoots,
workspaceRootPathId, pkgRootPathId, pkgRootName);
}
return sb.toString();
}
/*-------------------------------------------------------------------------------------*/
/**
* A helper method for getPathName(). This method is called recursively as we traverse from
* the path ID in question, right up to the root path. The recursive step moves up the
* path hierarchy until either / is reached or one of the path's is a "root". At that point,
* the recursion unwinds back to the start as it appends the path names to the result string.
*
* @param sb The StringBuffer we'll append path component names onto (as we recurse).
* @param pathId The ID of the path we're currently looking at (whose name we'll append to sb).
* @param showRoots True if we should return a file system root (e.g. "@root") in the path name.
* @param workspaceRootPathId Path ID of the "@workspace" root.
* @param pkgRootPathId PathID of the root for this file's package.
* @param pkgRootName Name of this file's package.
*/
private void getPathNameHelper(StringBuilder sb, int pathId, boolean showRoots,
int workspaceRootPathId, int pkgRootPathId,
String pkgRootName) {
/*
* Get the details of this path, including it's parent ID, its name and whether
* or not it's a root.
*/
Object pathDetails[] = getPathDetails(pathId);
int parentId = (Integer)pathDetails[0];
String name = (String)pathDetails[2];
/*
* If we're showing root names, and we've reached one, display it. This can
* be @root, @workspace, or the path's own package root.
*/
if (showRoots) {
if (pathId == 0) {
sb.append("@root");
return;
} else if (pathId == workspaceRootPathId) {
sb.append("@workspace");
return;
} else if (pathId == pkgRootPathId) {
sb.append('@');
sb.append(pkgRootName);
return;
}
}
/*
* If we're not showing roots, we terminate recursion at the / path.
*/
else if (!showRoots && name.equals("/")){
return;
}
/*
* Now the recursion has terminated and we start moving back along the sequence
* of paths until we reach the original path again. At each step, we'll append
* the path component onto the full result string.
*/
getPathNameHelper(sb, parentId, showRoots,
workspaceRootPathId, pkgRootPathId, pkgRootName);
sb.append("/");
sb.append(name);
}
/*-------------------------------------------------------------------------------------*/
/**
* A helper function for fetching the named child of a specified path, along with the
* path type of that child.
*
* @param parentId The parent path's ID.
* @param childName The name of the child path to search for within this parent.
* @return A Object[2] array, where Object[0] is a Integer containing the path ID, and
* Object[1] is a PathType object for the child.
*/
private Object[] getChildOfPathWithType(int parentId, String childName) {
Object result[];
/*
* Start by looking in the in-memory cache to see if it's there.
*/
PathNameCacheValue cacheValue = fileNameCache.get(parentId, childName);
if (cacheValue != null) {
return new Object[] { cacheValue.getChildPathId(), intToPathType(cacheValue.getChildType())};
}
// TODO: what happens if the mapping changes?
/*
* Not in cache, try the database
*/
try {
findChildPrepStmt.setInt(1, parentId);
findChildPrepStmt.setString(2, childName);
ResultSet rs = db.executePrepSelectResultSet(findChildPrepStmt);
/* if there's a result, return it and add it to the cache for faster access next time */
if (rs.next()){
int childId = rs.getInt(1);
int childType = rs.getInt(2);
result = new Object[] { Integer.valueOf(childId), intToPathType(childType)};
fileNameCache.put(parentId, childName, childId, childType);
}
/* else, no result = no child */
else {
result = null;
}
rs.close();
return result;
} catch (SQLException e) {
throw new FatalBuildStoreError("SQL problem in prepared statement", e);
}
}
/*-------------------------------------------------------------------------------------*/
/**
* Given a path name of the format "@root/absolute/path/name", split out the root and
* path components. If no root component is provided, default to "root". Note: minimal
* error checking is done on the root and path names, so they should be validated by this
* method's caller.
*
* @param fullPathName The full path string, possibly starting with a root name.
* @return A String[2] array, where element 0 is the root name, and element 1 is the
* path name.
*/
private String[] getRootAndPath(String fullPathName) {
/* the default values, in case no @root is provided */
String rootName = "root";
String pathName = fullPathName;
/*
* See if the path name starts with @, if so,
* split the full path into "root" and "path name" components.
* The root part of the name will end when a "/" is seen. If
* there's no "/", then the whole fullPathName string is the
* root name.
*/
if (fullPathName.startsWith("@")){
int slashIndex = fullPathName.indexOf('/');
if (slashIndex != -1){
rootName = fullPathName.substring(1, slashIndex);
pathName = fullPathName.substring(slashIndex);
} else {
rootName = fullPathName.substring(1);
pathName = "/";
}
}
String resultPair[] = new String[2];
resultPair[0] = rootName;
resultPair[1] = pathName;
return resultPair;
}
/*-------------------------------------------------------------------------------------*/
/**
* This is a helper function that does most of the work of the addChildOfPath() method,
* which is a public method. This helper function exists solely because we don't always
* want the extra error checking provided by addChildOfPath(), so sometimes we'll
* call this method directly.
*
* @param parentId The ID of the parent path.
* @param pathType The type of the path to be added (directory, file, etc).
* @param childName The name of the child path to add.
* @return The ID of the child path, or ErrorCode.ONLY_ONE_ALLOWED if the path already
* exists, but was of the wrong type.
*/
private int addChildOfPathHelper(int parentId, PathType pathType, String childName) {
IPackageMgr pkgMgr = buildStore.getPackageMgr();
int lastRowId;
/*
* Search for the path ID and path type for a child of "parentId" that has
* the name "childName". This is similar to the getChildOfPath() operation,
* but we also fetch the path's type.
*/
Object childPathAndType[] = getChildOfPathWithType(parentId, childName);
/* If child isn't yet present, we need to add it */
if (childPathAndType == null) {
/*
* TODO: fix the race condition here - there's a small chance that somebody
* else has already added it. Not thread safe.
*/
try {
insertChildPrepStmt.setInt(1, parentId);
insertChildPrepStmt.setInt(2, pathType.ordinal());
insertChildPrepStmt.setString(3, childName);
db.executePrepUpdate(insertChildPrepStmt);
lastRowId = db.getLastRowID();
if (lastRowId >= MAX_FILES) {
throw new FatalBuildStoreError("Exceeded maximum file number: " + MAX_FILES);
}
/* insert the default package membership values */
insertPackageMemberPrepStmt.setInt(1, IPackageMemberMgr.TYPE_FILE);
insertPackageMemberPrepStmt.setInt(2, lastRowId);
insertPackageMemberPrepStmt.setInt(3, pkgMgr.getImportPackage());
insertPackageMemberPrepStmt.setInt(4, IPackageMemberMgr.SCOPE_NONE);
if (db.executePrepUpdate(insertPackageMemberPrepStmt) != 1) {
throw new FatalBuildStoreError("Unable to insert new record into packageMembers table");
}
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
} catch (FatalBuildStoreError e) {
/*
* This is likely to happen if there's already a database row with the same name
* (which means there's a path with the same name that was trashed).
*/
return ErrorCode.BAD_PATH;
}
return lastRowId;
}
/* else, it exists, but we need to make sure it's the correct type of path */
else if ((PathType)childPathAndType[1] != pathType) {
return ErrorCode.ONLY_ONE_ALLOWED;
}
/* else, return the existing child ID */
else {
return (Integer)childPathAndType[0];
}
}
/*-------------------------------------------------------------------------------------*/
}