/**
   * Given an actionId, move the action, and possibly all its predecessors to the new package. If
   * necessary, an output file group is created and also moved to the new package. So too are all
   * the files in that groups.
   *
   * @param actionId ID of the action to be moved.
   * @return ID of the output file group, or ErrorCode.NOT_FOUND if there's no output group.
   * @throws CanNotRefactorException If something goes wrong.
   */
  private int moveActionToPackage(int actionId) throws CanNotRefactorException {

    /* first, check to see if this action is already imported this package - if so, we're done */
    Object existingMap = actionCache.get(Integer.valueOf(actionId));
    if (existingMap instanceof Integer) {
      return (Integer) existingMap;
    }

    /* validate that the action is atomic */
    Integer children[] = actionMgr.getChildren(actionId);
    if (children.length != 0) {
      throw new CanNotRefactorException(Cause.ACTION_NOT_ATOMIC, actionId);
    }

    /*
     * The action is not yet imported, and it is atomic. Figure out its current package
     * and prepare to move it to the new package.
     */
    int fileGroupId = ErrorCode.NOT_FOUND;
    PackageDesc desc = pkgMemberMgr.getPackageOfMember(IPackageMemberMgr.TYPE_ACTION, actionId);
    if (desc == null) {
      throw new FatalError("Can't retrieve action's current package");
    }

    /* Create UndoOp for changing the action's package */
    ActionUndoOp actionOp = new ActionUndoOp(buildStore, actionId);
    actionOp.recordPackageChange(desc.pkgId, destPkgId);
    multiOp.add(actionOp);

    /* Create a new file group containing all the files that this action generates */
    Integer writtenFiles[] = actionMgr.getFilesAccessed(actionId, OperationType.OP_WRITE);
    if (writtenFiles.length > 0) {
      fileGroupId = createSourceFileGroup(Arrays.asList(writtenFiles));

      /* Connect the "output" slot from the action to this new file group */
      ActionUndoOp slotOp = new ActionUndoOp(buildStore, actionId);
      slotOp.recordSlotChange(IActionMgr.OUTPUT_SLOT_ID, null, fileGroupId);
      multiOp.add(slotOp);
    }

    /* compute/generate/move all the predecessor actions or file groups */
    computeInputActions(actionId);

    /*
     * Store actionId/fileGroupId in our cache, to avoid doing this import again
     * and ending up with multiple output file groups when only one is required.
     */
    actionCache.put(actionId, fileGroupId);

    /* return the ID of the output file group */
    return fileGroupId;
  }
  /**
   * For the specified action, identify (or generate) the necessary input file groups, which may
   * involve recursively moving predecessor actions into our destination package.
   *
   * @param actionId ID of the action to be recursively dealt with.
   * @throws CanNotRefactorException Something went wrong.
   */
  private void computeInputActions(int actionId) throws CanNotRefactorException {

    /* compute the list of input files that are read by this action */
    Integer readFiles[] = actionMgr.getFilesAccessed(actionId, OperationType.OP_READ);

    /* From this list of input files, we need to track the loose files, the bad files and the actions */
    List<Integer> looseMembers = new ArrayList<Integer>();
    List<Integer> badFiles = new ArrayList<Integer>();
    List<Integer> actions = new ArrayList<Integer>();

    /* for each file, figure out which action generates it, or perhaps it's a loose file? */
    for (int i = 0; i < readFiles.length; i++) {
      int fileId = readFiles[i];

      /* if any actions "modify" (read and then write) this file, that's an error */
      Integer[] modifyingActions =
          actionMgr.getActionsThatAccess(fileId, OperationType.OP_MODIFIED);
      if (modifyingActions.length > 0) {
        badFiles.add(fileId);
      } else {
        /* figure out how this file is generated (if it is at all) */
        Integer generatorActions[] = actionMgr.getActionsThatAccess(fileId, OperationType.OP_WRITE);
        int generatorLength = generatorActions.length;

        /* no generating action, so it's a "loose" file */
        if (generatorLength == 0) {
          looseMembers.add(fileId);
        }

        /* With one generator action, record that action, if it's not already recorded */
        else if (generatorLength == 1) {
          if (!actions.contains(generatorActions[0])) {
            actions.add(generatorActions[0]);
          }
        }

        /* files with multiple generators are a problem */
        else {
          badFiles.add(fileId);
        }
      }
    }

    /*
     * Were there any files that were generated by multiple actions (or modified by an action).
     */
    if (badFiles.size() != 0) {
      throw new CanNotRefactorException(Cause.FILE_IS_MODIFIED, badFiles.toArray(new Integer[0]));
    }

    /*
     * We now have a list of actions (with no duplicates) that are known to generate the files
     * that we need as input to the current action (actionId). Recursively deal with those
     * actions, and use their output file groups as our input (possibly using a merge file group
     * and filters).
     */
    List<Integer> fileGroups = new ArrayList<Integer>();

    /* If there are any loose files, create a file group and populate it with those files */
    if (looseMembers.size() > 0) {
      int looseFileGroupId = createSourceFileGroup(looseMembers);
      fileGroups.add(looseFileGroupId);
    }

    /*
     * For each generating action, deal with it recursively, then record the output file group.
     * By the time this loop is done, we should have all the predecessor actions/groups scheduled
     * to be moved to destPkgId. The fileGroups list will contain the complete list of file
     * groups generated by these sub actions.
     */
    for (int subActionId : actions) {
      int inputFileGroupId = moveActionToPackage(subActionId);
      if (inputFileGroupId != ErrorCode.NOT_FOUND) {
        if (!fileGroups.contains(inputFileGroupId)) {
          fileGroups.add(inputFileGroupId);
        }
      }
    }

    /* join the upstream file group(s) to this action's input slot, if there are any groups. */
    int numUpstreamFileGroups = fileGroups.size();
    if (numUpstreamFileGroups >= 1) {
      int inputFileGroupId;

      /*
       * a single input group - possibly with a filter inserted to eliminate any additional
       * files that are in the group (generated by the action), but aren't required by
       * the action.
       */
      if (numUpstreamFileGroups == 1) {
        inputFileGroupId = createFilterIfNeeded(fileGroups.get(0), readFiles);
      }
      /* create a merge group */
      else {
        inputFileGroupId = createMergeFileGroup(fileGroups, readFiles);
      }

      ActionUndoOp slotOp = new ActionUndoOp(buildStore, actionId);
      slotOp.recordSlotChange(IActionMgr.INPUT_SLOT_ID, null, inputFileGroupId);
      multiOp.add(slotOp);
    }
  }