/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* CollectorGem.java
* Creation date: (09/19/01 6:17:00 PM)
* By: Edward Lam
*/
package org.openquark.gems.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.undo.StateEditable;
import org.openquark.cal.compiler.CompositionNode;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.metadata.FunctionMetadata;
import org.openquark.cal.services.CALFeatureName;
import org.openquark.cal.services.CALPersistenceHelper;
import org.openquark.gems.client.GemGraph.GemVisitor;
import org.openquark.util.Pair;
import org.openquark.util.UnsafeCast;
import org.openquark.util.xml.BadXMLDocumentException;
import org.openquark.util.xml.XMLPersistenceConstants;
import org.openquark.util.xml.XMLPersistenceHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* A CollectorGem represents a "let" definition in CAL. The gem tree connected to the collector defines the let.
* For a given target aggregator, there can only ever be one instance of a collector with a given name.
* Upon creation, collectors start out with the name given by DEFAULT_NAME.
*
* @author Edward Lam
*/
public final class CollectorGem extends Gem implements CompositionNode.Collector, NamedGem {
/**
* A default name for a collector.
* This is needed to pass validation on creation, but we shouldn't ever see this in the GemCutter.
*/
private static final String DEFAULT_NAME = "unassignedCollector";
/**
* Collectors use function metadata to store their design time metadata. The function metadata
* needs a CALFeatureName for a function. We use this module name as the module name for the function
* and the default collector name as the function name.
*/
private static final ModuleName DEFAULT_MODULE_NAME = ModuleName.make("TableTopModule");
/** The CALFeatureName all Collectors use for their FunctionMetadata. */
public static final CALFeatureName COLLECTOR_FEATURE_NAME = CALFeatureName.getFunctionFeatureName(QualifiedName.make(DEFAULT_MODULE_NAME, DEFAULT_NAME));
/** The name of the collector */
private String collectorName = DEFAULT_NAME;
/** The emitters associated with this collector. */
private final Set<ReflectorGem> emitterSet;
/** The metadata for this collector. */
private FunctionMetadata metadata;
/** The collector which defines the scope in which this collector is defined. If null, the collector is defined at the top-level. */
private CollectorGem targetCollector;
/** The declared type for the (possibly local) function whose definition is rooted at this collector. */
private TypeExpr declaredType = null;
/** The set of arguments targeting this collector.
* This contains all arguments which target this collector, even those which may not appear
* in the source generated for the definition this subtree represents (because the argument is unused..). */
private Set<PartInput> targetingArgumentSet;
/** The list of inputs whose collector arguments target this collector, such that they appear in the
* corresponding definition. */
private List<PartInput> reflectedInputList = new ArrayList<PartInput>();
/** The ArgumentStateEditable for this collector. */
private final ArgumentStateEditable argumentState = new ArgumentStateEditable();
/**
* This is a simple class to encapsulate a state editable for the collector's argument state.
* @author Edward Lam
*/
private class ArgumentStateEditable implements StateEditable {
/*
* Keys for fields in map used with state editable interface
*/
private static final String TARGETING_ARGUMENTS_KEY = "TargetingArgumentStateKey";
private static final String REFLECTED_INPUTS_KEY = "ReflectedInputsStateKey";
/**
* {@inheritDoc}
*/
public void restoreState(Hashtable<?, ?> state) {
Object stateValue;
stateValue = state.get(new Pair<CollectorGem, String>(CollectorGem.this, TARGETING_ARGUMENTS_KEY));
if (stateValue != null) {
targetingArgumentSet.clear();
targetingArgumentSet.addAll(UnsafeCast.<Set<PartInput>>unsafeCast(stateValue));
}
boolean changedTargetingArguments = (stateValue != null);
stateValue = state.get(new Pair<CollectorGem, String>(CollectorGem.this, REFLECTED_INPUTS_KEY));
if (stateValue != null) {
setReflectedInputs(UnsafeCast.<List<PartInput>>unsafeCast(stateValue));
} else if (changedTargetingArguments) {
// TODOEL: (hackish) Even though the reflected inputs didn't change, fire a reflected input event.
// The argument explorer uses this event to update its display.
// In the future, we may want to change it so that this is an "argument update" event. This would fire
// when clients make changes to the collector's targeting arguments, and say that the reflected inputs should update.
Gem.PartInput[] oldParts = reflectedInputList.toArray(new Gem.PartInput[reflectedInputList.size()]);
fireReflectedInputEvent(oldParts);
}
}
/**
* {@inheritDoc}
*/
public void storeState(Hashtable<Object, Object> state) {
// Now store all the states. The state key we use is a pair: this code gem and the field key.
state.put(new Pair<CollectorGem, String>(CollectorGem.this, TARGETING_ARGUMENTS_KEY), new LinkedHashSet<PartInput>(targetingArgumentSet));
state.put(new Pair<CollectorGem, String>(CollectorGem.this, REFLECTED_INPUTS_KEY), new ArrayList<PartInput>(reflectedInputList));
}
}
/**
* Constructor for a CollectorGem.
* @param targetCollector the collector which defines the scope in which this collector is defined.
* If null, the collector is defined at the top-level -- at least until the time when it is added to a gem graph,
* at which time, it will be re-targeted to the gem graph's target.
*/
public CollectorGem(CollectorGem targetCollector) {
this();
this.targetCollector = targetCollector;
}
/**
* Constructor for a CollectorGem with no target collector.
*/
public CollectorGem() {
// Initialize with the collecting part.
super(1);
// Create the set to hold emitters
emitterSet = new HashSet<ReflectorGem>();
// Initialize the argument lists
targetingArgumentSet = new LinkedHashSet<PartInput>();
// Initialize the metadata object.
metadata = new FunctionMetadata(COLLECTOR_FEATURE_NAME, GemCutter.getLocaleFromPreferences());
}
/**
* Adds the specified listener to receive reflected input change events from this gem.
* @param l the reflected input change listener
*/
public void addReflectedInputListener(ReflectedInputListener l) {
listenerList.add(ReflectedInputListener.class, l);
}
/**
* Removes the specified reflected input change listener so that it no longer receives reflected input change events from this gem.
* @param l the reflected input change listener
*/
public void removeReflectedInputListener(ReflectedInputListener l) {
listenerList.remove(ReflectedInputListener.class, l);
}
/**
* Fires an reflected input change event.
* @param oldParts the old reflected input parts.
*/
private void fireReflectedInputEvent(PartInput[] oldParts) {
Object[] listeners = listenerList.getListenerList();
ReflectedInputEvent e = null;
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ReflectedInputListener.class) {
if (e == null) {
// Lazily create the event:
e = new ReflectedInputEvent(this, oldParts);
}
((ReflectedInputListener) listeners[i + 1]).reflectedInputsChanged(e);
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected PartInput createInputPart(int inputNum) {
if (inputNum != 0) {
throw new IllegalArgumentException("The input number of a collector input must be 0.");
}
return new CollectingPart();
}
/**
* {@inheritDoc}
*/
@Override
protected PartOutput createOutputPart() {
// This gem does not have an output part.
return null;
}
/**
* Get the reflectors associated with this collector.
* @return the (unmodifiable) set of reflectors associated with this collector.
*/
public Set<ReflectorGem> getReflectors() {
return Collections.unmodifiableSet(emitterSet);
}
/**
* Associate a reflector with this collector.
* @param reflectorGem the reflector to associate
* @return boolean whether or not the element was added
*/
boolean addReflector(ReflectorGem reflectorGem) {
return emitterSet.add(reflectorGem);
}
/**
* Dissociate a reflector from this collector.
* @param reflectorGem the reflector to dissociate
* @throws IllegalArgumentException if the reflector does not belong to this collector.
*/
void removeReflector(ReflectorGem reflectorGem) {
if (!emitterSet.remove(reflectorGem)) {
throw new IllegalArgumentException("The specified reflector does not belong to this collector");
}
}
/**
* Get the name of the Gem (the name of the underlying let)
* @return QualifiedName the name of the gem
*/
public String getUnqualifiedName() {
return collectorName;
}
/**
* Set the name of the Gem (the name of the underlying let)
* @param newName String the new (unqualified) name
*/
public void setName(String newName) {
String newUnqualifiedName = newName;
// notify listeners
if (collectorName == null || !collectorName.equals(newUnqualifiedName)) {
String oldName = collectorName;
collectorName = newUnqualifiedName;
if (nameChangeListener != null) {
nameChangeListener.nameChanged(new NameChangeEvent(CollectorGem.this, oldName));
}
}
fireInputNameEvent(getCollectingPart());
}
/**
* {@inheritDoc}
*/
public CompositionNode.Collector getTargetCollector() {
return targetCollector;
}
/**
* Set the target collector for this collector.
*/
public void setTargetCollector(CollectorGem targetCollector) {
// Circular references for target collectors (a->b->c->a) are illegal and we could check and prevent
// such an occurance. As a quick safety check we'll prevent the degenerate case of a->a.
if (targetCollector == this) {
throw new IllegalArgumentException("The target for a collector can not be set to itself");
}
this.targetCollector = targetCollector;
}
/**
* Returns the target collector for which this collector is defined.
* @return the target collector for which this collector is defined, or null if this is a top-level collector.
* The target collector defines the scope of a collector -- the collector definition is visible for other subtrees
* targeting the same collector, or any collector definitions occurring within those subtrees.
*/
public CollectorGem getTargetCollectorGem() {
return targetCollector;
}
/**
* Return whether a given collector gem is enclosed by this one.
* This means that the gem is an inner scope of this collector.
* @param otherCollectorGem
* @return whether the given collector gem is enclosed by this one.
* Also returns true if otherCollectorGem is the same as this one.
*
*/
public boolean enclosesCollector(CollectorGem otherCollectorGem) {
for (CollectorGem collectorGem = otherCollectorGem; collectorGem != null; collectorGem = collectorGem.getTargetCollectorGem()) {
if (collectorGem == this) {
return true;
}
}
return false;
}
/**
* Get the arguments targeting this collector.
* Note that this will include any arguments targeting this collector which do not appear in its definition
* (ie. if they are unused).
* @return the list of arguments targeting this collector.
*/
public final List<PartInput> getTargetArguments() {
return new ArrayList<PartInput>(targetingArgumentSet);
}
/**
* Return whether a given argument is registered as a target on this collector.
* @param collectorArgument the argument in question.
* @return whether the given argument is registered as a target on this collector.
*/
public final boolean isTargetedBy(PartInput collectorArgument) {
return targetingArgumentSet.contains(collectorArgument);
}
/**
* @param otherCollector another collector.
* @return whether this collector's definition is visible to otherCollector.
*/
public final boolean isVisibleTo(CollectorGem otherCollector) {
// If this collector is the target collector, it's visible.
if (targetCollector == null) {
return true;
}
CollectorGem otherCollectorTarget = otherCollector.getTargetCollectorGem();
// If the other collector is the target collector (and this one isn't), only true if other collector equals or is the target of this one.
if (otherCollectorTarget == null) {
return this == otherCollector || targetCollector == otherCollector;
}
// For one collector to be visible to another, it must be the same collector, a sibling, an ancestor,
// a sibling of an ancestor, or a child.
// This can be simplified to: its target must enclose the other collector.
return targetCollector.enclosesCollector(otherCollector);
}
/**
* Get the inputs for arguments targeting this collector, and which also appear in its definition.
* @return the inputs whose arguments appear in this collector's definition.
*/
public final List<PartInput> getReflectedInputs() {
return new ArrayList<PartInput>(reflectedInputList);
}
/**
* Set the inputs which target this collector, and also appear in its definition.
* @param newReflectedInputs the inputs whose arguments should appear in this collector's definition.
*/
private final void setReflectedInputs(List<PartInput> newReflectedInputs) {
Gem.PartInput[] oldParts = reflectedInputList.toArray(new Gem.PartInput[reflectedInputList.size()]);
reflectedInputList = new ArrayList<PartInput>(newReflectedInputs);
fireReflectedInputEvent(oldParts);
}
/**
* Return whether a given argument is reflected by this collector.
* @param collectorArgument the argument in question.
* @return whether the given argument is reflected by this collector.
*/
public final boolean isReflected(PartInput collectorArgument) {
return reflectedInputList.contains(collectorArgument);
}
/**
* Update the reflected arguments on this collector, and associated reflectors.
* This reconciles the current gem tree with the arguments which target this collector to determine
* what arguments appear on corresponding reflectors.
* This method should be called whevever the arguments targeting this collector are changed.
* @return Map from any reflectors which changed to their old input parts.
*/
public Map<ReflectorGem, PartInput[]> updateReflectedInputs() {
setReflectedInputs(calculateReflectedInputs());
Map<ReflectorGem, PartInput[]> affectedReflectorsToOldInputsMap = new HashMap<ReflectorGem, PartInput[]>();
// Update associated reflectors.
for (final ReflectorGem reflector : getReflectors()) {
PartInput[] oldInputs = reflector.getInputParts();
if (reflector.updateForCollector(reflectedInputList)) {
affectedReflectorsToOldInputsMap.put(reflector, oldInputs);
}
}
return affectedReflectorsToOldInputsMap;
}
/**
* Calculate the arguments which should be reflected on this collector, and associated reflectors.
* This reconciles the current gem tree with the arguments which target this collector to determine
* what arguments appear on corresponding reflectors.
* @return the calculated inputs which should be reflected on associated reflectors.
*/
private List<PartInput> calculateReflectedInputs() {
// Get all the descendant inputs.
List<PartInput> forestInputs =
GemGraph.obtainUnboundDescendantInputs(this, GemGraph.TraversalScope.FOREST, GemGraph.InputCollectMode.UNBURNT_ONLY);
// Get all the arguments on those inputs which actually target this gem.
Set<PartInput> relevantTargetingArgs = new HashSet<PartInput>();
for (final Gem.PartInput inputArgument : forestInputs) {
if (this.targetingArgumentSet.contains(inputArgument)) {
// Ignore recursive use.
if (inputArgument.getGem() instanceof ReflectorGem && ((ReflectorGem)inputArgument.getGem()).getCollector() == this) {
continue;
}
relevantTargetingArgs.add(inputArgument);
}
}
// The reflected argument list is simply the intersection of the arguments from this scope with the targeting argument list.
Set<PartInput> reflectedArgumentSet = new LinkedHashSet<PartInput>(targetingArgumentSet);
reflectedArgumentSet.retainAll(relevantTargetingArgs);
// Construct the input list.
List<PartInput> calculatedReflectedInputList = new ArrayList<PartInput>(reflectedArgumentSet.size());
for (final PartInput partInput : reflectedArgumentSet) {
calculatedReflectedInputList.add(partInput);
}
return calculatedReflectedInputList;
}
/**
* Update this collector's argument list by adding an argument to the end of its current targeting arguments list.
* @param newArg the argument to add.
*/
public void addArgument(Gem.PartInput newArg) {
addArguments(Collections.singletonList(newArg));
}
/**
* Update this collector's argument list by adding a set of arguments to the end of its current targeting arguments list.
* @param newArgs the arguments to add.
*/
public void addArguments(Collection<PartInput> newArgs) {
addArguments(null, newArgs, true);
}
/**
* Update this collector's argument list by adding a set of arguments.
* @param inputArgument the input argument defining where to add the arguments,
* or null to add to the beginning/end of the set.
* @param newArgs the arguments to add.
* @param addAfter true to add after the provided argument, false to add before.
*/
public void addArguments(PartInput inputArgument, Collection<PartInput> newArgs, boolean addAfter) {
// Check for nothing to do.
if (newArgs.isEmpty()) {
return;
}
// (slow) sanity check.
Set<PartInput> targetingArgumentSetCopy = new HashSet<PartInput>(targetingArgumentSet);
targetingArgumentSetCopy.retainAll(newArgs);
if (!targetingArgumentSetCopy.isEmpty()) {
throw new IllegalArgumentException("Attempt to add a duplicate argument.");
}
for (final PartInput newArg : newArgs) {
if (newArg.isConnected()) {
throw new IllegalArgumentException("Attempt to add a connected input as an argument: " + newArg);
}
}
List<PartInput> targetingArgumentList = new ArrayList<PartInput>(targetingArgumentSet);
int addIndex;
if (inputArgument == null) {
addIndex = addAfter ? targetingArgumentList.size() - 1 : 0;
} else {
addIndex = targetingArgumentList.indexOf(inputArgument);
if (addIndex < 0) {
throw new IllegalArgumentException("Input argument not found.");
}
}
if (addAfter) {
addIndex++;
}
targetingArgumentList.addAll(addIndex, newArgs);
this.targetingArgumentSet = new LinkedHashSet<PartInput>(targetingArgumentList);
}
/**
* Update this collector's argument list by adding a set of arguments.
* @param addIndex the index at which to add the arguments.
* @param newArgs the arguments to add.
*/
public void addArguments(int addIndex, Collection<PartInput> newArgs) {
// Check for nothing to do.
if (newArgs.isEmpty()) {
return;
}
// (slow) sanity check.
Set<PartInput> targetingArgumentSetCopy = new HashSet<PartInput>(targetingArgumentSet);
targetingArgumentSetCopy.retainAll(newArgs);
if (!targetingArgumentSetCopy.isEmpty()) {
throw new IllegalArgumentException("Attempt to add a duplicate argument.");
}
for (final PartInput newArg : newArgs) {
if (newArg.isConnected()) {
GemCutter.CLIENT_LOGGER.log(Level.WARNING, "Attempt to add a connected input as an argument: " + newArg);
}
}
List<PartInput> targetingArgumentList = new ArrayList<PartInput>(targetingArgumentSet);
targetingArgumentList.addAll(addIndex, newArgs);
this.targetingArgumentSet = new LinkedHashSet<PartInput>(targetingArgumentList);
}
/**
* Update this collector's argument list by removing a given argument.
* @param argumentToRemove the argument to remove from this collector.
*/
public void removeArgument(PartInput argumentToRemove) {
if (!targetingArgumentSet.remove(argumentToRemove)) {
throw new IllegalArgumentException("Argument not found.");
}
}
/**
* Update this collector's argument list by removing the given arguments.
* @param argumentsToRemove the arguments to remove from this collector.
*/
public void removeArguments(Collection<PartInput> argumentsToRemove) {
if (!targetingArgumentSet.containsAll(argumentsToRemove)) {
throw new IllegalArgumentException("Some arguments not found.");
}
targetingArgumentSet.removeAll(argumentsToRemove);
}
/**
* Update this collector's argument list by replacing a given argument with a set of arguments.
* @param replacedArgument the argument to replace.
* @param newArgs the arguments with which the argument should be replaced.
*/
public void replaceArgument(PartInput replacedArgument, Set<PartInput> newArgs) {
// Create a list of the targeting arguments, find the index of the replaced argument.
List<PartInput> targetingArgumentList = new ArrayList<PartInput>(targetingArgumentSet);
int replacementIndex = targetingArgumentList.indexOf(replacedArgument);
if (replacementIndex < 0) {
throw new IllegalArgumentException("Input argument not found.");
}
for (final PartInput newArg : newArgs) {
if (newArg.isConnected()) {
GemCutter.CLIENT_LOGGER.log(Level.WARNING, "Attempt to add a connected input as an argument: " + newArg);
}
}
// Remove the replaced argument, add the new args.
targetingArgumentList.remove(replacementIndex);
targetingArgumentList.addAll(replacementIndex, newArgs);
// Set this collector's targeting arguments.
this.targetingArgumentSet = new LinkedHashSet<PartInput>(targetingArgumentList);
}
/**
* @return a copy of the design metadata for this collector.
*/
public FunctionMetadata getDesignMetadata() {
return (FunctionMetadata) metadata.copy();
}
/**
* Set the new metadata for this collector. A safe copy will be made.
* @param metadata the new design metadata.
*/
public void setDesignMetadata(FunctionMetadata metadata) {
metadata.copyTo(this.metadata);
}
/**
* Replaces the metadata for this collector with empty metadata.
*/
public void clearDesignMetadata() {
metadata = new FunctionMetadata(COLLECTOR_FEATURE_NAME, GemCutter.getLocaleFromPreferences());
}
/**
* {@inheritDoc}
*/
public TypeExpr getDeclaredType() {
if (declaredType == null) {
return null;
}
return declaredType.copyTypeExpr();
}
/**
* Set the declared type for the (possibly local) function whose definition is rooted at this collector
* @param declaredType the declared type, or null to clear.
*/
public void setDeclaredType(TypeExpr declaredType) {
this.declaredType = (declaredType == null) ? null : declaredType.copyTypeExpr();
}
/**
* If the target is connected (and thus the result output has a TypeExpr),
* then this method returns it. Else, returns null.
* @return TypeExpr the type of the result associated with this collector.
*/
@Override
public TypeExpr getResultType() {
return getCollectingPart().getType();
}
/**
* Set the output type of this root gem
* @param outType TypeExpr the new output type of this gem (which is root)
*/
@Override
void setRootOutputType(TypeExpr outType){
// this gem is always the root. Setting its type sets the type emitted.
getCollectingPart().setType(outType);
}
/**
* Assuming that this Gem is actually part of a tree rather than a graph, this method returns
* the Gem at the root of the tree.
* @return Gem the Gem at the root of the tree
*/
@Override
public Gem getRootGem() {
// A CollectorGem's root is itself
return this;
}
/**
* Get the collecting part for the collector.
* @return CollectingPart the collecting part for this CollectorGem
*/
public CollectingPart getCollectingPart() {
return (CollectingPart)getInputPart(0);
}
/**
* Describe this Gem
* @return the description
*/
@Override
public String toString() {
String targetString = targetCollector == null ? "" : " (Target: " + targetCollector.getUnambiguousStringId() + ")";
return "Collector " + getUnambiguousStringId() + targetString;
}
/**
* Get a string identifier which unambiguously identifies this collector wrt the string id's of other collectors
* (especially other collectors with the same unqualified name).
* @return an unambiguous string id for this collector.
*/
private String getUnambiguousStringId() {
return getUnqualifiedName() + " " + System.identityHashCode(this);
}
/**
* {@inheritDoc}
*/
@Override
void visitAncestors(GemVisitor gemVisitor, Set<Gem> gemsVisited){
// visit
if (seeVisitor(gemVisitor, gemsVisited)) {
return;
}
// traverse reflectors if appropriate
GemGraph.TraversalScope visitorScope = gemVisitor.getTraversalScope();
if (visitorScope == GemGraph.TraversalScope.FOREST) {
Set<ReflectorGem> reflectors = getReflectors();
for (final ReflectorGem rg : reflectors) {
rg.visitAncestors(gemVisitor, gemsVisited);
}
}
}
/**
* {@inheritDoc}
*/
@Override
void visitConnectedTrees(GemVisitor gemVisitor, Set<Gem> gemsVisited){
if (gemVisitor.getTraversalScope() != GemGraph.TraversalScope.FOREST) {
throw new IllegalArgumentException("Expecting a forest visitor.");
}
// visit
if (seeVisitor(gemVisitor, gemsVisited)) {
return;
}
// Follow the input connection
Connection inputConnection = getInputPart(0).getConnection();
if (inputConnection != null) {
inputConnection.getSource().getGem().visitConnectedTrees(gemVisitor, gemsVisited);
}
if (gemVisitor.getTraversalScope() == GemGraph.TraversalScope.FOREST) {
// traverse reflectors.
Set<ReflectorGem> reflectors = getReflectors();
for (final ReflectorGem reflectorGem : reflectors) {
reflectorGem.getRootGem().visitConnectedTrees(gemVisitor, gemsVisited);
}
// traverse the target, if this doesn't escape the target scope level of the visitor
CollectorGem targetCollectorGem = getTargetCollectorGem();
if (targetCollectorGem != null) {
CollectorGem visitorTargetCollector = gemVisitor.getTargetCollector();
if (visitorTargetCollector != null && !visitorTargetCollector.enclosesCollector(targetCollectorGem)) {
return;
}
targetCollectorGem.visitConnectedTrees(gemVisitor, gemsVisited);
}
}
}
/**
* Returns true if this Gem has been given a name.
* @return boolean
*/
boolean isNameInitialized() {
// If the name is not the same object as the default name then it has been initialized.
return DEFAULT_NAME != collectorName;
}
/**
* @return a StateEditable for this collector's arguments.
*/
StateEditable getArgumentStateEditable() {
return argumentState;
}
/*
* Methods supporting XMLPersistable ********************************************
*/
/**
* {@inheritDoc}
*/
@Override
public void saveXML(Node parentNode, GemContext gemContext) {
Document document = (parentNode instanceof Document) ? (Document)parentNode : parentNode.getOwnerDocument();
// Create the collector gem element
Element resultElement = document.createElementNS(GemPersistenceConstants.GEM_NS, GemPersistenceConstants.COLLECTOR_GEM_TAG);
resultElement.setPrefix(GemPersistenceConstants.GEM_NS_PREFIX);
parentNode.appendChild(resultElement);
// Add info for the superclass gem.
super.saveXML(resultElement, gemContext);
// Now add CollectorGem-specific info
// Target collector (if any).
if (targetCollector != null) {
resultElement.setAttribute(GemPersistenceConstants.COLLECTOR_GEM_TARGET_COLLECTOR_ATTR, gemContext.getIdentifier(targetCollector, true));
}
// Add an element for the name.
Element nameElement = CALPersistenceHelper.unqualifiedNameToElement(getUnqualifiedName(), document);
resultElement.appendChild(nameElement);
// This was left in for several months, ending Oct 27/03. We may want to re-institute for non-top-level collectors?
// // Save the design metadata
// metadata.saveXML(resultElement);
// Add an element for the arguments.
Element argumentsElement = document.createElement(GemPersistenceConstants.ARGUMENTS_TAG);
resultElement.appendChild(argumentsElement);
// Add elements for the arguments (if any)
for (final PartInput targetingInput : targetingArgumentSet) {
// Get the element.
Element argumentElement = GemCutterPersistenceHelper.inputToArgumentElement(targetingInput, document, gemContext);
// Add an attribute saying whether the argument is reflected.
boolean reflected = reflectedInputList.contains(targetingInput);
argumentElement.setAttribute(GemPersistenceConstants.ARGUMENT_REFLECTED_ATTR, reflected ? XMLPersistenceConstants.TRUE_STRING : XMLPersistenceConstants.FALSE_STRING);
// Add the element.
argumentsElement.appendChild(argumentElement);
}
}
/**
* Create a new CollectorGem and loads its state from the specified XML element.
* @param gemElement Element the element representing the structure to deserialize.
* @param gemContext the context in which the gem is being instantiated.
* @param loadInfo the argument info for this load session.
* @return CollectorGem
* @throws BadXMLDocumentException
*/
public static CollectorGem getFromXML(Element gemElement, GemContext gemContext, Argument.LoadInfo loadInfo) throws BadXMLDocumentException {
CollectorGem gem = new CollectorGem();
gem.loadXML(gemElement, gemContext, loadInfo);
return gem;
}
/**
* Load this object's state.
* @param gemElement Element the element representing the structure to deserialize.
* @param gemContext the context in which the gem is being instantiated.
* @param loadInfo the argument info for this load session.
*/
void loadXML(Element gemElement, GemContext gemContext, Argument.LoadInfo loadInfo) throws BadXMLDocumentException {
XMLPersistenceHelper.checkTag(gemElement, GemPersistenceConstants.COLLECTOR_GEM_TAG);
XMLPersistenceHelper.checkPrefix(gemElement, GemPersistenceConstants.GEM_NS_PREFIX);
List<Element> childElems = XMLPersistenceHelper.getChildElements(gemElement);
int nChildElems = childElems.size();
// Get info for the underlying gem.
Element superGemElem = (nChildElems < 1) ? null : (Element) childElems.get(0);
XMLPersistenceHelper.checkIsElement(superGemElem);
super.loadXML(superGemElem, gemContext);
// Get the name
Element nameElem = (nChildElems < 2) ? null : (Element) childElems.get(1);
XMLPersistenceHelper.checkIsElement(nameElem);
String gemName = CALPersistenceHelper.elementToUnqualifiedName(nameElem);
// Set the target aggregator if any. If the attribute is not present, the target aggregator is null.
String targetCollectorAttribute = gemElement.getAttribute(GemPersistenceConstants.COLLECTOR_GEM_TARGET_COLLECTOR_ATTR);
if (!targetCollectorAttribute.equals("")) {
Gem gem = gemContext.getGem(targetCollectorAttribute);
if (gem == null) {
// TODOEL: these messages really should go into a Status object.
GemCutter.CLIENT_LOGGER.log(Level.WARNING, "The target collector could not be found for this collector.");
targetCollector = null;
} else if (!(gem instanceof CollectorGem)) {
GemCutter.CLIENT_LOGGER.log(Level.WARNING, "The target collector id for this collector does not correspond to an collector gem.");
targetCollector = null;
} else {
targetCollector = (CollectorGem)gem;
}
} else {
targetCollector = null;
}
collectorName = gemName;
emitterSet.clear();
// Get the arguments
Element argumentsElement = (nChildElems < 3) ? null : (Element) childElems.get(2);
XMLPersistenceHelper.checkIsElement(argumentsElement);
List<Element> argumentsChildElems = XMLPersistenceHelper.getChildElements(argumentsElement);
int nArgumentsChildElems = argumentsChildElems.size();
for (int i = 0; i < nArgumentsChildElems; i++) {
Element orphanedInputChildElem = argumentsChildElems.get(i);
Pair<String, Integer> orphanedInputInfoPair = GemCutterPersistenceHelper.argumentElementToInputInfo(orphanedInputChildElem);
// Add the argument info the the load info. Extra info is the boolean attribute saying whether this input is reflected.
boolean isReflected = XMLPersistenceHelper.getBooleanAttribute(orphanedInputChildElem, GemPersistenceConstants.ARGUMENT_REFLECTED_ATTR);
loadInfo.addArgument(this, orphanedInputInfoPair.fst(), orphanedInputInfoPair.snd(), Boolean.valueOf(isReflected));
}
}
/**
* Populate internal argument state during loading.
* @param loadInfo
* @param gemContext
*/
public void loadArguments(Argument.LoadInfo loadInfo, GemContext gemContext) throws BadXMLDocumentException {
List<PartInput> newReflectedInputList = new ArrayList<PartInput>();
this.targetingArgumentSet = new LinkedHashSet<PartInput>();
for (int i = 0, nArguments = loadInfo.getNArguments(this); i < nArguments; i++) {
Pair<String, Integer> inputInfo = loadInfo.getInputInfo(this, i);
boolean isReflected = ((Boolean)loadInfo.getOtherInfo(this, i)).booleanValue();
Gem inputGem = gemContext.getGem(inputInfo.fst());
int inputIndex = (inputInfo.snd()).intValue();
if (inputGem == null) {
throw new BadXMLDocumentException(null, "Argument gem not found.");
}
Gem.PartInput targetingInput = inputGem.getInputPart(inputIndex);
targetingArgumentSet.add(targetingInput);
if (isReflected) {
newReflectedInputList.add(targetingInput);
}
}
setReflectedInputs(newReflectedInputList);
}
/**
* Makes a copy of the specified gem. This function will check if there
* is a gem that has the same name. If so, it will add a disambiguating
* suffix to the name, unless the keepOriginalName flag is set to true,
* in which case a 'blind' copy is completed.
* @return CollectorGem;
*/
CollectorGem makeCopy(){
// Create a new collector.
CollectorGem collectorGem = new CollectorGem(targetCollector);
String name = getUnqualifiedName();
collectorGem.setName(name);
collectorGem.setDesignMetadata(getDesignMetadata());
return collectorGem;
}
/**
* The PartConnectable representing the collecting part of the CollectorGem
* Creation date: (09/20/01 11:31:00 AM)
* @author Edward Lam
*/
public class CollectingPart extends PartInput {
/**
* Default constructor for a collecting part.
*/
private CollectingPart() {
// always the first "input"
super(0);
// Collecting Parts start out with a parametric type 'a'.
TypeExpr newType = TypeExpr.makeParametricType();
setType(newType);
// Ensure that this part's name stays in sync with the collector's name.
setArgumentName(new ArgumentName(collectorName));
addNameChangeListener(new NameChangeListener() {
public void nameChanged(NameChangeEvent e) {
setArgumentName(new ArgumentName(collectorName));
}
});
}
/**
* {@inheritDoc}
*/
@Override
void setInputNum(int inputNum) {
if (inputNum != 0) {
throw new IllegalArgumentException("Attempt to change the input number of a collector input.");
}
}
/**
* Burn this input
* @param burnt boolean True to burn, false to unburn.
*/
@Override
public void setBurnt(boolean burnt){
throw new UnsupportedOperationException("Can't burn an input of a collector");
}
/**
* {@inheritDoc}
*/
@Override
public final String getOriginalInputName() {
return collectorName;
}
/**
* {@inheritDoc}
*/
@Override
final void setOriginalInputName(String originalName) {
throw new UnsupportedOperationException();
}
}
}