// Get the updated targetable inputs.
Set<PartInput> updatedTargetableInputSet = new LinkedHashSet<PartInput>();
int nArgs = changedGem.getNInputs();
for (int i = 0; i < nArgs; i++) {
PartInput updatedGemInput = changedGem.getInputPart(i);
if (!updatedGemInput.isConnected() && !updatedGemInput.isBurnt()) {
updatedTargetableInputSet.add(updatedGemInput);
}
}
// Get the new (untargeted) arguments.
List<PartInput> untargetedNewArguments = new ArrayList<PartInput>(updatedTargetableInputSet);
untargetedNewArguments.removeAll(oldTargetableGemInputs);
// Find the best collector to target.
// This should only be null if there were no targetable inputs on the tree or the gem which changed.
CollectorGem collectorToTarget = oldTargetableGemInputs.isEmpty() ? getCollectorToTarget(oldUnburntTreeInputList)
: getCollectorToTarget(oldTargetableGemInputs);
if (collectorToTarget == null) {
collectorToTarget = GemGraph.obtainOutermostCollector(changedGem);
}
// Get the collector's old targeting arguments.
List<PartInput> targetArgs = collectorToTarget.getTargetArguments();
int nTargetArgs = targetArgs.size();
// Get the tree arguments which used to target the collector to target, in order.
List<PartInput> oldTargetingTreeInputs = new ArrayList<PartInput>();
for (final PartInput unburntTreeInput : oldUnburntTreeInputList) {
if (GemGraph.getInputArgumentTarget(unburntTreeInput) == collectorToTarget) {
oldTargetingTreeInputs.add(unburntTreeInput);
}
}
// Calculate characteristics of the previous tree inputs - whether they were together, and/or in natural order.
Pair<Boolean, Boolean> oldTreeInputsTogetherOrInNaturalOrder = isTogetherOrInNaturalOrder(oldTargetingTreeInputs, targetArgs);
boolean oldTreeInputsTogether = oldTreeInputsTogetherOrInNaturalOrder.fst().booleanValue();
boolean oldTreeInputsInNaturalOrder = oldTreeInputsTogetherOrInNaturalOrder.snd().booleanValue();
// Gem the gem inputs which carry over, in order.
List<PartInput> remainingOldGemInputList = new ArrayList<PartInput>(unburntTreeInputSet);
remainingOldGemInputList.retainAll(oldTargetableGemInputs);
// Determine if they occur together in natural order (or just together) in the targeted collector's target arguments.
Pair<Boolean, Boolean> isTogetherOrInNaturalOrder = isTogetherOrInNaturalOrder(oldTargetableGemInputs, targetArgs);
boolean isTogether = isTogetherOrInNaturalOrder.fst().booleanValue();
boolean isInNaturalOrder = isTogetherOrInNaturalOrder.snd().booleanValue();
// If the old inputs (on the gem or on the tree) were in natural order, ensure that they remain so.
if (oldTreeInputsInNaturalOrder) {
collectorToTarget.removeArguments(targetArgs);
targetArgs = getTargetArgsInOrder(targetArgs, unburntTreeInputList);
collectorToTarget.addArguments(targetArgs);
// The collector to target will also be affected..
affectedCollectorSet.add(collectorToTarget);
} else if (isInNaturalOrder) {
collectorToTarget.removeArguments(targetArgs);
targetArgs = getTargetArgsInOrder(targetArgs, Arrays.asList(changedGem.getInputParts()));
collectorToTarget.addArguments(targetArgs);
// The collector to target will also be affected..
affectedCollectorSet.add(collectorToTarget);
}
// Calculate the inputs which are going away.
Set<PartInput> departingInputs = new HashSet<PartInput>(oldTargetableGemInputs);
departingInputs.removeAll(updatedTargetableInputSet);
// If there aren't any untargeted new arguments, we're done.
if (untargetedNewArguments.isEmpty()) {
// Remove from their targets any arguments for inputs which are going away.
affectedCollectorSet.addAll(removeFromTargets(departingInputs));
return affectedCollectorSet;
}
//
// The rest of this method deals with where to place untargeted new arguments.
//
// The collector to target will also be affected..
affectedCollectorSet.add(collectorToTarget);
if (isTogether && isInNaturalOrder) {
// This is the argument on collectorToTarget before which this gem's args appear.
// Null means add to the beginning.
PartInput argBefore;
// Add the new args to wherever they appear in the tree input set.
PartInput firstUpdatedInput = updatedTargetableInputSet.iterator().next();
int firstUpdatedInputIndex = unburntTreeInputList.indexOf(firstUpdatedInput);
if (firstUpdatedInputIndex < 1) {
argBefore = null;
} else {
// Get the immediately preceding input on the tree which targets the same collector.
List<PartInput> precedingTreeInputs = unburntTreeInputList.subList(0, firstUpdatedInputIndex);
PartInput precedingTargetingInput = getTargetingArg(precedingTreeInputs, collectorToTarget, false);
if (precedingTargetingInput != null) {
// Add after the preceding tree input which targets the same collector.
argBefore = precedingTargetingInput;
} else {
// No preceding tree inputs target the same collector.
// Determine if any inputs which follow on the tree target the same collector.
PartInput lastUpdatedInput = (new ArrayList<PartInput>(updatedTargetableInputSet)).get(updatedTargetableInputSet.size() - 1);
int lastUpdatedInputIndex = unburntTreeInputList.indexOf(lastUpdatedInput);
List<PartInput> followingTreeInputs = unburntTreeInputList.subList(lastUpdatedInputIndex + 1, unburntTreeInputList.size());
PartInput followingTargetingInput = getTargetingArg(followingTreeInputs, collectorToTarget, false);
if (followingTargetingInput != null) {
// Add before the following tree input which targets the same collector.
int followingArgTargetIndex = targetArgs.indexOf(followingTargetingInput);
// Construct the reverse list of preceding targeting args.
List<PartInput> reversedPrecedingTargetArgs = new ArrayList<PartInput>(targetArgs.subList(0, followingArgTargetIndex));
Collections.reverse(reversedPrecedingTargetArgs);
// Set the arg before to the last preceding arg which isn't an input to the changed gem.
// If there isn't any such arg, add to the beginning.
argBefore = null;
for (final PartInput precedingTargetArg : reversedPrecedingTargetArgs) {
if (precedingTargetArg.getGem() != changedGem) {
argBefore = precedingTargetArg;
break;
}
}
} else {
// Add to the end of the collector's args.
argBefore = targetArgs.get(nTargetArgs - 1);
}
}
}
// remove all the old args..
collectorToTarget.removeArguments(remainingOldGemInputList);
// add all the updated args back..
collectorToTarget.addArguments(argBefore, updatedTargetableInputSet, argBefore != null);
} else if (isInNaturalOrder) {
// The old inputs appear in natural order in the target arg list.
// Maintain natural order among new inputs.
// Get the tree arguments which will target the collector to target.
List<PartInput> newTargetingTreeInputs = new ArrayList<PartInput>();
for (final PartInput unburntTreeInput : unburntTreeInputList) {
if (GemGraph.getInputArgumentTarget(unburntTreeInput) == collectorToTarget || unburntTreeInput.getGem() == changedGem) {
newTargetingTreeInputs.add(unburntTreeInput);
}
}
// If the old tree inputs appeared together in natural order, replace with the new tree inputs.
if (oldTreeInputsTogether && oldTreeInputsInNaturalOrder) {
// Remove the arguments, add them back at the appropriate location.
int addIndex = targetArgs.indexOf(oldTargetingTreeInputs.get(0));
collectorToTarget.removeArguments(oldTargetingTreeInputs);
collectorToTarget.addArguments(addIndex, newTargetingTreeInputs);
} else if (oldTreeInputsInNaturalOrder) {
// If the tree inputs just appear in natural order but not together, insert semi-smartly.
for (final PartInput newInput : untargetedNewArguments) {
// If no preceding tree args, add before the first of the args which targeted the collector.
if (newInput == newTargetingTreeInputs.get(0)) {
PartInput firstTargetingArg = oldTargetingTreeInputs.get(0);
collectorToTarget.addArguments(firstTargetingArg, Collections.singleton(newInput), false);
} else {
// Add after the arg which precedes it.
PartInput precedingTargetingTreeInput = newTargetingTreeInputs.get(newTargetingTreeInputs.indexOf(newInput) - 1);
collectorToTarget.addArguments(precedingTargetingTreeInput, Collections.singleton(newInput), true);
}
}
} else {
// Otherwise, the tree inputs are not in natural order, but the old gem inputs are.
// Add to the end.
// Note: it would be better to maintain order where this makes sense
// (eg. if intervening gem tree inputs appear together but are reordered wrt each other).
// Now add the arguments to the collector, at the end of the target arg list.
collectorToTarget.addArguments(untargetedNewArguments);
}
} else {
// The remaining args are not in natural order on target args.
// This is the argument on collectorToTarget before which the new args appear.
// Null means add to the end.
PartInput argBefore;
if (isTogether) {
// Add after the last of the target's arguments that belongs to an input on this gem.
argBefore = null;
for (final PartInput targetArg : targetArgs) {