/******************************************************************************* * Copyright (c) 2012 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 java.util.SortedSet; import java.util.Stack; import java.util.TreeSet; import com.buildml.model.IActionMgr; import com.buildml.model.IBuildStore; import com.buildml.model.IFileMgr; import com.buildml.model.IActionMgr.FileAccess; import com.buildml.model.IFileMgr.PathType; import com.buildml.model.IPackageMemberMgr.MemberDesc; import com.buildml.model.IPackageMgr; import com.buildml.model.types.ActionSet; import com.buildml.model.undo.ActionUndoOp; import com.buildml.model.undo.MultiUndoOp; import com.buildml.refactor.CanNotRefactorException; import com.buildml.refactor.CanNotRefactorException.Cause; import com.buildml.refactor.IImportRefactorer; import com.buildml.utils.errors.ErrorCode; /** * An implementation of the IImportRefactorer class. See that class for usage details. * * @author Peter Smith <psmith@arapiki.com> */ public class ImportRefactorer implements IImportRefactorer { /*=====================================================================================* * TYPES/FIELDS *=====================================================================================*/ /** The BuildStore that all our refactorings will operate on. */ private IBuildStore buildStore; /** The FileMgr used by these refactorings. */ private IFileMgr fileMgr; /** The ActionMgr used by these refactorings. */ private IActionMgr actionMgr; /** The PackageMgr used by these refactorings. */ private IPackageMgr pkgMgr; /*=====================================================================================* * CONSTRUCTORS *=====================================================================================*/ /** * Create a new ImportRefactorer object which will perform operations on the * specified BuildStore * * @param buildStore The BuildStore on which to perform refactoring operations. */ public ImportRefactorer(IBuildStore buildStore) { this.buildStore = buildStore; this.fileMgr = buildStore.getFileMgr(); this.actionMgr = buildStore.getActionMgr(); this.pkgMgr = buildStore.getPackageMgr(); } /*=====================================================================================* * PUBLIC METHODS *=====================================================================================*/ /* (non-Javadoc) * @see com.buildml.refactor.IImportRefactorer#deleteFile(int) */ @Override public void deletePath(MultiUndoOp multiOp, int pathId, boolean alsoDeleteAction, boolean removeFromAction) throws CanNotRefactorException { /* the path must not be a non-empty directory - otherwise give an error */ PathType pathType = fileMgr.getPathType(pathId); if (pathType == PathType.TYPE_DIR) { if (fileMgr.getChildPaths(pathId).length != 0) { throw new CanNotRefactorException(Cause.DIRECTORY_NOT_EMPTY, new Integer[] { pathId }); } } /* the helper does most of the work */ ImportRefactorerUtils.deletePathHelper(buildStore, pathId, alsoDeleteAction, removeFromAction, multiOp); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.refactor.IImportRefactorer#deleteFileTree(int) */ @Override public void deletePathTree(MultiUndoOp multiOp, int dirId, boolean alsoDeleteActions, boolean removeFromAction) throws CanNotRefactorException { /* the helper does most of the work */ ImportRefactorerUtils.deletePathTreeHelper(buildStore, dirId, alsoDeleteActions, removeFromAction, multiOp); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.refactor.IImportRefactorer#makeActionAtomic(int) */ @Override public void makeActionAtomic(MultiUndoOp multiOp, int actionId) throws CanNotRefactorException { /* if the action has been trashed, that's a problem */ if (actionMgr.isActionTrashed(actionId)) { throw new CanNotRefactorException(Cause.ACTION_IS_TRASHED, new Integer[] { actionId }); } /* if this action has no children, it's already atomic */ Integer children[] = actionMgr.getChildren(actionId); if (children.length == 0) { return; } /* figure out the complete list of actions that we'll be merging */ List<Integer> actionsToMerge = new ArrayList<Integer>(); ImportRefactorerUtils.collectChildren(actionMgr, actionId, actionsToMerge); /* obtain the complete list of file access across those actions */ FileAccess[] fileAccesses = actionMgr.getSequencedFileAccesses(actionsToMerge.toArray(new Integer[0])); /* schedule all existing file accesses for removal */ for (FileAccess fileAccess : fileAccesses) { ActionUndoOp actionOp = new ActionUndoOp(buildStore, fileAccess.actionId); actionOp.recordRemovePathAccess(fileAccess.seqno, fileAccess.pathId, fileAccess.opType); multiOp.add(actionOp); } /* schedule those same file access to be performed by the parent action */ for (FileAccess fileAccess : fileAccesses) { ActionUndoOp actionOp = new ActionUndoOp(buildStore, actionId); actionOp.recordAddPathAccess(fileAccess.seqno, fileAccess.pathId, fileAccess.opType); multiOp.add(actionOp); } /* move the child actions to the trash */ for (int childActionId : actionsToMerge) { if (childActionId != actionId) { ActionUndoOp actionOp = new ActionUndoOp(buildStore, childActionId); actionOp.recordMoveToTrash(); multiOp.add(actionOp); } } } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.refactor.IImportRefactorer#deleteAction(int) */ @Override public void deleteAction(MultiUndoOp multiOp, int actionId) throws CanNotRefactorException { /* check that we're not trying to delete an invalid action, or the root action */ int parentActionId = actionMgr.getParent(actionId); Integer actions[] = new Integer[] { actionId }; if ((parentActionId == ErrorCode.BAD_VALUE) || (parentActionId == ErrorCode.NOT_FOUND)) { throw new CanNotRefactorException(Cause.INVALID_ACTION, actions); } /* * Check that the action to be deleted is not "in use". This will throw an * exception if it's in use. */ ImportRefactorerUtils.validateActionsNotInUse(actionMgr, actions); /* re-parent all of the immediate child actions */ Integer childActions[] = actionMgr.getChildren(actionId); for (int childActionId : childActions) { ActionUndoOp actionOp = new ActionUndoOp(buildStore, childActionId); actionOp.recordParentChange(actionId, parentActionId); multiOp.add(actionOp); } /* schedule the action to be trashed (along with file-access links) */ ImportRefactorerUtils.scheduleRemoveAction(buildStore, multiOp, actions); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.refactor.IImportRefactorer#mergeActions(int[]) */ @Override public void mergeActions(MultiUndoOp multiOp, ActionSet actionIds) throws CanNotRefactorException { /* check that all actions are valid and non-atomic */ List<Integer> invalidActions = new ArrayList<Integer>(); List<Integer> trashedActions = new ArrayList<Integer>(); List<Integer> nonAtomicActions = new ArrayList<Integer>(); SortedSet<Integer> actionIdSet = new TreeSet<Integer>(); for (int actionId : actionIds) { if (!actionMgr.isActionValid(actionId)) { invalidActions.add(actionId); } else if (actionMgr.isActionTrashed(actionId)) { trashedActions.add(actionId); } else if (actionMgr.getChildren(actionId).length != 0) { nonAtomicActions.add(actionId); } actionIdSet.add(actionId); } if (invalidActions.size() != 0) { throw new CanNotRefactorException( Cause.INVALID_ACTION, invalidActions.toArray(new Integer[0])); } if (trashedActions.size() != 0) { throw new CanNotRefactorException( Cause.ACTION_IS_TRASHED, trashedActions.toArray(new Integer[0])); } if (nonAtomicActions.size() != 0) { throw new CanNotRefactorException( Cause.ACTION_NOT_ATOMIC, nonAtomicActions.toArray(new Integer[0])); } /* if there's only one action (or no actions), there's nothing to do */ if (actionIds.size() < 2) { return; } /* obtain a sorted array of actions, with duplicates removed */ Integer actionsArray[] = actionIdSet.toArray(new Integer[actionIdSet.size()]); int firstActionId = actionsArray[0]; /* * Foreach action, schedule the file-access links for removal. This will ensure * that the file-access links can be recovered after an "undo", even if some * of the links are dissolved due to merging the actions (i.e. temporary files * being dissolved). */ FileAccess[] fileAccesses = actionMgr.getSequencedFileAccesses(actionsArray); for (FileAccess fileAccess : fileAccesses) { ActionUndoOp actionOp = new ActionUndoOp(buildStore, fileAccess.actionId); actionOp.recordRemovePathAccess(fileAccess.seqno, fileAccess.pathId, fileAccess.opType); multiOp.add(actionOp); } /* * Schedule all the file-access links to be added to the first action. This may * cause some of the links to be dissolved (if they're repetitions, or if they * cancel each other out). */ for (FileAccess fileAccess : fileAccesses) { ActionUndoOp actionOp = new ActionUndoOp(buildStore, firstActionId); actionOp.recordAddPathAccess(fileAccess.seqno, fileAccess.pathId, fileAccess.opType); multiOp.add(actionOp); } /* * Schedule the shell command for the first action to be the concatenation of * all shell commands, across all merged actions. */ StringBuilder newShellCommand = new StringBuilder(); String firstActionCommand = null; for (int i = 0; i < actionsArray.length; i++) { String actionCommand = (String) actionMgr.getSlotValue(actionsArray[i], IActionMgr.COMMAND_SLOT_ID); if (i == 0) { firstActionCommand = actionCommand; } else { /* insert \n between merged commands */ newShellCommand.append('\n'); } newShellCommand.append(actionCommand); } ActionUndoOp actionOp = new ActionUndoOp(buildStore, firstActionId); actionOp.recordSlotChange(IActionMgr.COMMAND_SLOT_ID, firstActionCommand, newShellCommand.toString()); multiOp.add(actionOp); /* * Schedule all actions to be trashed, except for the first. */ for (int i = 1; i < actionsArray.length; i++) { ActionUndoOp deleteActionOp = new ActionUndoOp(buildStore, actionsArray[i]); deleteActionOp.recordMoveToTrash(); multiOp.add(deleteActionOp); } } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.refactor.IImportRefactorer#moveMembersToPackage() */ @Override public void moveMembersToPackage(MultiUndoOp multiOp, int destPkgId, List<MemberDesc> members) throws CanNotRefactorException { MovePackageRefactorer moveRefactor = new MovePackageRefactorer(destPkgId, multiOp, buildStore); moveRefactor.moveMembersToPackage(members); } /*-------------------------------------------------------------------------------------*/ }