/*******************************************************************************
* Copyright (c) 2011 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.types;
import com.buildml.model.IActionMgr;
import com.buildml.model.IBuildStore;
import com.buildml.model.IPackageMemberMgr;
import com.buildml.model.IPackageMgr;
import com.buildml.model.IReportMgr;
import com.buildml.utils.errors.ErrorCode;
import com.buildml.utils.types.IntegerTreeSet;
/**
* Implements an unordered set of action IDs. This is used in numerous places
* where a collection of actions must be grouped together into a single unit.
*
* The ActionSet data type is different from a regular set, since the parent/child
* relationship between entries is maintained.
*
* @author "Peter Smith <psmith@arapiki.com>"
*/
public class ActionSet extends IntegerTreeSet {
/*=====================================================================================*
* TYPES/FIELDS
*=====================================================================================*/
/**
* The ActionMgr object that contains the actions referenced in this ActionSet.
*/
private IActionMgr actionMgr;
/*=====================================================================================*
* CONSTRUCTORS
*=====================================================================================*/
/**
* Creates a new ActionSet and initializes it to being empty.
*
* @param actionMgr The ActionMgr object that owns the actions in this set.
*/
public ActionSet(IActionMgr actionMgr) {
/* most of the functionality is provided by the IntegerTreeSet class */
super();
/* except we also need to record our ActionMgr object */
this.actionMgr = actionMgr;
}
/*-------------------------------------------------------------------------------------*/
/**
* Creates a new ActionSet and initializes it from an array of integer values.
*
* @param actionMgr The ActionMgr object that owns the actions in this set.
* @param initValues The initial values to be added to the ActionSet.
*/
public ActionSet(IActionMgr actionMgr, Integer[] initValues) {
/* most of the functionality is provided by the IntegerTreeSet class */
super(initValues);
/* except we also need to record our ActionMgr object */
this.actionMgr = actionMgr;
}
/*=====================================================================================*
* PUBLIC METHODS
*=====================================================================================*/
/**
* Given a action's ID, return the ID of the action's parent.
*
* @param id The ID of the action whose parent we're interested in.
* @return The ID of the action's parent.
*/
@Override
public int getParent(int id) {
int parent = actionMgr.getParent(id);
/* if we've reached the root, our parent is ourselves */
if (parent == ErrorCode.NOT_FOUND) {
return id;
}
return parent;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.utils.types.IntegerTreeSet#isValid(int)
*/
@Override
public boolean isValid(int id) {
return actionMgr.isActionValid(id) && !actionMgr.isActionTrashed(id);
}
/*-------------------------------------------------------------------------------------*/
/**
* Given a action's ID, return the array of children of the action.
* @param id The ID of the action whose children we wish to determine.
*/
@Override
public Integer[] getChildren(int id) {
return actionMgr.getChildren(id);
}
/*-------------------------------------------------------------------------------------*/
/**
* Merge the content of a second ActionSet into this ActionSet.
*
* @param second The second ActionSet.
*/
public void mergeSet(ActionSet second) {
/* ensure the ActionMgr is the same for both ActionSets */
if (actionMgr != second.actionMgr) {
return;
}
super.mergeSet(second);
}
/*-------------------------------------------------------------------------------------*/
/**
* Given the String-formatted specification of a set of actions, populate this ActionSet
* with those action values. A action specification has the format: {[-]actionNum[/[depth]]}
* That is, we use the following syntax rules:
* <ol>
* <li>A action specification can have zero or more entries.</li>
* <li>Each entry contains a mandatory action number, which will be added to the ActionSet
* (or removed - see later).</li>
* <li>The action number may be followed by [/depth] to indicate that all actions in the sub tree,
* starting at the specified action and moving down the action tree "depth" level, should
* be added (or removed).</li>
* <li>If 'depth' is omitted (only the '/' is provided), all actions is the subtree are added
* (regardless of their depth).</li>
* <li>If the action number is prefixed by '-', the actions are removed from the ActionSet, rather
* than being added.</li>
* <li>The special syntax "%pkg/foo" means all actions in the package "foo".</li>
* <li>The special syntax "%not-pkg/foo" means all actions outside the package "foo".</li>
* </ol>
*
* @param actionSpecs An array of command line arguments that specify which actions (or sub-trees
* of actions) should be added (or removed) from the action tree.
* @return ErrorCode.OK on success, or Error.BAD_VALUE if one of the action specifications
* is badly formed.
*/
public int populateWithActions(String actionSpecs[]) {
IBuildStore bs = actionMgr.getBuildStore();
IPackageMgr pkgMgr = bs.getPackageMgr();
IPackageMemberMgr pkgMemberMgr = bs.getPackageMemberMgr();
/*
* Process each action spec in turn. They're mostly independent, although
* removing actions from the ActionSet requires that you've already added a larger
* set of actions from which actions can be subtracted from. The order is
* therefore important.
*/
for (String actionSpec : actionSpecs) {
/* only non-empty action specs are allowed */
int tsLen = actionSpec.length();
if (tsLen < 1) {
return ErrorCode.BAD_VALUE;
}
/* check for commands that start with %, and end with / */
if (actionSpec.startsWith("%")){
/*
* Figure out what the "name" is. It must be terminated by a '/',
* which is then followed by the command's argument(s).
*/
int slashIndex = actionSpec.indexOf('/');
if (slashIndex == -1) { /* there must be a / */
return ErrorCode.BAD_VALUE;
}
String commandName = actionSpec.substring(1, slashIndex);
String commandArgs = actionSpec.substring(slashIndex + 1);
if (commandName.equals("p") || commandName.equals("pkg")){
ActionSet pkgActionSet = pkgMemberMgr.getActionsInPackage(commandArgs);
if (pkgActionSet == null) {
return ErrorCode.BAD_VALUE;
}
mergeSet(pkgActionSet);
}
else if (commandName.equals("np") || (commandName.equals("not-pkg"))){
ActionSet pkgActionSet = pkgMemberMgr.getActionsOutsidePackage(commandArgs);
if (pkgActionSet == null) {
return ErrorCode.BAD_VALUE;
}
mergeSet(pkgActionSet);
}
/* try to match the action's command name */
else if (commandName.equals("m") || commandName.equals("match")){
IReportMgr reports = bs.getReportMgr();
/* substitute * with %, and un-escape \\: to be : */
commandArgs = commandArgs.replace('*', '%');
commandArgs = commandArgs.replaceAll("\\\\:", ":");
ActionSet matchingActionSet = reports.reportActionsThatMatchName(commandArgs);
mergeSet(matchingActionSet);
}
/* else, the command isn't recognized */
else {
return ErrorCode.BAD_VALUE;
}
}
else {
/*
* Parse the string. It'll be in the format: [-]NNNN[/[DD]]
* Does it start with an optional '-'?
*/
int actionNumPos = 0; /* by default, action number is at start of string */
boolean isAdditiveSpec = true; /* by default, we're adding (not removing) actions */
if (actionSpec.charAt(actionNumPos) == '-'){
actionNumPos++;
isAdditiveSpec = false;
}
/* is there a '/' character that separates the action number from the depth? */
int slashIndex = actionSpec.indexOf('/', actionNumPos);
/* yes, there's a /, so we care about the depth (otherwise we'd default to depth = 1 */
int depth = 1;
if (slashIndex != -1) {
/* if there's no number after the '/', the depth is -1 (infinite) */
if (slashIndex + 1 == tsLen) {
depth = -1;
}
/* else, the number after the / is the depth */
else {
try {
depth = Integer.valueOf(actionSpec.substring(slashIndex + 1, tsLen));
} catch (NumberFormatException ex) {
return ErrorCode.BAD_VALUE;
}
}
} else {
slashIndex = tsLen;
}
/* what is the action number? It's between 'actionNumPos' and 'slashIndex' */
int actionNum;
try {
actionNum = Integer.valueOf(actionSpec.substring(actionNumPos, slashIndex));
} catch (NumberFormatException ex) {
return ErrorCode.BAD_VALUE;
}
/* populate this ActionSet, based on the actionNum and depth the user provided */
populateWithActionsHelper(actionNum, depth, isAdditiveSpec);
}
}
return ErrorCode.OK;
}
/*=====================================================================================*
* PROTECTED METHODS
*=====================================================================================*/
/**
* @return the maximum number of actions that can be represented in this ActionSet
* (numbered 0 to getMaxIdNumber() - 1).
*/
protected int getMaxIdNumber()
{
return IActionMgr.MAX_ACTIONS;
}
/*=====================================================================================*
* PRIVATE METHODS
*=====================================================================================*/
/**
* This is a helper method, to be used only by populateWithActions().
*
* @param actionNum The action number to be added to the ActionSet.
* @param depth The number of tree levels to add. Use 1 to indicate that only this
* action should be added, 2 to indicate that this action and it's immediate children be
* added etc. The value -1 is used to indicate that all levels should be added.
* @param toBeAdded True if we should add the actions to the ActionSet, else false to
* remove them.
*/
private void populateWithActionsHelper(int actionNum, int depth, boolean toBeAdded) {
/* we always add/remove the action itself */
if (toBeAdded) {
add(Integer.valueOf(actionNum));
} else {
remove(actionNum);
}
/*
* And perhaps add/remove the children, if they're within the depth range,
* or if there's no depth range specified (defaults to -1)
*/
if ((depth > 1) || (depth == -1)) {
Integer children [] = actionMgr.getChildren(actionNum);
for (int i = 0; i < children.length; i++) {
populateWithActionsHelper(children[i], (depth == -1) ? -1 : depth - 1, toBeAdded);
}
}
}
/*-------------------------------------------------------------------------------------*/
}