/******************************************************************************* * Copyright (c) 2013 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: * psmith - initial API and * implementation and/or initial documentation *******************************************************************************/ package com.buildml.refactor.imports; import java.util.ArrayList; import java.util.List; import com.buildml.model.IActionMgr; import com.buildml.model.IActionMgr.FileAccess; import com.buildml.model.IActionMgr.OperationType; import com.buildml.model.IActionTypeMgr; import com.buildml.model.IBuildStore; import com.buildml.model.IFileGroupMgr; import com.buildml.model.IFileMgr; import com.buildml.model.IFileMgr.PathType; import com.buildml.model.ISlotTypes.SlotDetails; import com.buildml.model.undo.ActionUndoOp; import com.buildml.model.undo.FileUndoOp; import com.buildml.model.undo.MultiUndoOp; import com.buildml.refactor.CanNotRefactorException; import com.buildml.refactor.CanNotRefactorException.Cause; /** * A class of static utility methods to support commands in ImportRefactorer(). * * @author Peter Smith <psmith@arapiki.com> */ public class ImportRefactorerUtils { /*=====================================================================================* * PACKAGE-STATIC METHODS *=====================================================================================*/ /** * Collect together a list of all an action's child action IDs. For complex actions, this * may involve traversing multiple levels of the action tree. * * @param actionMgr The action mgr that owns the actions. * @param actionId The parent action ID. * @param actionsToMerge A list to which the action's descendents will be added. */ /* package */ static void collectChildren(IActionMgr actionMgr, int actionId, List<Integer> actionsToMerge) { Integer children[] = actionMgr.getChildren(actionId); for (int childActionId : children) { collectChildren(actionMgr, childActionId, actionsToMerge); } actionsToMerge.add(actionId); } /*-------------------------------------------------------------------------------------*/ /** * Helper method for validating whether an action (or array of actions) is in use * by downstream files. That is, these actions generate files which may then be used * as input into other actions. This function returns silently if the actions are NOT * in use, or throws an exception if one or more generated files are in used. * * @param actionMgr The IActionMgr we should query from. * @param actions The array of actions that we're testing. * @throws CanNotRefactorException If one or more actions is still in use. */ /* package */ static void validateActionsNotInUse(IActionMgr actionMgr, Integer[] actions) throws CanNotRefactorException { List<Integer> allPathsInUse = new ArrayList<Integer>(); for (int actionId : actions) { /* For each path that this action generates... */ Integer filesWrittenByAction[] = actionMgr.getFilesAccessed(actionId, OperationType.OP_WRITE); for (int writtenPathId : filesWrittenByAction) { /* are there other actions (other than us) that use this generated path? */ // TODO: this should really look at the action IDs, rather than just the length. if (actionMgr.getActionsThatAccess(writtenPathId, OperationType.OP_UNSPECIFIED).length != actions.length) { allPathsInUse.add(writtenPathId); } } } if (allPathsInUse.size() != 0) { throw new CanNotRefactorException(Cause.ACTION_IN_USE, allPathsInUse.toArray(new Integer[0])); } } /*-------------------------------------------------------------------------------------*/ /** * Helper method for removing file accesses from an action. This is used when we're * deleting paths, without deleting the actions that read those paths. Instead, we * simply remove the file access that the action(s) have for the path. * * @param buildStore The IBuildStore we can query for information. * @param multiOp The undo/redo operation that we'll use for scheduling. * @param pathId The path whose accesses must be removed from the reading actions. * @param actions The actions that the file access should be removed from. */ /* package */ static void scheduleRemoveFilesAccessFromAction( IBuildStore buildStore, MultiUndoOp multiOp, int pathId, Integer[] actions) { IActionMgr actionMgr = buildStore.getActionMgr(); /* * Locate all file access (for these actions) that "read" pathId. Add their * removal to our multiOp. */ FileAccess[] fileAccesses = actionMgr.getSequencedFileAccesses(actions); for (FileAccess fileAccess : fileAccesses) { if ((fileAccess.pathId == pathId) && (fileAccess.opType == OperationType.OP_READ)) { ActionUndoOp actionOp = new ActionUndoOp(buildStore, fileAccess.actionId); actionOp.recordRemovePathAccess(fileAccess.seqno, pathId, OperationType.OP_READ); multiOp.add(actionOp); } } } /*-------------------------------------------------------------------------------------*/ /** * Helper method for scheduling an action to be removed. This must also remove any * file-access links that are currently associated with the action. * * @param buildStore The IBuildStore we can query for information. * @param multiOp The undo/redo operation that we'll use for scheduling. * @param actions The actions to be removed. */ /* package */ static void scheduleRemoveAction(IBuildStore buildStore, MultiUndoOp multiOp, Integer[] actions) { IActionMgr actionMgr = buildStore.getActionMgr(); /* in addition to removing actions, we also need to remove generated files */ List<Integer> writtenFilesToRemove = new ArrayList<Integer>(); /* * Remove links between this action and all paths it accesses. We need information on * which operation is used to access the path, so we break this out. */ FileAccess[] fileAccesses = actionMgr.getSequencedFileAccesses(actions); for (FileAccess fileAccess : fileAccesses) { ActionUndoOp actionOp = new ActionUndoOp(buildStore, fileAccess.actionId); actionOp.recordRemovePathAccess(fileAccess.seqno, fileAccess.pathId, fileAccess.opType); multiOp.add(actionOp); if (fileAccess.opType == OperationType.OP_WRITE){ writtenFilesToRemove.add(fileAccess.pathId); } } /* Move the actions into the trash.*/ for (int actionId : actions) { ActionUndoOp actionOp = new ActionUndoOp(buildStore, actionId); actionOp.recordMoveToTrash(); multiOp.add(actionOp); } /* remove all written files - we can only do this once all action-path links are removed. */ for (int writtenPathId : writtenFilesToRemove) { FileUndoOp fileOp = new FileUndoOp(buildStore, writtenPathId); fileOp.recordRemovePath(); multiOp.add(fileOp); } } /*-------------------------------------------------------------------------------------*/ /** * A helper function, shared by deletePath() and deletePathTree(). * * @param buildStore The IBuildStore to query/update. * @param pathId The path to be deleted. * @param alsoDeleteAction True if we should also delete actions that generate the path. * @param removeFromAction If this path is read by an action, remove the path-access from the action. * @param multiOp Undo/redo history item to add operation steps to. * @throws CanNotRefactorException Something went wrong. */ /* package */ static void deletePathHelper(IBuildStore buildStore, int pathId, boolean alsoDeleteAction, boolean removeFromAction, MultiUndoOp multiOp) throws CanNotRefactorException { IActionMgr actionMgr = buildStore.getActionMgr(); IActionTypeMgr actionTypeMgr = buildStore.getActionTypeMgr(); IFileMgr fileMgr = buildStore.getFileMgr(); IFileGroupMgr fileGroupMgr = buildStore.getFileGroupMgr(); /* the path must exist and must not be trashed - otherwise give an error */ PathType pathType = fileMgr.getPathType(pathId); if ((pathType == PathType.TYPE_INVALID) || (fileMgr.isPathTrashed(pathId))) { throw new CanNotRefactorException(Cause.INVALID_PATH, pathId); } /* the path must not be a member of any file groups */ Integer containingGroups[] = fileGroupMgr.getSourceGroupsContainingPath(pathId); if (containingGroups.length != 0){ throw new CanNotRefactorException(Cause.FILE_STILL_IN_GROUP, containingGroups); } /* * If "removeFromAction" is false, then the path must not be used as input to an * action - otherwise give an error. */ Integer actionsReadingPath[] = actionMgr.getActionsThatAccess(pathId, OperationType.OP_READ); if ((actionsReadingPath.length != 0) && !removeFromAction) { throw new CanNotRefactorException(Cause.PATH_IN_USE, actionsReadingPath); } /* the path must not be the "current directory" for any actions */ Integer actionsExecutingInDir[] = actionMgr.getActionsWhereSlotEquals(IActionMgr.DIRECTORY_SLOT_ID, pathId); if (actionsExecutingInDir.length != 0) { throw new CanNotRefactorException(Cause.DIRECTORY_CONTAINS_ACTIONS, actionsExecutingInDir); } /* * If there are actions that generate (not READ) this path, then we must delete those * actions too. However, only delete them if "alsoDeleteActions" is set. */ Integer actionsUsingPath[] = actionMgr.getActionsThatAccess(pathId, OperationType.OP_UNSPECIFIED); Integer actionsWritingPath[] = subtractArrays(actionsUsingPath, actionsReadingPath); /* remove OP_READ actions */ if ((actionsWritingPath.length != 0) && !alsoDeleteAction) { throw new CanNotRefactorException(Cause.PATH_IS_GENERATED, actionsWritingPath); } /* * Make sure that all the actions that write to the path are atomic. */ for (int actionId: actionsWritingPath) { if (actionMgr.getChildren(actionId).length != 0) { throw new CanNotRefactorException(Cause.ACTION_NOT_ATOMIC, actionId); } } /* * For the action (or actions) that we'll now need to delete, check to see if any of their generated * paths are used as input into other actions. If so, the output paths are considered "in use". * Calling this method will throw a CanNotRefactorException if anything goes wrong. */ validateActionsNotInUse(actionMgr, actionsWritingPath); /* * All is good, now go ahead and start deleting things. We can now build up a history item * of the changes to be made to the BuildStore. */ /* * If this path is read by any actions, remove that file accesses from the actions. * This implies that removeFromActions is true (we've already check this above). */ scheduleRemoveFilesAccessFromAction(buildStore, multiOp, pathId, actionsReadingPath); /* remove the actions, and any associated file accesses */ scheduleRemoveAction(buildStore, multiOp, actionsWritingPath); /* * If we didn't already delete this path's generating action (and therefore all the paths it generates), * we'll still need to explicitly delete the path (this is the case where it's an unused path we're deleting). */ if (actionsWritingPath.length == 0) { FileUndoOp fileOp = new FileUndoOp(buildStore, pathId); fileOp.recordRemovePath(); multiOp.add(fileOp); } } /*-------------------------------------------------------------------------------------*/ /** * A helper method for deletePathTree(). Performs a bottom up traversal of a directory * hierarchy. * * @param buildStore The IBuildStore to query/update. * @param pathId The path (file or directory) to be deleted. * @param alsoDeleteActions True if we should also delete actions that generate the path. * @param removeFromAction If this path is read by an action, remove the path-access from the action. * @param multiOp Undo/redo history item to add operation steps to. * @throws CanNotRefactorException */ /* package */ static void deletePathTreeHelper(IBuildStore buildStore, int pathId, boolean alsoDeleteActions, boolean removeFromAction, MultiUndoOp multiOp) throws CanNotRefactorException { IFileMgr fileMgr = buildStore.getFileMgr(); /* delete children first */ Integer children[] = fileMgr.getChildPaths(pathId); for (int childId : children) { deletePathTreeHelper(buildStore, childId, alsoDeleteActions, removeFromAction, multiOp); } /* now delete the current path */ deletePathHelper(buildStore, pathId, alsoDeleteActions, removeFromAction, multiOp); } /*=====================================================================================* * PRIVATE METHODS *=====================================================================================*/ /** * Subtract (remove) the members of the second array from the first array. A totally * new array is returned (neither of the input arrays are modified). * * @param firstArray The array of Integer that the secondArray will be removed from. * @param secondArray The array of Integer to subtract from firstArray. * @return The result of subtracting the members of secondArray from firstArray. */ private static Integer[] subtractArrays(Integer[] firstArray, Integer[] secondArray) { List<Integer> resultArray = new ArrayList<Integer>(); for (int firstMember : firstArray) { boolean found = false; for (int secondMember : secondArray) { if (firstMember == secondMember) { found = true; break; } } if (!found) { resultArray.add(firstMember); } } return resultArray.toArray(new Integer[resultArray.size()]); } /*-------------------------------------------------------------------------------------*/ }