/*
* 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.
*/
/*
* GemGraph.java
* Creation date: (10/18/00 1:00:12 PM)
* By: Luke Evans
*/
package org.openquark.gems.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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.TreeMap;
import java.util.Map.Entry;
import java.util.logging.Level;
import org.openquark.cal.compiler.CALSourceGenerator;
import org.openquark.cal.compiler.CompositionNode;
import org.openquark.cal.compiler.FieldName;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.PolymorphicVarContext;
import org.openquark.cal.compiler.RecordType;
import org.openquark.cal.compiler.Scope;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy;
import org.openquark.cal.compiler.SourceModel;
import org.openquark.cal.compiler.TypeException;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.TypeChecker.TypeCheckInfo;
import org.openquark.cal.services.CALWorkspace;
import org.openquark.cal.valuenode.DefaultValueNodeTransformer;
import org.openquark.cal.valuenode.ValueNode;
import org.openquark.cal.valuenode.ValueNodeBuilderHelper;
import org.openquark.cal.valuenode.ValueNodeTransformer;
import org.openquark.gems.client.Gem.PartConnectable;
import org.openquark.gems.client.Gem.PartInput;
import org.openquark.util.Pair;
import org.openquark.util.UnsafeCast;
/**
* A graph of gems (the supercombinator we're building).
* Terminology:
* Tree - a set of gems connected to each other (visually)
* Forest - a set of trees which are connected to each other via collector-emitter pairs.
* Root - the ancestor of all other gems in a tree. It's either a collector or a
* functional agent gem with an unconnected output.
* GemGraph - the set of all gems we're working with. Forms a partition set on forests present.
*
* @author Luke Evans
*/
public class GemGraph implements TypeStringProvider {
/** The target collector for this gem graph. */
private final CollectorGem targetCollector;
/** Used for creating new ValueNodes */
private ValueNodeBuilderHelper valueNodeBuilderHelper;
/** Used for transforming the ValueNodes (again for the validation of the ValueGem) */
private final ValueNodeTransformer valueNodeTransformer;
/** The set of gems that we are currently working with. */
private Set<Gem> gemSet;
/** The connections between gems. */
private final Set<Connection> connectionSet;
private final Set<CollectorGem> collectorSet;
/** Used to track type and row variables in the types represented in all gems in the GemGraph */
private PolymorphicVarContext polymorphicVarContext = PolymorphicVarContext.make();
/** the index of the next collector to create */
private int nextCollectorIndex = 1;
/** The index of the next code gem to create. */
private int nextCodeGemIndex = 1;
/** Listeners for gem connection events. */
private GemConnectionListener gemConnectionListener;
private final List<GemGraphChangeListener> gemGraphChangeListeners;
/** The object responsible for retargeting arguments in response to certain gem model events. */
private final GemGraphArgumentManager argumentManager;
/** A StateEditable used to undo argument changes resulting from intermediate states used during type checking. */
private final CollectorArgumentStateEditable collectorArgumentStateEditable;
/**
* A visitor that visits connected gems in a gem graph.
* Creation date: (01/22/02 4:52:22 PM)
* @author Edward Lam
*/
public static abstract class GemVisitor {
/** The scope of visitor.*/
private final TraversalScope traversalScope;
/**
* The target collector for the scope to explore.
* Gems outside of the scope defined by the target collector will not be traversed.
* If null, all gems will be traversed.
*/
private final CollectorGem targetCollector;
/**
* Constructor for a GemVisitor
*
* @param traversalScope the scope of this visitor.
* @param targetCollector the target collector for the scope to explore.
* Gems outside of the scope defined by the target collector will not be traversed.
* If null, all gems will be traversed.
*/
public GemVisitor(TraversalScope traversalScope, CollectorGem targetCollector) {
this.traversalScope = traversalScope;
this.targetCollector = targetCollector;
}
/**
* Notify the visitor that we have visited a gem.
* @param gemVisited Gem the visited gem
* @return boolean true if we can stop traversing
*/
public abstract boolean visitGem(Gem gemVisited);
/**
* Get the scope of this visitor.
* @return the scope of this visitor.
*/
public final TraversalScope getTraversalScope() {
return traversalScope;
}
/**
* Get the target collector for this visitor.
* @return The target collector for the scope to explore.
* Gems outside of the scope defined by the target collector will not be traversed.
* If null, all gems will be traversed.
*/
public final CollectorGem getTargetCollector() {
return targetCollector;
}
}
/**
* Traversal scope enum pattern.
* @author Edward Lam
*/
public static final class TraversalScope {
private final String typeString;
/**
* Constructor for a traversal scope.
* @param typeString user-readable name for the type.
*/
private TraversalScope(String typeString) {
this.typeString = typeString;
}
@Override
public String toString() {
return typeString;
}
/** Traverses the tree only. */
public static final TraversalScope TREE = new TraversalScope("Tree");
/** Traverses the tree plus other trees connected by collector-emitter pairs, within a given scope. */
public static final TraversalScope FOREST = new TraversalScope("Forest");
}
/**
* Input collect mode enum pattern.
* Creation date: (02/08/02 2:07:00 PM)
* @author Edward Lam
*/
public static final class InputCollectMode {
public static final InputCollectMode ALL = new InputCollectMode ();
public static final InputCollectMode BURNT_ONLY = new InputCollectMode ();
public static final InputCollectMode UNBURNT_ONLY = new InputCollectMode ();
}
/**
* Constructor for a GemGraph.
*/
public GemGraph() {
// Create the Sets and Maps for the gems and connections
gemSet = new HashSet<Gem>();
connectionSet = new HashSet<Connection>();
collectorSet = new HashSet<CollectorGem>();
valueNodeTransformer = new DefaultValueNodeTransformer();
gemGraphChangeListeners = new ArrayList<GemGraphChangeListener>();
collectorArgumentStateEditable = new CollectorArgumentStateEditable(this);
// Create the target.
this.targetCollector = new CollectorGem(null);
// Instantiate the argument manager
argumentManager = new GemGraphArgumentManager(targetCollector);
// Add the target to the gem graph.
addGem(targetCollector);
}
/**
* @see Object#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder ("GemGraph:\n");
for (final Gem rootGem : getRoots()) {
displayGemSubtreeText (rootGem, sb, 0);
}
return sb.toString();
}
/**
* Display a gem tree to the console
* @param gem the root of the gem tree to display.
*/
public static void displayGemSubtree(Gem gem) {
StringBuilder sb = new StringBuilder();
if (gem instanceof CollectorGem) {
CollectorGem collector = (CollectorGem) gem;
sb.append(collector.getUnqualifiedName() + ":\n");
}
displayGemSubtreeText(gem, sb, 0);
System.out.println(sb.toString());
}
/**
* A helper function for showing the gem graph as a string.
*/
public static void displayGemSubtreeText(Gem gem, StringBuilder sb, int indentLevel) {
final String indentString = " ";
for (int indentN = 0; indentN < indentLevel; ++indentN) {
sb.append(indentString);
}
sb.append(gem.toString());
sb.append('\n');
++indentLevel;
Gem.PartInput [] inputs = gem.getInputParts();
for (int i = 0; i < inputs.length; ++i) {
Gem.PartInput input = inputs[i];
if (input.isBurnt()) {
for (int indentN = 0; indentN < indentLevel; ++indentN) {
sb.append(indentString);
}
sb.append(input.getArgumentName().getCompositeName());
sb.append(" :: ");
sb.append(input.getType());
sb.append(" <burnt>\n");
} else if (!input.isConnected()) {
for (int indentN = 0; indentN < indentLevel; ++indentN) {
sb.append(indentString);
}
sb.append(input.getArgumentName().getCompositeName());
sb.append(" :: ");
sb.append(input.getType().toString());
sb.append(" <free>\n");
} else {
displayGemSubtreeText(input.getConnection().getSource().getGem(), sb, indentLevel);
}
}
}
/**
* Insert a Gem into this GemGraph
* @param gem Gem the Gem to insert
* @return Gem the Gem we just added (for convenience)
* @throws IllegalArgumentException if the gem to be added is already connected to other gems (since
* these other gems will not be in the gem graph) or if the gem is already in the gem graph.
*/
public Gem addGem(Gem gem) {
if (gem == null) {
throw new NullPointerException();
}
// Gems must be unconnected if you want to add them.
if (gem.isConnected()) {
throw new IllegalArgumentException("Attempt to add an already connected gem to the gem graph");
}
// Check that the gem name (if any) doesn't conflict with any gems already in the gem graph.
if (gem instanceof CodeGem) {
CodeGem codeGem = (CodeGem)gem;
if (codeGem.isNameInitialized() && getCodeGemNames().contains(((CodeGem)gem).getUnqualifiedName())) {
throw new IllegalArgumentException("Attempt to add a code gem to a gem graph which already contains a code gem with the same name.");
}
} else if (gem instanceof CollectorGem) {
CollectorGem collectorGem = (CollectorGem)gem;
if (collectorGem.isNameInitialized() && getCollectorNames().contains(((CollectorGem)gem).getUnqualifiedName())) {
throw new IllegalArgumentException("Attempt to add a collector gem to a gem graph which already contains a collector gem with the same name.");
}
}
// Add the Gem. If the gem was already in the set then throw illegal argument exception.
if (!gemSet.add(gem)) {
throw new IllegalArgumentException("Attempt to add the gem twice to the gem graph");
}
// special handling for various gems
if (gem instanceof CollectorGem) {
CollectorGem cGem = (CollectorGem)gem;
// Try to assign the default name here. The function will make sure that this only gets done once so
// we don't need to worry about the counter getting thrown off if we do this more than once.
assignDefaultName(cGem);
// add this collector to the collectorSet
collectorSet.add(cGem);
// re-target the input argument.
argumentManager.retargetArgumentsOnAdd(cGem);
// Ensure that the collector has a target.
if (cGem != targetCollector && cGem.getTargetCollector() == null) {
cGem.setTargetCollector(targetCollector);
}
// Verify that the collector doesn't have any emitters yet
if (!cGem.getReflectors().isEmpty()) {
throw new IllegalArgumentException("A collector can only be added to the gem graph if it has no emitters.");
}
} else if (gem instanceof ReflectorGem) {
ReflectorGem rGem = (ReflectorGem)gem;
CollectorGem cGem = rGem.getCollector();
cGem.addReflector(rGem);
// Verify that the collector for this reflector is already in the gem graph
if (!gemSet.contains(cGem) || !collectorSet.contains(cGem)) {
throw new IllegalArgumentException("Attempt to add a reflector to the gem graph without first adding the collector");
}
} else if (gem instanceof CodeGem) {
// Assign the default name here.
assignDefaultName((CodeGem)gem);
}
// notify listeners.
for (final GemGraphChangeListener listener : gemGraphChangeListeners) {
listener.gemAdded(new GemGraphAdditionEvent(gem));
}
return gem;
}
/**
* Remove a Gem from this GemGraph. Also invokes its cleanup code.
* Not "exceptional" if the gem is already removed.
* @param gem the Gem to remove
* @throws IllegalArgumentException if the gem to be removed is still connected to other gems or
* if it does not exist in this gem graph.
*/
public void removeGem(Gem gem) {
// Disallow deletion of the target gem.
if (gem == targetCollector) {
GemCutter.CLIENT_LOGGER.log(Level.WARNING, "Attempt to delete the target gem from the gem graph.");
return;
}
// Gems must be unconnected if you want to remove them
if (gem.getOutputPart() != null && gem.getOutputPart().isConnected()) {
throw new IllegalArgumentException("Attempt to remove a gem that is still connected");
}
PartInput[] inputs = gem.getInputParts();
for (int i=0; i<inputs.length; ++i) {
if (inputs[i].isConnected()) {
throw new IllegalArgumentException("Attempt to remove a gem that is still connected");
}
}
if (gem instanceof CollectorGem) {
CollectorGem collector = (CollectorGem)gem;
// Remove the collector's argument from its target.
// We must do this before removing the collector, since the gem needs to be in the gem graph for argument target
// resolution to happen properly.
argumentManager.retargetArgumentsOnRemove(collector, targetCollector);
}
// Remove the Gem (if the gem was not in the set then throw illegal argument exception)
if (!gemSet.remove(gem)) {
throw new IllegalArgumentException("Attempt to remove a gem that does not exist in the gem graph");
}
if (gem instanceof ReflectorGem) {
// Remove this gem from the collector
ReflectorGem rGem = (ReflectorGem)gem;
CollectorGem cGem = rGem.getCollector();
cGem.removeReflector(rGem);
} else if (gem instanceof CollectorGem) {
CollectorGem collector = (CollectorGem)gem;
// Do some error checking first
if (!collectorSet.contains(collector)) {
throw new IllegalStateException("Attempt to remove a collector from the gem graph that is not in the collector set");
}
if (!collector.getReflectors().isEmpty()) {
throw new IllegalArgumentException("All emitters for a collector must be removed from the gem graph before removing the collector");
}
// Remove this gem from the collectors set
collectorSet.remove(collector);
}
for (final GemGraphChangeListener listener : gemGraphChangeListeners) {
listener.gemRemoved(new GemGraphRemovalEvent(gem));
}
}
/**
* Make a new connection between two gems.
*
* @param partOutput
* @param partInput
*/
public void connectGems (Gem.PartOutput partOutput, Gem.PartInput partInput) {
connectGems(new Connection(partOutput, partInput));
}
/**
* Make a connection using an existing connection. In order to facilitate proper undo and redo we
* need to be able to add back an existing connection object rather than create a new connection.
* Emitters will also be updated as appropriate
*
* @param conn the existing connection
* @throws IllegalArgumentException if either of the input or output parts are already connected or
* are burnt. Also throws IllegalArgumentException if the gems to which the input or output parts
* belong are not in the gem graph.
*/
public void connectGems(Connection conn) {
// Do some error checking here
Gem.PartOutput from = conn.getSource();
Gem.PartInput to = conn.getDestination();
if (from.isConnected()
|| to.isConnected()
|| to.isBurnt()) {
throw new IllegalArgumentException("Attempt to make an illegal gem connection");
}
if (!gemSet.contains(from.getGem())) {
throw new IllegalArgumentException("Attempt to make an illegal gem connection");
}
if (!gemSet.contains(to.getGem())) {
throw new IllegalArgumentException("Attempt to make an illegal gem connection");
}
if (!to.isValid()) {
throw new IllegalArgumentException("Attempt to connect to an invalid input");
}
// Add the connection to the graph
connectionSet.add(conn);
// Let the Gems know that their interfaces are bound
from.bindConnection(conn);
to.bindConnection(conn);
// Handle argument retargeting.
argumentManager.retargetForConnect(conn);
// Send all interested listeners the notification of connection
if (gemConnectionListener != null) {
gemConnectionListener.connectionOccurred(new GemConnectionEvent(conn, GemConnectionEvent.EventType.CONNECTION));
}
for (final GemGraphChangeListener listener : gemGraphChangeListeners) {
listener.gemConnected(new GemGraphConnectionEvent(conn));
}
}
/**
* Return whether making a given connection will result in a valid gem graph.
* @param conn the connection to make.
* @param typeCheckInfo the type check info to use to validate the gem graph.
* @return whether making a given connection will result in a valid gem graph.
*/
public boolean canConnect(Connection conn, TypeCheckInfo typeCheckInfo) {
Gem.PartOutput from = conn.getSource();
Gem.PartInput to = conn.getDestination();
// Check that the connecting parts aren't connected or burnt
if (from.isConnected() || to.isConnected() || to.isBurnt()) {
return false;
}
// Check that both gems are in the gem graph.
if (!(gemSet.contains(from.getGem()) && gemSet.contains(to.getGem()))) {
return false;
}
// Check that the destination is valid.
if (!to.isValid()) {
return false;
}
// Temporarily bind the parts to test.
from.bindConnection(conn);
to.bindConnection(conn);
// Calculate whether any collector arguments were affected.
boolean argumentsAffected = getInputArgumentTarget(to) != null;
// Grab the gem graph's current collector and argument state.
Hashtable<Object, Object> state = null;
if (argumentsAffected) {
state = new Hashtable<Object, Object>();
collectorArgumentStateEditable.storeState(state);
}
// Handle argument retargeting.
argumentManager.retargetForConnect(conn);
// Check for graph validity..
boolean canConnect = checkGraphValid(typeCheckInfo);
// Revert any changes we made.
from.bindConnection(null);
to.bindConnection(null);
if (argumentsAffected) {
collectorArgumentStateEditable.restoreState(state);
}
// Return the result.
return canConnect;
}
/**
* Return the collector which is targeted by the given input argument.
* This method simply asks all enclosing collectors whether it contains a given input as a targeting argument.
* Note: when disconnecting, this method should be called *before* the disconnection takes place.
* @param inputArgument the argument in question.
* @return the collector targeted by this argument, or null if no target can be found.
*/
public static CollectorGem getInputArgumentTarget(Gem.PartInput inputArgument) {
for (CollectorGem enclosingCollector = inputArgument.getGem().getRootCollectorGem();
enclosingCollector != null; enclosingCollector = enclosingCollector.getTargetCollectorGem()) {
if (enclosingCollector.isTargetedBy(inputArgument)) {
return enclosingCollector;
}
}
return null;
}
/**
* Analyze the current gem graph for inconsistencies in arguments targeting its collectors,
* and try to fix them up.
* An example of an inconsistency is when a collector holds onto a particular argument as a targeting argument,
* but that argument's input is not enclosed by that collector.
*/
void validateInputTargets() {
// Remove any arguments which collectors think target that collector, but whose inputs are not enclosed by it.
// Also remove any arguments which are connected.
for (final CollectorGem collectorGem : getCollectors()) {
for (final PartInput targetingArgument : collectorGem.getTargetArguments()) {
if (targetingArgument.isConnected() || getInputArgumentTarget(targetingArgument) != collectorGem) {
collectorGem.removeArgument(targetingArgument);
}
}
}
// Create a list of arguments whose inputs are rooted by collectors, but don't have targets.
List<PartInput> untargetedArguments = new ArrayList<PartInput>();
for (final Gem rootGem : getRoots()) {
if (rootGem instanceof CollectorGem) {
// Get the unburnt inputs on the descendant subtree.
List<PartInput> unburntTreeInputs =
obtainUnboundDescendantInputs(rootGem, TraversalScope.TREE, InputCollectMode.UNBURNT_ONLY);
// Iterate over the inputs.
for (final Gem.PartInput unburntTreeInput : unburntTreeInputs) {
// If the argument is unbound, add it to the list.
if (getInputArgumentTarget(unburntTreeInput) == null) {
untargetedArguments.add(unburntTreeInput);
}
}
}
}
// Now retarget the untargeted inputs to the target collector.
// TODOEL: update any reflectors reflecting the target collector.
getTargetCollector().addArguments(getTargetCollector().getTargetArguments().size(), untargetedArguments);
}
/**
* Disconnect the connection.
* Emitters will also be updated as appropriate.
* @param conn the connection to disconnect
*/
public void disconnectGems(Connection conn) {
Gem.PartConnectable from = conn.getSource();
Gem.PartConnectable to = conn.getDestination();
if (!gemSet.contains(from.getGem())) {
throw new IllegalArgumentException("Attempt to make an illegal gem connection");
}
if (!gemSet.contains(to.getGem())) {
throw new IllegalArgumentException("Attempt to make an illegal gem connection");
}
if (!connectionSet.contains(conn)) {
throw new IllegalArgumentException("Attempt to make an illegal gem connection");
}
// Remove from the graph
connectionSet.remove(conn);
// Let the Gems know that their interfaces are unbound (null connection).
from.bindConnection(null);
to.bindConnection(null);
argumentManager.retargetForDisconnect(conn);
// Send all interested listeners the notification of disconnection
if (gemConnectionListener != null) {
gemConnectionListener.disconnectionOccurred(new GemConnectionEvent(conn, GemConnectionEvent.EventType.DISCONNECTION));
}
for (final GemGraphChangeListener listener : gemGraphChangeListeners) {
listener.gemDisconnected(new GemGraphDisconnectionEvent(conn));
}
}
/**
* Retarget an input argument from one collector to another.
* @param collectorArgument the argument to retarget.
* @param newTarget the collector to which the input will be retargeted.
* @param addIndex the index at which the retargeted argument will be placed, or -1 to add to the end.
* @return the old argument index, or -1 if there was no old target.
*/
public int retargetInputArgument(PartInput collectorArgument, CollectorGem newTarget, int addIndex) {
// Defer to the method in the argument retargeter.
return argumentManager.retargetInputArgument(collectorArgument, newTarget, addIndex);
}
/**
* Retarget the inputs on the given gem for a definition change.
* @param changedGem the gem whose definition changed.
* @param oldInputs the inputs from before the change.
*/
public void retargetArgumentsForDefinitionChange(Gem changedGem, PartInput[] oldInputs) {
argumentManager.retargetArgumentsForDefinitionChange(changedGem, oldInputs);
}
/**
* Determine a composite argument name (unique to the forest) for each free input in the
* GemGraph and save the names into the appropriate PartInput.
* TODOEL: update for scoping. It is not necessary for names of all free inputs to be unique within the forest,
* only free inputs which show up as arguments at their scoping level.
*/
private void checkArgNamesDisambiguated() {
// Iterate through the roots and remember the ones that have been looked at already.
Set<Gem> checkedRoots = new HashSet<Gem>();
for (final Gem root : getRoots()) {
// Check to see if this root has been "checked" already - if so then continue on to the next root.
if (checkedRoots.contains(root)) {
continue;
}
// Get the forest roots for this Gem and add them to the set of "checked" roots
Set<Gem> forestRoots = obtainForestRoots(root, true);
checkedRoots.addAll(forestRoots);
// Iterate through the forest roots.
// Collect all free inputs into a big input list which is to be disambiguated wrt each other.
// Also build a set of all the CollectorGem names.
List<PartInput> inputList = new ArrayList<PartInput>();
Set<String> collectorNames = new HashSet<String>();
for (final Gem treeRoot : forestRoots) {
if (treeRoot instanceof CollectorGem) {
collectorNames.add(((CollectorGem)treeRoot).getUnqualifiedName());
}
inputList.addAll(obtainUnboundDescendantInputs(treeRoot, TraversalScope.TREE, InputCollectMode.ALL));
}
disambiguateCompositeArgNames(inputList, collectorNames);
}
}
/**
* Helper for disambiguateCompositeArgNames().
* Determines the composite names for a collection of PartInputs all belonging to the same forest.
* The names will be saved to the appropriate PartInput.
* @param inputList list of PartInputs for which to calculate composite arg names
* @param existingNames list of names that generated names must not conflict with (ie: collector names)
*/
private static void disambiguateCompositeArgNames(List<PartInput> inputList, Set<String> existingNames) {
Map<String, Integer> nameToFrequency = new HashMap<String, Integer>();
Map<String, Integer> savedNameToFrequency = new HashMap<String, Integer>();
Set<String> savedNames = new HashSet<String>();
Set<String> nakedCollectorNames = new HashSet<String>();
// Seed the savedNames set and the savedNameToFrequency and nameToFrequency maps
// with the elements of the existingNames set - this ensures that generated names/suffixes
// will not collide with any existing names.
savedNames.addAll(existingNames);
for (final String currentName : existingNames) {
int freq = 1;
if (nameToFrequency.containsKey(currentName)) {
freq = nameToFrequency.get(currentName).intValue() + 1;
}
nameToFrequency.put(currentName, Integer.valueOf(freq));
savedNameToFrequency.put(currentName, Integer.valueOf(freq));
}
// Iterate through the inputs and determine the frequency of each name.
// At the same time add the names that were saved by users to the set of saved names and
// also build a set of naked collector names.
for (final PartInput currInput : inputList) {
boolean baseNameSavedByUser = true;
String baseName = currInput.getDesignMetadata().getDisplayName();
if (baseName == null) {
baseName = currInput.getOriginalInputName();
baseNameSavedByUser = false;
}
// Update the set of names of unconnected ("naked") collectors.
if (currInput.getGem() instanceof CollectorGem) {
nakedCollectorNames.add(baseName);
}
// If the name was saved by a user or is from a naked collector then add it to the
// saved names set. Also update the frequency count for saved names.
//
// NOTE: We treat naked collector input names as though they were user saved so that
// other args with the same saved base name will have suffixes.
if (baseNameSavedByUser || currInput.getGem() instanceof CollectorGem) {
savedNames.add(baseName);
int freq = 1;
if (savedNameToFrequency.containsKey(baseName)) {
freq = savedNameToFrequency.get(baseName).intValue() + 1;
}
savedNameToFrequency.put(baseName, Integer.valueOf(freq));
}
// Update the frequency count.
int freq = 1;
if (nameToFrequency.containsKey(baseName)) {
freq = nameToFrequency.get(baseName).intValue() + 1;
}
nameToFrequency.put(baseName, Integer.valueOf(freq));
}
// A map to keep track of the next ordinal to used as a suffix for a given base name.
Map<String, Integer> nameToSuffixMap = new HashMap<String, Integer>();
// Pre-seed with the names of the naked collectors.
// Naked collector names should never have a suffix, so we handle them first.
for (final String nakedCollectorName : nakedCollectorNames) {
nameToSuffixMap.put(nakedCollectorName, Integer.valueOf(0));
}
// Iterate through the inputs again but this time generate a composite name for each.
for (final PartInput currInput : inputList) {
boolean baseNameSavedByUser = true;
String baseName = currInput.getDesignMetadata().getDisplayName();
String suffix = "";
if (baseName == null) {
baseName = currInput.getOriginalInputName();
baseNameSavedByUser = false;
}
// Only give the name a suffix if it doesn't belong to a naked collector.
// Naked collector names never get changed.
if (!(currInput.getGem() instanceof CollectorGem)) {
// If more than one input shares this name, a disambiguating suffix may be needed.
if (nameToFrequency.get(baseName).intValue() > 1) {
// Figure out how often the user saved the same basename. If more than once,
// then we need to disambiguate the saved name. Otherwise it can stay the same
// and we only need to disambiguate the not user-saved basenames.
int saveFreq = 0;
if (baseNameSavedByUser) {
saveFreq = savedNameToFrequency.get(baseName).intValue();
}
// If the name is not user-saved or it is saved more than once, then disambiguate it.
if (!baseNameSavedByUser || saveFreq != 1) {
// Get the last suffix we used.
int ordinal = 0;
if (nameToSuffixMap.containsKey(baseName)) {
ordinal = nameToSuffixMap.get(baseName).intValue();
}
String generatedName = baseName;
do {
ordinal++;
generatedName = baseName + "_" + ordinal;
// Make sure the new generated name isn't one of the saved names and
// also doesn't exist as any other name. For example, there might
// be the names a_1 and a_2 as argument names for a gem in the forest.
// If the users saves two names as 'a', then a_1 and a_2 are not valid
// disambiguated names for those inputs. Instead they become a_3 and a_4.
} while (savedNames.contains(generatedName) || nameToFrequency.containsKey(generatedName));
// Save the suffix we found.
suffix = "_" + ordinal;
nameToSuffixMap.put(baseName, Integer.valueOf(ordinal));
}
}
// Set the new name for the input.
ArgumentName inputName = new ArgumentName(baseName);
inputName.setDisambiguatingSuffix(suffix);
inputName.setBaseNameSavedByUser(baseNameSavedByUser);
currInput.setArgumentName(inputName);
}
}
}
/**
* Return a set of all the runnable gems in the graph.
* @return the set of all runnable gems
*/
public Set<Gem> getRunnableGems() {
Set<Gem> runnableGems = new HashSet<Gem>();
for (final Gem gem : gemSet) {
if (gem.isRunnable()) {
runnableGems.add(gem);
}
}
return runnableGems;
}
/**
* @param gem a gem
* @return whether the gem graph contains the gem.
*/
public boolean hasGem(Gem gem) {
return gemSet.contains(gem);
}
/**
* Return the set of Gems in this GemGraph.
* @return the (unmodifiable) set of gems in the gem graph
*/
public final Set<Gem> getGems() {
return Collections.unmodifiableSet(gemSet);
}
/**
* Return the set of Connections in this GemGraph.
* @return the set of connections in the gem graph
*/
public final Set<Connection> getConnections() {
return new HashSet<Connection>(connectionSet);
}
/**
* Get all rootGems in the GemGraph
* @return all root gems in the GemGraph.
*/
public Set<Gem> getRoots() {
Set<Gem> rootSet = new HashSet<Gem>();
for (final Gem nextGem : gemSet) {
Gem nextRoot = nextGem.getRootGem();
if (nextRoot != null) { // for unconnected target
rootSet.add(nextGem.getRootGem());
}
}
return rootSet;
}
/**
* Get all value rootGems in the GemGraph
* @return all value root gems in the GemGraph.
*/
private Set<Gem> getValueRoots() {
Set<Gem> rootSet = new HashSet<Gem>();
for (final Gem nextGem : gemSet) {
Gem nextRoot = nextGem.getRootGem();
if (nextRoot instanceof ValueGem) {
rootSet.add(nextGem.getRootGem());
}
}
return rootSet;
}
/**
* Returns the CAL source corresponding to the gem graph
* @return SourceModel.FunctionDefn the corresponding source model.
*/
public SourceModel.FunctionDefn.Algebraic getCALSource() {
return CALSourceGenerator.getFunctionSourceModel(getTargetCollector().getUnqualifiedName(), getTargetCollector(), Scope.PUBLIC);
}
/**
* Get all collectors in the GemGraph
* @return all collector gems in the GemGraph.
*/
public Set<CollectorGem> getCollectors(){
return new HashSet<CollectorGem>(collectorSet);
}
/**
* Get this gem graph's target collector.
* @return the target collector for this gem graph.
*/
public CollectorGem getTargetCollector() {
return targetCollector;
}
/**
* Organize the collectors so that outer collectors come before inner ones.
* @param collectorsToOrder the collectors to be ordered.
*/
public static Set<CollectorGem> getOrderedCollectorSet(Collection<CollectorGem> collectorsToOrder) {
Set<CollectorGem> orderedCollectorSet = new LinkedHashSet<CollectorGem>();
Set<CollectorGem> collectorsToAdd = new HashSet<CollectorGem>(collectorsToOrder);
while (!collectorsToAdd.isEmpty()) {
// Create a set with the collectors not yet added from this iteration.
Set<CollectorGem> collectorsToAddUpdated = new HashSet<CollectorGem>();
// Iterate over the collectors to add.
for (final CollectorGem collectorToAdd : collectorsToAdd) {
CollectorGem collectorTarget = collectorToAdd.getTargetCollectorGem();
if (collectorTarget == null || orderedCollectorSet.contains(collectorTarget)) {
// Add the collector if its target is already added.
orderedCollectorSet.add(collectorToAdd);
} else if (!collectorsToAdd.contains(collectorTarget)) {
// The gem graph does not know about this collector's target. What to do??
GemCutter.CLIENT_LOGGER.log(Level.WARNING, "Attempt to save a collector whose target is not in the gem graph.");
} else {
// If the target has not been added, save for the next iteration.
collectorsToAddUpdated.add(collectorToAdd);
}
}
collectorsToAdd = collectorsToAddUpdated;
}
return orderedCollectorSet;
}
/**
* Get all code gem (unqualified) names in the GemGraph.
* @return all (unqualified) code gem names in the GemGraph.
*/
public Set<String> getCodeGemNames(){
Set<String> codeGemNames = new HashSet<String>();
// iterate over the code gems.
for (final Gem gem : gemSet) {
if (gem instanceof CodeGem) {
codeGemNames.add(((CodeGem)gem).getUnqualifiedName());
}
}
return codeGemNames;
}
/**
* Get all collector (unqualified) names in the GemGraph.
* @return all (unqualified) collector names in the GemGraph.
*/
public Set<String> getCollectorNames() {
Set<String> collectorNames = new HashSet<String>();
// iterate over the collectors
Set<CollectorGem> collectorSet = getCollectors();
for (final CollectorGem collectorGem : collectorSet) {
collectorNames.add(collectorGem.getUnqualifiedName());
}
return collectorNames;
}
/**
* Get all collectors in the GemGraph with a given name.
* Note that we can temporarily have more than one collector with a given name
* if we are changing it - this is just an invalid permanent state.
* @param name String the name to check for.
* @return all collector gems in the GemGraph with a given name
*/
public Set<CollectorGem> getCollectorsForName(String name) {
// set of collectors whose names match
Set<CollectorGem> matchingCollectorSet = new HashSet<CollectorGem>();
// iterate over the collectors
Set<CollectorGem> collectorSet = getCollectors();
for (final CollectorGem collectorGem: collectorSet) {
if (name.equals(collectorGem.getUnqualifiedName())){
matchingCollectorSet.add(collectorGem);
}
}
return matchingCollectorSet;
}
/**
* If the code gem hasn't had a name assigned yet then generate a default name and assign it.
* @param codeGem the gem to which to assign a name
* @return boolean - true if the name is set here, false if the name has already been set.
*/
boolean assignDefaultName(CodeGem codeGem) {
// Check that the code gem's name has not already been assigned
if (!codeGem.isNameInitialized()) {
// we must take care not to assign a pre-existing name
Set<String> invalidNames = getCodeGemNames();
// code gems and collector gems share a common namespace
invalidNames.addAll(getCollectorNames());
String defaultName;
do {
// As no name has yet been supplied, we make one up
defaultName = "newCodeGem" + nextCodeGemIndex;
nextCodeGemIndex++;
} while (invalidNames.contains(defaultName));
// assign the name
codeGem.setName(defaultName);
return true;
}
return false;
}
/**
* If the collector hasn't had a name assigned yet then generate a default name for the let
* variable associated with a collector, and update the relevant associations.
* @param collectorGem CollectorGem the gem to which to assign a name
* @return boolean - true if the name is set here, false if the name has already been set.
*/
boolean assignDefaultName(CollectorGem collectorGem) {
// Check that the collector's name has not already been assigned
if (!collectorGem.isNameInitialized()) {
// we must take care not to assign a pre-existing name
Set<String> invalidNames = getCollectorNames();
// collectors and code gems share a common namespace
invalidNames.addAll(getCodeGemNames());
String defaultName;
do {
defaultName = "value" + nextCollectorIndex;
nextCollectorIndex++;
} while (invalidNames.contains(defaultName));
// assign the name
collectorGem.setName(defaultName);
return true;
}
return false;
}
/**
* Sort a collection of Gems that implement the NamedNode interface alphabetically by unqualified name
* If tobeSorted items are not of type NamedGem, an Error is thrown
* @param toBeSorted the collection of Gems that implement the NamedNode interface
* that need sorting
* @return a list of Gems from toBeSorted in alphabetical order by unqualified name
*/
static List<Gem> sortNamedGemsInAlphabeticalOrder(Collection<Gem> toBeSorted) {
// A comparator for the NamedNode Interface that will
// compare the names of the nodes for sorting purposes
final class NamedNodeComparator implements Comparator<Object> {
public int compare(Object o1, Object o2) {
// make sure o1 and o2 are really NamedNodes
if (o1 instanceof NamedGem && o2 instanceof NamedGem) {
String qName1 = ((NamedGem)o1).getUnqualifiedName();
String qName2 = ((NamedGem)o2).getUnqualifiedName();
return qName1.compareTo(qName2);
} else {
throw new Error("GemGraph.sortTargetsInAlphabeticalOrder(): Items are not NamedNodes.");
}
}
}
// create a list from toBeSorted, sort the list and return it
NamedNodeComparator nodeComparator = new NamedNodeComparator();
List<Gem> namedGemList = new ArrayList<Gem>(toBeSorted);
Collections.sort(namedGemList, nodeComparator);
return namedGemList;
}
/**
* Update the references to functional agents in the GemGraph.
* @param workspace the workspace from which to obtain functional agent references
*/
void updateFunctionalAgentReferences(CALWorkspace workspace) throws GemEntityNotPresentException {
for (final Gem gem : gemSet) {
if (gem instanceof FunctionalAgentGem) {
((FunctionalAgentGem)gem).updateGemEntity(workspace);
}
}
}
/**
* Tells you if this gem forms the root of a gem tree/forest that is broken.
* @param gem the gem whose descendants we should look at.
* @param traversalScope the visitor scope.
* @return boolean if this gem roots a gem tree that is broken
*/
private static boolean isAncestorOfBrokenGem(Gem gem, TraversalScope traversalScope){
// declare a visitor that can detect broken descendants
class BrokenDescendantDetector extends GemVisitor {
boolean brokenDescendantFound = false;
BrokenDescendantDetector(TraversalScope traversalScope) {
super(traversalScope, null);
}
@Override
public boolean visitGem(Gem gemVisited){
if (brokenDescendantFound) {
return true;
}
if (gemVisited.isBroken()) {
brokenDescendantFound = true;
return true;
}
return false;
}
boolean foundBrokenDescendant() {
return brokenDescendantFound;
}
}
// create a visitor of appropriate type
BrokenDescendantDetector brokenDescendantDetector = new BrokenDescendantDetector(traversalScope);
// visit and detect
gem.visitDescendants(brokenDescendantDetector);
return brokenDescendantDetector.foundBrokenDescendant();
}
/**
* Tells you if this gem forms the root of a gem tree that is broken, or any
* child trees are broken
* @param gem the gem whose descendants we should look at.
* @return boolean if this gem roots a gem forest that is broken
*/
static boolean isAncestorOfBrokenGemForest(Gem gem){
return isAncestorOfBrokenGem(gem, TraversalScope.FOREST);
}
/**
* Tells you if this gem forms the root of a gem tree (not forest) that is broken.
* @param gem the gem whose descendants we should look at.
* @return boolean if this gem roots a gem tree that is broken
*/
static boolean isAncestorOfBrokenGemTree(Gem gem){
return isAncestorOfBrokenGem(gem, TraversalScope.TREE);
}
/**
* Returns whether this gem and its descendants are all let gems (collectors and emitters and 0-argument reflectors).
* @param gem Gem the gem whose descendants we should look at.
* @return boolean true if this is the ancestor of a let chain
*/
static boolean isLetChainAncestor(Gem gem){
// declare a visitor that detects non-let gems
class LetGemDetector extends GemVisitor {
boolean foundNonLet = false;
LetGemDetector() {
super(TraversalScope.FOREST, null); // it's a forest visitor
}
@Override
public boolean visitGem(Gem gemVisited){
if (foundNonLet) {
return true;
}
// detect let
if (!(gemVisited instanceof CompositionNode.Collector ||
(gemVisited instanceof CompositionNode.Emitter && gemVisited.getNInputs() == 0))) {
foundNonLet = true;
return true;
}
return false;
}
boolean foundLetChain() {
return !foundNonLet;
}
}
// detect chain of let gems
LetGemDetector letGemDetector = new LetGemDetector();
gem.visitDescendants(letGemDetector);
return letGemDetector.foundLetChain();
}
/**
* Obtain the root of a gem's tree, as well as the roots of its "ancestors".
* @param gem Gem the gem whose ancestors to obtain.
* @param excludeSet a set of roots to exclude from gathering
* ie. if we find one of these, we ignore it and its ancestors.
* @return the set of roots gathered
*/
private static Set<Gem> obtainAncestorRoots(Gem gem, final Set<Gem> excludeSet){
// declare a visitor that can collect root gems
class AncestorRootObtainer extends GemVisitor {
Set <Gem> rootSet = new HashSet<Gem>();
AncestorRootObtainer() {
super(TraversalScope.FOREST, null); // it's a forest visitor
}
@Override
public boolean visitGem(Gem gemVisited){
if (excludeSet.contains(gemVisited)) {
return true;
}
// add if its a root
if (gemVisited.isRootGem()) {
rootSet.add(gemVisited);
}
return false;
}
Set<Gem> getRoots() {
return rootSet;
}
}
// collect the root gems
AncestorRootObtainer ancestorRootObtainer = new AncestorRootObtainer();
gem.visitAncestors(ancestorRootObtainer);
return ancestorRootObtainer.getRoots();
}
/**
* Calculate the depth of a collector gem.
* A collector with no target has depth 0.
* A collector which targets a collector with no target has depth 1.
* A collector which targets a collector with depth 1 has depth 2.
* etc.
*
* @param collectorGem the collector gem for which to calculate the depth.
* @return the "depth" of the collector gem.
*/
static int getCollectorDepth(CollectorGem collectorGem) {
int collectorDepth = 0;
for (CollectorGem collectorTarget = collectorGem.getTargetCollectorGem();
collectorTarget != null;
collectorTarget = collectorTarget.getTargetCollectorGem()) {
collectorDepth++;
}
return collectorDepth;
}
/**
* Determine if a set of collectors form a single line of targeting "ancestry".
* What this means is that for any two collectors in the set, one of the collectors is enclosed by the other.
*
* @param collectorSet the collectors to analyze.
* @return true if the collectors in the set form a single line of targeting "ancestry".
* Also true if the set is empty.
*/
public static boolean formsAncestorChain(Set<CollectorGem> collectorSet) {
// Create a map, sorted by key, from collector depth to collector gem.
Map<Integer, CollectorGem> depthToAncestorMap = new TreeMap<Integer, CollectorGem>();
for (final CollectorGem collectorGem : collectorSet) {
// Add the mapping.
int collectorDepth = getCollectorDepth(collectorGem);
CollectorGem oldValue = depthToAncestorMap.put(Integer.valueOf(collectorDepth), collectorGem);
// If there are duplicate mappings with different values, this can't be an ancestor chain.
if (oldValue != null && oldValue != collectorGem) {
return false;
}
}
// Check that the collector at a given depth encloses the collector at the next depth.
CollectorGem lastAncestor = null;
for (final Integer depth : depthToAncestorMap.keySet()) {
CollectorGem ancestor = depthToAncestorMap.get(depth);
if (lastAncestor != null && !lastAncestor.enclosesCollector(ancestor)) {
return false;
}
lastAncestor = ancestor;
}
return true;
}
/**
* Returns all of the collectors enclosing a given gem.
* ie. the collector gem at the head of the tree, its target, its target's target, ...
* The iterator on this set will return the collectors in reverse scope order (ie. innermost collector first, outermost last).
* @param gem the gem in question.
* @return the enclosing collectors. An iterator on this set will return the collectors in
* reverse scope order.
*/
public static Set<CollectorGem> obtainEnclosingCollectors(Gem gem) {
Set<CollectorGem> enclosingCollectorSet = new LinkedHashSet<CollectorGem>();
for (CollectorGem enclosingRoot = gem.getRootCollectorGem(); enclosingRoot != null; enclosingRoot = enclosingRoot.getTargetCollectorGem()) {
enclosingCollectorSet.add(enclosingRoot);
}
return enclosingCollectorSet;
}
/**
* Returns the outermost collector enclosing a given gem.
* @param gem the gem in question.
* @return the outermost enclosing collector.
*/
public static CollectorGem obtainOutermostCollector(Gem gem) {
CollectorGem outermostCollector = gem.getRootCollectorGem();
if (outermostCollector != null) {
for (CollectorGem enclosingCollector = outermostCollector.getTargetCollectorGem();
enclosingCollector != null; enclosingCollector = outermostCollector.getTargetCollectorGem()) {
outermostCollector = enclosingCollector;
}
}
return outermostCollector;
}
/**
* Returns all of the descendents gems connected to this gem (returns the tree it resides in).
* @param gem
* @return the descendant gems.
* An iterator on this set will return the gems in the order that they would be encountered
* in a pre-order traversal of the descendant tree.
*/
public static Set<Gem> obtainSubTree(Gem gem){
return obtainSubTreeGems(gem, null);
}
/**
* Returns all of the descendent gems connected to this gem (returns the tree it resides in).
* If a gemClass is specified, then only gems of this type will be returned.
* If gemClass is null, then all gems in the subtree will be returned.
*/
public static Set<Gem> obtainSubTreeGems(Gem gem, final Class<?> gemClass) {
class TreeObtainer extends GemVisitor {
// the set of connected gems
Set<Gem> connectedGems = new LinkedHashSet<Gem>();
/**
* Constructor for the TreeObtainer
*/
TreeObtainer () {
super(TraversalScope.TREE, null);
}
/**
* Implementation of the visit Gem Method
* @param gemVisited
* @return boolean
*/
@Override
public boolean visitGem(Gem gemVisited){
if (gemClass == null || gemClass.isInstance(gemVisited)) {
connectedGems.add(gemVisited);
}
return false;
}
/**
* @return set of connected gems
*/
public Set<Gem> getConnectedGems() {
return connectedGems;
}
}
// Create a new obtainer object
TreeObtainer treeObtainer = new TreeObtainer();
gem.visitDescendants(treeObtainer);
return treeObtainer.getConnectedGems();
}
/**
* Obtain roots of broken trees and their ancestor roots.
* @param rootSet the set of roots on which to operate
* @return the set of roots of broken trees and their ancestor roots.
*/
static Set<Gem> obtainBrokenAncestorRoots(Set<Gem> rootSet) {
// keep track of the roots we remove
Set<Gem> brokenSet = new HashSet<Gem>();
// iterate over the roots
for (final Gem nextRoot : rootSet) {
// skip this root if we already found it.
if (brokenSet.contains(nextRoot)) {
continue;
}
// add ancestors and anything it may define (ie. ancestors) to the broken set if the tree is broken
if (isAncestorOfBrokenGemTree(nextRoot)) {
brokenSet.addAll(obtainAncestorRoots(nextRoot, brokenSet));
}
}
return brokenSet;
}
/**
* Obtain all the roots of the trees of the forest of which a gem is part, including outer scopes.
*
* @param gemFromForest a gem in the forest.
* @param includeBroken boolean whether to include broken trees and their ancestors.
* @return the set of roots gathered.
*/
public static Set<Gem> obtainForestRoots(Gem gemFromForest, final boolean includeBroken){
return obtainForestRoots(Collections.singleton(gemFromForest), includeBroken);
}
/**
* Obtain all the roots of the trees of the forest of which a set of gems is part, including outer scopes.
*
* @param gemsInForest gems in the forest.
* @param includeBroken boolean whether to include broken trees and their ancestors.
* @return the set of roots gathered.
*/
public static Set<Gem> obtainForestRoots(Set<Gem> gemsInForest, final boolean includeBroken){
// declare a visitor that can collect root gems
class ForestRootObtainer extends GemVisitor {
Set <Gem> rootSet = new HashSet<Gem>();
ForestRootObtainer() {
super(TraversalScope.FOREST, null);
}
@Override
public boolean visitGem(Gem gemVisited){
// add if its a root, and (if we care) not broken
if (gemVisited.isRootGem()) {
if (includeBroken || !isAncestorOfBrokenGemTree(gemVisited)){
rootSet.add(gemVisited);
}
}
// never done early
return false;
}
Set<Gem> getRoots() {
return rootSet;
}
}
// collect the root gems
ForestRootObtainer forestRootObtainer = new ForestRootObtainer();
for (final Gem gem : gemsInForest) {
gem.visitGraph(forestRootObtainer);
}
return forestRootObtainer.getRoots();
}
/**
* Obtain unconnected inputs of the Gem tree of which this Gem is the root as well as (optionally) in descendant subtrees at the
* same scoping level.
* Note: outer (enclosing) scopes will not be traversed.
*
* @param gem the gem whose descendants we should look at.
* @param traversalScope the scope of descendant inputs to collect.
* @param inputCollectMode Which inputs to collect - all, burnt only, or unburnt only
* @return the List of unbound parts
*/
public static List<PartInput> obtainUnboundDescendantInputs(Gem gem, TraversalScope traversalScope, final InputCollectMode inputCollectMode) {
/*
* A visitor that can collect descendant parts
* How this works:
* When we iterate over the inputs, the unconnected ones that first appear are added to the unbound parts
* After a connected input, the inputs which appear after are saved, to appear after the inputs on the connected gem.
* After the last input is processed on a gem, we process any inputs saved to appear after the gem.
* If there are, we "pull them down" to appear after the last gem connected to an input on this gem.
* If there are no connected gems on this gem, then we can add all the saved inputs.
*/
class DescendantPartObtainer extends GemVisitor {
// the list of unbound parts
List<PartInput> unboundParts = new ArrayList<PartInput>();
// a map from a gem to a list of parts that should be added immediately after the gem's parts are added
Map<Gem, List<PartInput>> addPartsAfterGemMap = new HashMap<Gem, List<PartInput>>();
// the set of collectors whose inputs have been spoken for (but not necessarily traversed)
Set<CollectorGem> inputCollectors = new HashSet<CollectorGem>();
public DescendantPartObtainer(TraversalScope traversalScope, CollectorGem targetCollector) {
super(traversalScope, targetCollector);
}
@Override
public boolean visitGem(Gem gemVisited){
Gem addAfterGem = null;
// ignore if not at the target scoping level.
CollectorGem targetCollector = getTargetCollector();
if (targetCollector != null && !targetCollector.enclosesCollector(gemVisited.getRootCollectorGem())) {
return false; // never done early.
}
// iterate over the inputs
int numArgs = gemVisited.getNInputs();
for (int i = 0; i < numArgs; i++) {
Gem.PartInput input = gemVisited.getInputPart(i);
// if input is connected, further parts are added after the connected gem's inputs.
if (input.isConnected()) {
addAfterGem = input.getConnection().getSource().getGem();
// otherwise, add inputs if burn status is ok.
} else if (shouldCollectInput(inputCollectMode, input)) {
// input not connected. If there were no previous connected inputs, just add it.
if (addAfterGem == null) {
unboundParts.add(input);
// otherwise, save the inputs until the inputs of the previous connected input's gem are added
} else {
List<PartInput> addAfterPartsList = addPartsAfterGemMap.get(addAfterGem);
if (addAfterPartsList == null) {
addAfterPartsList = new ArrayList<PartInput>();
addPartsAfterGemMap.put(addAfterGem, addAfterPartsList);
}
addAfterPartsList.add(input);
}
}
}
// Special case for 0-input emitters while traversing subtrees
// Pretend the emitter has an input connected to a collector, unless we accounted for the collector's inputs already..
if (getTraversalScope() == TraversalScope.FOREST && gemVisited.getNInputs() == 0 && gemVisited instanceof ReflectorGem) {
CollectorGem collectorGem = ((ReflectorGem)gemVisited).getCollector();
if (!inputCollectors.contains(collectorGem) && (targetCollector != null && targetCollector.enclosesCollector(collectorGem))) {
inputCollectors.add(collectorGem);
addAfterGem = collectorGem;
}
}
List<PartInput> partsAfter = addPartsAfterGemMap.get(gemVisited);
if (partsAfter != null) {
if (addAfterGem == null) {
// there are no gems connected to inputs - we can add them to the unbound parts list as well now
unboundParts.addAll(partsAfter);
} else {
// add the parts to the list of parts waiting for the last connected gem's parts to be added
List<PartInput> addAfterPartsList = addPartsAfterGemMap.get(addAfterGem);
if (addAfterPartsList == null) {
addAfterPartsList = new ArrayList<PartInput>();
addPartsAfterGemMap.put(addAfterGem, addAfterPartsList);
}
addAfterPartsList.addAll(partsAfter);
}
}
// never done early
return false;
}
private boolean shouldCollectInput(InputCollectMode icm, Gem.PartInput input) {
return ((icm == InputCollectMode.ALL) ||
(icm == InputCollectMode.BURNT_ONLY && input.isBurnt()) ||
(icm == InputCollectMode.UNBURNT_ONLY && !input.isBurnt()));
}
List<PartInput> getUnboundParts() {
return unboundParts;
}
}
// create a visitor of appropriate type
CollectorGem targetCollector = gem.getRootCollectorGem();
DescendantPartObtainer descendantPartObtainer = new DescendantPartObtainer(traversalScope, targetCollector);
// visit and detect
gem.visitDescendants(descendantPartObtainer);
return descendantPartObtainer.getUnboundParts();
}
/**
* Obtain all the unconnected parts in all associated trees (including outer scopes) of which this gem is part.
* @param forestGem Gem a gem in the forest.
* @return the set of unbound parts
*/
static Set<PartConnectable> obtainUnboundGemForestParts(Gem forestGem){
// declare a visitor that can collect unbound parts
class UnboundPartObtainer extends GemVisitor {
Set <PartConnectable> partSet = new HashSet<PartConnectable>();
UnboundPartObtainer() {
super(TraversalScope.FOREST, null);
}
@Override
public boolean visitGem(Gem gemVisited){
List<PartConnectable> connectableParts = gemVisited.getConnectableParts();
// add unconnected parts if any
for (final PartConnectable part : connectableParts) {
if (!part.isConnected()) {
partSet.add(part);
}
}
// never done early
return false;
}
Set<PartConnectable> getParts() {
return partSet;
}
}
// collect the root gems
UnboundPartObtainer unboundPartObtainer = new UnboundPartObtainer();
forestGem.visitGraph(unboundPartObtainer);
return unboundPartObtainer.getParts();
}
/**
* Disconnects all the valueGems, (this is a helper method that should be used in conjunction with reconnectValueGems()
* @param parametricsOnly whether to disconnect only parametric value gems, or all value gems.
* @return the connections which were disconnected.
* The iteration order remains constant with time.
*/
private Set<Connection> disconnectValueGems(boolean parametricsOnly) {
Set<Connection> oldConnections = new LinkedHashSet<Connection>();
// Go through all the gems in the graph
for (final Gem gem : getGems()) {
// we're only interested in value gems
if (gem instanceof ValueGem) {
ValueGem valueGem = (ValueGem)gem;
// Disconnect.
if (gem.isConnected() && (!parametricsOnly || valueGem.getValueNode().containsParametricValue())) {
Connection connection = gem.getOutputPart().getConnection();
oldConnections.add(connection);
connection.getSource().bindConnection(null);
connection.getDestination().bindConnection(null);
}
}
}
return oldConnections;
}
/**
* Infer the types of the all value gems outputs.
* ie. return what the types of the inputs would be if all value gems were disconnected.
* @param parametricsOnly whether to calculate on the basis of all value gems being disconnected, or only parametric value gems.
* @param info the info to use for typing the tree.
* @return map from value gem to its inferred type.
* null if the value gem types could not be inferred.
*/
private Map<ValueGem, TypeExpr> inferValueGemTypes(boolean parametricsOnly, TypeCheckInfo info) {
// We must update/switch value gem values according to the types of the inputs to which they are connected.
// To do this, we disconnect them, type the tree, and then reconnect each valuegem, retyping them as appropriate.
Set<Connection> oldConnections = disconnectValueGems(parametricsOnly);
// the map which will be returned.
Map<ValueGem, TypeExpr> resultMap = new HashMap<ValueGem, TypeExpr>();
// Check for nothing to do..
if (oldConnections.isEmpty()) {
return resultMap;
}
// Create the dummy collector, set it as the target for the gem graph's target.
// It's the enclosing collector, so the name doesn't have to be unique..
CollectorGem tempTarget = new CollectorGem();
tempTarget.setName("valueGemArgumentCollector");
tempTarget.addArguments(0, Collections.singleton((PartInput)tempTarget.getCollectingPart()));
targetCollector.setTargetCollector(tempTarget);
this.gemSet = new HashSet<Gem>(gemSet); // prevents a ConcurrentModificationException..
this.gemSet.add(tempTarget);
this.collectorSet.add(tempTarget);
try {
// Add the newly-freed arguments to the dummy collector.
Set<PartInput> freedArgumentSet = new LinkedHashSet<PartInput>();
for (final Connection conn : oldConnections) {
PartInput freedArgument = conn.getDestination();
freedArgumentSet.add(freedArgument);
}
tempTarget.addArguments(0, freedArgumentSet);
// Type the resulting graph..
Map<Object, TypeExpr> unboundPartTypeMap = null;
try {
unboundPartTypeMap = getUnboundPartTypes(info);
} catch (TypeException e) {
// Can't do much about this..
GemCutter.CLIENT_LOGGER.log(Level.WARNING, "Unable to infer value gem types.", e);
return null;
}
// Populate the result map..
for (final Connection conn : oldConnections) {
Gem.PartInput freedInput = conn.getDestination();
ValueGem valueGem = (ValueGem)conn.getSource().getGem();
resultMap.put(valueGem, unboundPartTypeMap.get(freedInput));
}
} finally {
targetCollector.setTargetCollector(null);
gemSet.remove(tempTarget);
collectorSet.remove(tempTarget);
reconnectConnections(oldConnections);
}
return resultMap;
}
/**
* Reconnects all the connections.
* This is a helper method to be used in conjunction with some methods that perform temporary disconnections.
* @param oldConnections the connections to reconnect
*/
static void reconnectConnections(Set<Connection> oldConnections) {
for (final Connection connection : oldConnections) {
connection.getDestination().bindConnection(connection);
connection.getSource().bindConnection(connection);
}
}
/**
* Returns the least constrained value of this valueGem as dictated by its connections.
* This method ignores the type constraints placed by this and other value gems. The
* returned value will be null if the value gem is connected to a broken gem graph.
*
* Example: add 1.0 2.0.
* The least constrained type of the 1.0 value is Num a => a -> a
*
* @param valueGem
* @param info
* @return TypeExpr
*/
public TypeExpr getLeastConstrainedValueType(ValueGem valueGem, TypeCheckInfo info) {
// If a valueGem is disconnected, it has no constraints!
if (!valueGem.isConnected()) {
return TypeExpr.makeParametricType();
}
Map<ValueGem, TypeExpr> inferredValueGemTypeMap = inferValueGemTypes(false, info);
return inferredValueGemTypeMap.get(valueGem);
}
/**
* Determines what the types of value gems would be if a specified value gem were to commit its value
* to a given value node.
* This may cause other value gems to switch their data types as well.
* @param valueGemToSwitch The value gem switching value.
* @param oldValueNode the value before the change.
* @param newValueNode the new value for the value gem
* @param info a TypeCheckInfo object to use for type checking.
* @return Map map from value gem to new value.
*/
public Map<ValueGem, ValueNode> getValueGemSwitchValues(ValueGem valueGemToSwitch, ValueNode oldValueNode, ValueNode newValueNode, TypeCheckInfo info) {
Map<ValueGem, ValueNode> valueGemToNewValueNodeMap = new HashMap<ValueGem, ValueNode>();
// If it's unconnected, all we have to do is set the valueNode of the destination valueGem!
// Otherwise... it's an tiny bit trickier...
if (!valueGemToSwitch.isConnected()) {
valueGemToNewValueNodeMap.put(valueGemToSwitch, newValueNode);
return valueGemToNewValueNodeMap;
}
Map<ValueGem, TypeExpr> valueGemTypeMap = inferValueGemTypes(false, info);
Map<PartInput, ValueNode> inputToValueNodeMap = new HashMap<PartInput, ValueNode>();
Map<PartInput, ValueGem> inputToValueGemMap = new HashMap<PartInput, ValueGem>();
Map<PartInput, TypeExpr> inputToUnconstrainedTypeMap = new HashMap<PartInput, TypeExpr>();
for (final Map.Entry<ValueGem, TypeExpr> mapEntry: valueGemTypeMap.entrySet()) {
ValueGem valueGem = mapEntry.getKey();
TypeExpr valueGemUnconstrainedType = mapEntry.getValue();
Gem.PartOutput valueGemOutput = valueGem.getOutputPart();
if (!valueGemOutput.isConnected()) {
// Don't have to worry about switching values for unconnected value gems.
continue;
}
Gem.PartInput connectedInput = valueGemOutput.getConnection().getDestination();
inputToValueGemMap.put(connectedInput, valueGem);
inputToUnconstrainedTypeMap.put(connectedInput, valueGemUnconstrainedType);
if (valueGem == valueGemToSwitch) {
inputToValueNodeMap.put(connectedInput, oldValueNode);
} else {
inputToValueNodeMap.put(connectedInput, valueGem.getValueNode());
}
}
// Get the switched values
InputValueTypeSwitchHelper switcher = new InputValueTypeSwitchHelper(valueNodeBuilderHelper, valueNodeTransformer);
Map<PartInput, ValueNode> inputToNewValueNodeMap = switcher.getInputSwitchValues(oldValueNode, newValueNode, inputToValueNodeMap, inputToUnconstrainedTypeMap);
// Populate the return map.
for (final Map.Entry<PartInput, ValueNode> mapEntry : inputToNewValueNodeMap.entrySet()) {
Gem.PartInput input = mapEntry.getKey();
ValueGem connectedValueGem = inputToValueGemMap.get(input);
ValueNode newInputValue = mapEntry.getValue();
valueGemToNewValueNodeMap.put(connectedValueGem, newInputValue);
}
valueGemToNewValueNodeMap.put(valueGemToSwitch, newValueNode); // necessary..?
return valueGemToNewValueNodeMap;
}
/**
* Update all the ValueGems with new types.
* Note: definition changes will be posted for the value gems that change type.
* @param valueGemTypeMap map from value gem to its updated type.
*/
private void updateValueGemsWithNewTypes(Map<ValueGem, TypeExpr> valueGemTypeMap) {
for (final Map.Entry<ValueGem, TypeExpr> mapEntry : valueGemTypeMap.entrySet()) {
// we get the type of the valueGem
ValueGem valueGem = mapEntry.getKey();
TypeExpr originalSourceExpr = valueGem.getValueNode().getTypeExpr();
// and we get the newType that we want
TypeExpr newSourceType = mapEntry.getValue();
// So if the type was changed, we want to swap the nodes
if (!newSourceType.sameType(originalSourceExpr)) {
// we transmute the node to come up with a valueNode of our desired type
ValueNode valueNode = valueGem.getValueNode().transmuteValueNode(valueNodeBuilderHelper, valueNodeTransformer, newSourceType);
// post definition changes if desired when swapping the nodes
valueGem.changeValue(valueNode);
}
}
}
/**
* Copy a set of gems and their associated forest.
* The gems will be copied, plus any associated gem trees from the gems' forest.
* Notes:
* - copied arguments will be not be assigned to any targets if their corresponding targets are not copied.
* - metadata are not copied.
* - if a collector gem to copy has no target, it will not be copied; instead, its copy will be set to the gemGraph target.
* @param gemsInForest the gems to copy.
* @param gemGraph the gem graph into which the copied gems will be placed
* @param includeBroken whether to include broken roots.
* @return Pair of map from roots in the original to the roots in the copy and map from gems in the original to the gems in the copy.
*
*/
public static Pair<Map<Gem, Gem>, Map<Gem, Gem>> copyGemForest(Set<Gem> gemsInForest, GemGraph gemGraph, boolean includeBroken) {
// Disable argument updating while copying is going on.
boolean argUpdatingWasDisabled = gemGraph.setArgumentUpdatingDisabled(true);
// The procedure is:
// Copy all the value gems, functional agents, code gems, collectors (without setting targets).
// This should leave only emitters and reflectors uncopied.
// Assign copied collector targets.
// Create all emitters.
// Create all reflectors (but uninitialized).
// Update input state, including connections.
// Set all argument targets.
Set<Gem> forestRootSet = obtainForestRoots(gemsInForest, includeBroken);
CollectorGem copiedTargetCollector = null;
// Get all the gems in the relevant forest.
Set<Gem> allGemSet = new HashSet<Gem>();
for (final Gem nextRoot : forestRootSet) {
allGemSet.addAll(obtainSubTree(nextRoot));
}
// map from gem in original to gem in the gem tree copy being created.
Map<Gem, Gem> oldToNewGemMap = new HashMap<Gem, Gem>();
// Create sets to keep track of gems we need later.
Set<CollectorGem> oldCollectorSet = new HashSet<CollectorGem>();
Set<ReflectorGem> oldReflectorSet = new HashSet<ReflectorGem>();
// Do a first pass over all the gems.
// Copy all functional agents, value gems, code gems, collectors. Track collectors, emitters, reflectors.
for (final Gem nextGem : allGemSet) {
if (nextGem instanceof FunctionalAgentGem) {
FunctionalAgentGem faGem = (FunctionalAgentGem)nextGem;
Gem gemCopy = new FunctionalAgentGem(faGem.getGemEntity());
gemGraph.addGem(gemCopy);
oldToNewGemMap.put(nextGem, gemCopy);
} else if (nextGem instanceof ValueGem) {
ValueGem valueGem = (ValueGem)nextGem;
Gem gemCopy = new ValueGem(valueGem.getValueNode());
gemGraph.addGem(gemCopy);
oldToNewGemMap.put(nextGem, gemCopy);
} else if (nextGem instanceof CodeGem) {
CodeGem codeGem = (CodeGem)nextGem;
Gem gemCopy = codeGem.makeCopy();
gemGraph.addGem(gemCopy);
oldToNewGemMap.put(nextGem, gemCopy);
} else if (nextGem instanceof CollectorGem) {
CollectorGem collectorGem = (CollectorGem)nextGem;
CollectorGem gemCopy;
// if a collector gem to copy has no target, set its copy to the gemGraph target instead of copying it.
boolean isTargetCollector = collectorGem.getTargetCollector() == null;
if (isTargetCollector) {
if (copiedTargetCollector != null) {
throw new IllegalStateException("Attempt to copy a gem forest with multiple outermost targets.");
}
copiedTargetCollector = collectorGem;
gemCopy = gemGraph.getTargetCollector();
} else {
gemCopy = new CollectorGem(null);
}
gemCopy.setName(collectorGem.getUnqualifiedName());
gemCopy.setDeclaredType(collectorGem.getDeclaredType());
// Don't add if it's the target, as it will already exist in the gem graph.
// Note: the collector's input will be added as an argument to the gemGraph's target collector.
if (!isTargetCollector) {
gemGraph.addGem(gemCopy);
}
oldToNewGemMap.put(nextGem, gemCopy);
oldCollectorSet.add((CollectorGem)nextGem);
} else if (nextGem instanceof ReflectorGem) {
oldReflectorSet.add((ReflectorGem)nextGem);
} else {
throw new IllegalArgumentException("Unexpected gem type: " + nextGem);
}
}
// Set targets for new collectors.
for (final CollectorGem oldCollector : oldCollectorSet) {
CollectorGem newCollector = (CollectorGem)oldToNewGemMap.get(oldCollector);
// Create the gem, add the mapping.
CollectorGem newTargetCollector = (CollectorGem)oldToNewGemMap.get(oldCollector.getTargetCollector());
if (newTargetCollector != null) {
newCollector.setTargetCollector(newTargetCollector);
}
}
// Create all the reflectors.
for (final ReflectorGem oldReflector : oldReflectorSet) {
CollectorGem oldCollector = oldReflector.getCollector();
// Create the gem, add the mapping.
ReflectorGem newReflector = new ReflectorGem((CollectorGem)oldToNewGemMap.get(oldCollector), oldReflector.getNInputs());
gemGraph.addGem(newReflector);
oldToNewGemMap.put(oldReflector, newReflector);
}
// copy reflector argument state.
for (final ReflectorGem oldReflector : oldReflectorSet) {
ReflectorGem newReflector = (ReflectorGem)oldToNewGemMap.get(oldReflector);
newReflector.copyArgumentState(oldReflector, oldToNewGemMap);
}
// Now iterate over all old gem inputs.
// Burn, connect, or name the corresponding new gem inputs as necessary.
for (final Gem nextOldGem : allGemSet) {
Gem gemCopy = oldToNewGemMap.get(nextOldGem);
for (int i = 0, nInputs = nextOldGem.getNInputs(); i < nInputs; i++) {
Gem.PartInput oldInput = nextOldGem.getInputPart(i);
if (oldInput.isBurnt()) {
gemCopy.getInputPart(i).setBurnt(true);
} else if (oldInput.isConnected()) {
Gem connectedGem = oldToNewGemMap.get(oldInput.getConnectedGem());
gemGraph.connectGems(connectedGem.getOutputPart(), gemCopy.getInputPart(i));
} else {
Gem.PartInput newInput = gemCopy.getInputPart(i);
newInput.setArgumentName(oldInput.getArgumentName());
}
}
}
// Set all the collector arguments.
for (final CollectorGem oldCollector: oldCollectorSet) {
CollectorGem newCollector = (CollectorGem)oldToNewGemMap.get(oldCollector);
List<PartInput> newTargetingArguments = new ArrayList<PartInput>();
// Iterate over the old collector's target arguments.
for (final PartInput oldPartInput : oldCollector.getTargetArguments()) {
// Get the corresponding new argument.
Gem newGem = oldToNewGemMap.get(oldPartInput.getGem());
if (newGem != null) {
PartInput newArgument = newGem.getInputPart(oldPartInput.getInputNum());
newTargetingArguments.add(newArgument);
}
}
// Remove the arguments from their old targets.
for (final PartInput targetingArgument : newTargetingArguments) {
CollectorGem argumentTarget = getInputArgumentTarget(targetingArgument);
if (argumentTarget != null) {
argumentTarget.removeArgument(targetingArgument);
}
}
// Set the arguments on the collector.
newCollector.addArguments(0, newTargetingArguments);
}
// Now gather the new forest roots so we can return them.
Map<Gem, Gem> newForestRootMap = new HashMap<Gem, Gem>();
for (final Gem oldRoot : forestRootSet) {
newForestRootMap.put(oldRoot, oldToNewGemMap.get(oldRoot));
}
// Restore argument updating.
gemGraph.setArgumentUpdatingDisabled(argUpdatingWasDisabled);
return new Pair<Map<Gem, Gem>, Map<Gem, Gem>>(newForestRootMap, oldToNewGemMap);
}
/**
* Copy a gem and its associated forest.
* The gem will be copied, plus any associated gem trees from the gem's forest.
* Notes:
* - copied arguments will be not be assigned to any targets if their corresponding targets are not copied.
* - metadata are not copied.
* - if a collector gem to copy has no target, it will not be copied; instead, its copy will be set to the gemGraph target.
* @param gem the gem to copy.
* @param gemGraph the gem graph into which the copied gems will be placed
* @param includeBroken whether to include broken roots.
* @return Pair of map from roots in the original to the roots in the copy and map from gems in the original to the gems in the copy.
*/
public static Pair<Map<Gem, Gem>, Map<Gem, Gem>> copyGemForest(Gem gem, GemGraph gemGraph, boolean includeBroken) {
return copyGemForest(Collections.singleton(gem), gemGraph, includeBroken);
}
/**
* Determine whether the gem graph is valid as it is currently defined.
* @param info the type check info to use to validate the gem graph.
* @return whether the gem graph is valid as it is currently defined.
*/
public boolean checkGraphValid(TypeCheckInfo info) {
// value gem connections temporarily disconnected from their destination.
Set<Connection> disconnectedValueGemConnections = new HashSet<Connection>();
try {
// TODOEL: This checking only has to happen on connect / disconnect (/ burn?) (switching does its own value gem updating..).
// We must update/switch value gem values according to the types of the inputs to which they are connected (eg. update on connect).
// To do this, we infer their types, and check according to the newly-inferred types.
Map<ValueGem, TypeExpr> valueGemToInferredTypeMap = inferValueGemTypes(true, info);
if (valueGemToInferredTypeMap != null) {
// Get the updated value gem types.
Map<ValueGem, TypeExpr> valueGemTypeMap = getUnifiedValueGemTypes(valueGemToInferredTypeMap, info);
for (final Map.Entry<ValueGem, TypeExpr> mapEntry : valueGemTypeMap.entrySet()) {
ValueGem valueGem = mapEntry.getKey();
// We can skip the next bit if the value gem is not connected..
Connection outputConnection = valueGem.getOutputPart().getConnection();
if (outputConnection == null) {
continue;
}
// we get the type of the valueGem
TypeExpr originalSourceExpr = valueGem.getValueNode().getTypeExpr();
// and we get the newType that we want
TypeExpr newSourceType = mapEntry.getValue();
// If the type was changed, attach a temporary value gem of the correct type.
if (!newSourceType.sameType(originalSourceExpr)) {
// Transmute the node to come up with a valueNode of our desired type
ValueNode valueNode = valueGem.getValueNode().transmuteValueNode(valueNodeBuilderHelper, valueNodeTransformer, newSourceType);
// Create a temporary value gem and connect it.
ValueGem tempValueGem = new ValueGem(valueNode);
PartInput connectedInput = outputConnection.getDestination();
connectedInput.bindConnection(new Connection(tempValueGem.getOutputPart(), connectedInput));
// Add to the set of temporarily disconnected value gem connections.
disconnectedValueGemConnections.add(outputConnection);
}
}
}
// Test whether the unbound parts can be typed.
getUnboundPartTypes(info);
} catch (TypeException te) {
// If there's a type exception, the resulting gem graph can't be typed.
return false;
} finally {
// re-bind any connections which were temporarily disconnected.
for (final Connection connection : disconnectedValueGemConnections) {
connection.getDestination().bindConnection(connection);
}
}
return true;
}
/**
* Updates the input and output types for all gems in the GemGraph.
* Also updates the types of ValueGems.
* @param info the info to use for typing the tree.
* @throws TypeException if the gem graph is not in a valid state.
*/
public void typeGemGraph(TypeCheckInfo info) throws TypeException {
// refresh the polymorphicVarContext
polymorphicVarContext = PolymorphicVarContext.make();
// disambiguate arg names.
checkArgNamesDisambiguated();
// TODOEL: This updating only has to happen on connect / disconnect (/ burn?) (switching does its own value gem updating..).
// We must update/switch value gem values according to the types of the inputs to which they are connected (eg. update on connect).
// To do this, we infer their types, and update according to the newly-inferred types.
Map<ValueGem, TypeExpr> valueGemToInferredTypeMap = inferValueGemTypes(true, info);
if (valueGemToInferredTypeMap != null && !valueGemToInferredTypeMap.isEmpty()) {
Map<ValueGem, TypeExpr> valueGemToNewTypeMap = getUnifiedValueGemTypes(valueGemToInferredTypeMap, info);
updateValueGemsWithNewTypes(valueGemToNewTypeMap);
}
// Get the types of the parts to be typed.
Map<Object, TypeExpr> unboundPartToTypeMap = getUnboundPartTypes(info);
// Set the types on the updated graph.
for (final Map.Entry<Object, TypeExpr> mapEntry : unboundPartToTypeMap.entrySet()) {
Object part = mapEntry.getKey();
TypeExpr partType = mapEntry.getValue();
if (part instanceof Gem.PartConnectable) {
// Set the part type.
((Gem.PartConnectable)part).setType(partType);
} else {
// Set the root output type
((Gem)part).setRootOutputType(partType);
}
}
}
/**
* Get the types of all the unbound parts in the gem graph as it is currently defined, except for value gem output parts.
* @param info
* @return a Map which contains two types of mappings to associated type expr. Map from the associated input or map from the associated root.
* This corresponds to the output type. In the case of collectors, it will be the collector type.
* @throws TypeException
*/
private Map<Object, TypeExpr> getUnboundPartTypes(TypeCheckInfo info) throws TypeException {
Map<Object, TypeExpr> unboundPartToTypeMap = new HashMap<Object, TypeExpr>();
Set<Gem> rootSet = getRoots();
Set<Gem> valueRoots = getValueRoots();
// we only want to get new types for non-value roots
rootSet.removeAll(valueRoots);
// we also don't want to try to type anything that's broken,
// so remove all the broken roots and their ancestors from the sets we actually type
Set<Gem> brokenSet = obtainBrokenAncestorRoots(rootSet);
rootSet.removeAll(brokenSet);
// Broken trees' parts are type null. Set root output types null, and gather sinks
Set<PartInput> noTypeSinks = new HashSet<PartInput>();
for (final Gem brokenRoot : brokenSet) {
noTypeSinks.addAll(obtainUnboundDescendantInputs(brokenRoot, TraversalScope.TREE, InputCollectMode.ALL));
unboundPartToTypeMap.put(brokenRoot, null);
}
// type the sinks null
for (final Gem.PartConnectable part : noTypeSinks){
unboundPartToTypeMap.put(part, null);
}
// Grab all the good types. This may throw a TypeException.
final Pair<Map<CompositionNode.CompositionArgument, TypeExpr>, Map<CompositionNode, List<TypeExpr>>> resultPair = info.getTypeChecker().checkGraph(rootSet, info.getModuleName(), new MessageLogger());
Map<CompositionNode.CompositionArgument, TypeExpr> argMap = resultPair.fst();
Map<CompositionNode, List<TypeExpr>> rootNodeMap = resultPair.snd();
// TODO Display any error messages from logger?
// Populate the map.
for (final Entry<CompositionNode, List<TypeExpr>> mapEntry : rootNodeMap.entrySet()) {
CompositionNode key = mapEntry.getKey();
if (!brokenSet.contains(key)) {
Gem rootGem = (Gem)key;
List<TypeExpr> rootTypes = mapEntry.getValue();
TypeExpr rootType = rootTypes.get(rootTypes.size() - 1);
unboundPartToTypeMap.put(rootGem, rootType);
}
}
for (final Entry<CompositionNode.CompositionArgument, TypeExpr> mapEntry : argMap.entrySet()) {
CompositionNode.CompositionArgument key = mapEntry.getKey();
// must be an input argument.
PartInput inputArgument = (PartInput)key;
TypeExpr inputType = mapEntry.getValue();
unboundPartToTypeMap.put(inputArgument, inputType);
}
return unboundPartToTypeMap;
}
/**
* Given a map from value gem to its inferred type, calculate what the value gem types would be if all value gem types
* were unified with their inferred types.
* @param valueGemToInferredTypeMap map from value gem to its inferred type, with which it should be unified.
* @param typeCheckInfo the info to use to revalidate the graph.
* @return map from value gem to the type corresponding to the value gem's type subject to the constraint of
* unification with it inferred type.
* @throws TypeException
*/
private Map<ValueGem, TypeExpr> getUnifiedValueGemTypes(Map<ValueGem, TypeExpr> valueGemToInferredTypeMap, TypeCheckInfo typeCheckInfo) throws TypeException {
int nTypes = valueGemToInferredTypeMap.size();
TypeExpr[] sourceTypePieces = new TypeExpr[nTypes];
TypeExpr[] destTypePieces = new TypeExpr[nTypes];
int index = 0;
// Populate the arrays of type pieces..
for (final Map.Entry<ValueGem, TypeExpr> mapEntry : valueGemToInferredTypeMap.entrySet()) {
ValueGem valueGem = mapEntry.getKey();
TypeExpr inferredType = mapEntry.getValue();
TypeExpr sourceExpr = valueGem.getValueNode().getTypeExpr();
sourceTypePieces[index] = sourceExpr;
//note destType may be null in case the connected tree is broken.
destTypePieces[index] = inferredType;
++index;
}
TypeExpr[] unifiedTypes = TypeExpr.unifyTypePieces(destTypePieces, sourceTypePieces, typeCheckInfo.getModuleTypeInfo());
// The result map..
Map<ValueGem, TypeExpr> valueGemToUnifiedConnectionTypeMap = new HashMap<ValueGem, TypeExpr>();
index = 0;
for (final ValueGem valueGem : valueGemToInferredTypeMap.keySet()) {
valueGemToUnifiedConnectionTypeMap.put(valueGem, unifiedTypes[index]);
index++;
}
return valueGemToUnifiedConnectionTypeMap;
}
/**
* Get the String representation of a type.
* @param type TypeExpr the type
* @param namingPolicy rules to use in determining the type string
* @return the string representation for the type expression, or null if type undefined
*/
public final String getTypeString(TypeExpr type, ScopedEntityNamingPolicy namingPolicy) {
return type.toString(polymorphicVarContext, namingPolicy);
}
/**
* Determine if two types will unify.
* @param fromType TypeExpr the first type
* @param toType TypeExpr the second type
* @param typeCheckInfo the typeCheckInfo to use.
* @return boolean true if types are unifiable
*/
static boolean typesWillUnify(TypeExpr fromType, TypeExpr toType, TypeCheckInfo typeCheckInfo) {
if (fromType == null || toType == null) {
return false;
}
// Check for unification
return TypeExpr.canUnifyType(fromType, toType, typeCheckInfo.getModuleTypeInfo());
}
/**
* Check two parts for basic connectivity, ignoring their current types.
* This includes seeing if they are already connected, checking for brokenness, and seeing
* if a cyclic tree would result.
* @param from Gem.PartConnectable the part from which the connection is made
* @param to Gem.PartConnectable the part to which the connection is made
* @return boolean true if valid, false otherwise
*/
static boolean arePartsConnectable(Gem.PartConnectable from, Gem.PartConnectable to) {
// Not valid if the destination or source are already connected!
if (from.isConnected() || to.isConnected()) {
return false;
}
// Also not valid if either of them is burnt.
if (from instanceof Gem.PartInput && ((Gem.PartInput) from).isBurnt()) {
return false;
}
if (to instanceof Gem.PartInput && ((Gem.PartInput) to).isBurnt()) {
return false;
}
// Defer to the other method..
if (!arePartsConnectableIfDisconnected(from, to)) {
return false;
}
// Check types for null -- canUnifyType() can't handle nulls.
TypeExpr toGemInType = to.getType();
TypeExpr fromGemOutType = from.getType();
return toGemInType != null && fromGemOutType != null;
}
/**
* Check two parts for basic connectivity, ignoring their current types.
* This includes seeing if they checking for brokenness, and seeing if a cyclic tree would result.
* Does not check whether the parts are connected. So they may not be connectable now, but the result
* of this method may still return true in the future.
* @param from the part from which the connection is made
* @param to the part to which the connection is made
* @return boolean true if valid, false otherwise
*/
public static boolean arePartsConnectableIfDisconnected(Gem.PartConnectable from, Gem.PartConnectable to) {
// to must be a sink and from must be a source
if (!(to instanceof Gem.PartInput && from instanceof Gem.PartOutput)) {
return false;
}
// still invalid if the input is burnt
if (((Gem.PartInput)to).isBurnt()){
return false;
}
Gem fromGem = from.getGem();
Gem toGem = to.getGem();
// don't allow cyclic trees
if (obtainSubTree(fromGem).contains(toGem)) {
return false;
}
// can't connect anything that's broken
if (isAncestorOfBrokenGemForest(fromGem) || isAncestorOfBrokenGemForest(toGem.getRootGem())) {
return false;
}
// Check for visibility constraints due to reflectors connected to the gem subtrees.
Set<ReflectorGem> fromSubtreeReflectors = UnsafeCast.<Set<ReflectorGem>>unsafeCast(obtainSubTreeGems(fromGem, ReflectorGem.class));
if (!fromSubtreeReflectors.isEmpty()) {
CollectorGem toRootCollectorGem = toGem.getRootCollectorGem();
if (toRootCollectorGem != null) {
// There is a CollectorGem at the root of the gem tree.
// It must be able to see all collectors for reflectors in the tree.
// Iterate over the subtree reflectors.
for (final ReflectorGem subtreeReflector : fromSubtreeReflectors) {
CollectorGem reflectorCollector = subtreeReflector.getCollector();
// If the subtree reflector collector isn't visible to the root collector, can't connect.
if (!reflectorCollector.isVisibleTo(toRootCollectorGem)) {
return false;
}
}
} else {
// There isn't a CollectorGem at the root of the gem tree,.
// We must be able to attach a collector which can see all collectors for reflectors in the tree.
// 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.
// So, in order to be able to define a collector which can see all the reflector definitions in an unrooted subtree,
// the targets of the reflectors' collectors must form an ancestor (enclosement) chain.
Set<ReflectorGem> toSubtreeReflectors = UnsafeCast.<Set<ReflectorGem>>unsafeCast(obtainSubTreeGems(toGem, ReflectorGem.class));
// Don't have to check if target subtree doesn't have reflectors.
if (!toSubtreeReflectors.isEmpty()) {
Set<CollectorGem> collectorTargetsToCheck = new HashSet<CollectorGem>();
// Iterate over the reflectors in both subtrees.
for ( final ReflectorGem subtreeReflector : toSubtreeReflectors) {
CollectorGem subtreeReflectorCollector = subtreeReflector.getCollector();
// Add the reflector collector's target (if any) to the set of collector targets to check.
CollectorGem subtreeReflectorCollectorTarget = subtreeReflectorCollector.getTargetCollectorGem();
if (subtreeReflectorCollectorTarget != null) {
collectorTargetsToCheck.add(subtreeReflectorCollectorTarget);
}
}
// Check that they form an ancestor chain.
if (!GemGraph.formsAncestorChain(collectorTargetsToCheck)) {
return false;
}
}
}
}
return true;
}
/**
* Check if the connection binding the output of a Gem (from) to an input of a Gem (to) is valid.
* This is known as a function composition. This includes checking if 'to' in currently unbound (which it must be).
* @param from the part from which the connection is made
* @param to the part to which the connection is made
* @param currentModuleTypeInfo the ModuleTypeInfo for the current module.
* @return boolean true if valid, false otherwise
*/
public static boolean isCompositionConnectionValid(Gem.PartConnectable from, Gem.PartConnectable to, ModuleTypeInfo currentModuleTypeInfo) {
// check for connectivity constraints
if (!arePartsConnectable(from, to) || !isConnectionValid(from,to)) {
return false;
}
// see if types will unify.
try {
// Get types for canUnifyType. Shouldn't be null since we already checked if they're connectable!
TypeExpr toInputType = to.getType();
TypeExpr fromOutputType = from.getType();
return TypeExpr.canUnifyType (toInputType, fromOutputType, currentModuleTypeInfo);
} catch (ClassCastException cce) {
} catch (NullPointerException npe) {
}
return false;
}
/**
* Return true if the connection is possible between a record and record field selection gem
* with a free field name - if it is one of the possible filed names are returned, otherwise false
* @param from the source part of the connection - expected to be a record, otherwise false will be returned
* @param to the to destination of the connection - expected to be a record field selection gem, otherwise false will be returned
* @param currentModuleTypeInfo
* @return fieldName if connection can be made, otherwise null
*/
public static FieldName isValidConnectionToRecordFieldSelection(Gem.PartConnectable from, Gem.PartConnectable to, ModuleTypeInfo currentModuleTypeInfo) {
// check for connectivity constraints
if (!arePartsConnectable(from, to) || !isConnectionValid(from,to)) {
return null;
}
if (! (from.getType() instanceof RecordType)) {
return null;
}
if (!(to.getGem() instanceof RecordFieldSelectionGem)) {
return null;
}
RecordFieldSelectionGem toGem = (RecordFieldSelectionGem) to.getGem();
if (toGem.isFieldFixed()) {
return null;
}
RecordType toType = (RecordType) to.getType();
FieldName fieldName = toGem.getFieldName();
TypeExpr requiredFieldType= toType.getHasFieldType(fieldName);
return RecordFieldSelectionGem.pickFieldName(from.getType(), requiredFieldType, currentModuleTypeInfo);
}
/**
* This checks to see if a connection from record field selection gem is possible if the input is burnt
* if the connection is possible, the first viable field name is returned, otherwise null
* @param from the source of the connection, expected the output part of a record field selection gem, otherwise false will be returned
* @param to the destination of the connection, expected to be something that requires an arity 1 function with a record as the input, otherwise false will be returned
* @param currentModuleTypeInfo
* @return FieldName returns a field name or null iff the connection is not possible
*/
public static FieldName isValidConnectionFromBurntRecordFieldSelection(Gem.PartConnectable from, Gem.PartConnectable to, ModuleTypeInfo currentModuleTypeInfo) {
// check for connectivity constraints
if (!arePartsConnectable(from, to) || !isConnectionValid(from,to)) {
return null;
}
if (!(from.getGem() instanceof RecordFieldSelectionGem)) {
return null;
}
if (!from.getGem().getNodeArgument(0).isBurnt()) {
return null;
}
if (to.getType().getArity() < 1) {
return null;
}
if (((RecordFieldSelectionGem)from.getGem()).isFieldFixed()) {
return null;
}
TypeExpr[] typePieces = to.getType().getTypePieces(1);
FieldName fieldName = RecordFieldSelectionGem.pickFieldName(typePieces[0], typePieces[1], currentModuleTypeInfo);
return fieldName;
}
/**
* This method checks whether the connection between the specified inputs and outputs are valid
* if the inputs and outputs were disconnected from their current connections.
*
* @param from Gem.PartConnectable the part from which the connection is made
* @param to Gem.PartConnectable the part to which the connection is made
* @return boolean true if valid, false otherwise
*/
public static boolean isCompositionConnectionValidIfDisconnected(Gem.PartConnectable from, Gem.PartConnectable to, TypeCheckInfo info) {
// check for connectivity constraints
if (!arePartsConnectableIfDisconnected(from, to) || !isConnectionValid(from,to)) {
return false;
}
// see if types will unify.
try {
// Get types for canUnifyType. Shouldn't be null since we already checked if they're connectable!
TypeExpr toInputType = to.getType();
TypeExpr fromOutputType;
if (from.isConnected()) {
// If the output was connected, we want to infer the type of this output by inferring it from its connected input.
fromOutputType = from.getConnection().getDestination().inferType(info);
} else {
fromOutputType = ((Gem.PartOutput) from).getType();
}
return TypeExpr.canUnifyType (toInputType, fromOutputType, info.getModuleTypeInfo());
} catch (ClassCastException cce) {
} catch (NullPointerException npe) {
}
return false;
}
/**
* Check for any additional constraints on connections, aside from basic connectivity.
* For now, this just checks that value gems with parametric values can't connect.
*
* @param from Gem.PartConnectable the part from which the connection is made
* @param to Gem.PartConnectable the part to which the connection is made
* @return boolean true if valid, false otherwise
*/
public static boolean isConnectionValid(Gem.PartConnectable from, Gem.PartConnectable to) {
// Can't connect ValueGems that contain parametric values.
if (from.getGem() instanceof ValueGem) {
ValueGem valueGem = ((ValueGem)from.getGem());
if (valueGem.containsParametricValues()) {
return false;
}
}
return true;
}
/**
* Returns all of the unqualified names of the codegems and collectors
* @return the unqualified names of codegems and collectors.
*/
Set<String> getCodeGemsCollectorsNames() {
Set<String> codeGemsCollectorsNames = getCodeGemNames();
codeGemsCollectorsNames.addAll(getCollectorNames());
return codeGemsCollectorsNames;
}
/**
* Sets the disableArgumentUpdating flag.
* This causes automatic argument updating by the gem graph to be disabled.
* @param newValue The new value. If false, argument updating is enabled. However, affected reflectors will not have been updated.
* @return the old value.
*/
boolean setArgumentUpdatingDisabled(boolean newValue) {
return argumentManager.setArgumentUpdatingDisabled(newValue);
}
/**
* Sets the valueNodeBuilderHelper used for validating the ValueGems
* @param valueNodeBuilderHelper
*/
public void setValueNodeBuilderHelper(ValueNodeBuilderHelper valueNodeBuilderHelper) {
this.valueNodeBuilderHelper = valueNodeBuilderHelper;
}
/**
* Adds the contents of the specified gem graph into the current one.
* Collectors and code gems will be renamed so that they don't confict with existing names.
* If the specified gem graph's target is unconnected, it will be assumed that the gem graph is merely a container for the
* gems contained therein. The target will not be copied, and collectors targeting the target will be retargeted to this
* gem graph's target.
* @param otherGemGraph the gem graph which will be merged into this one. Its target will not be copied if it is unconnected.
*/
public void addGemGraph(GemGraph otherGemGraph) {
Set<Gem> rootsToAdd = otherGemGraph.getRoots();
CollectorGem thisTargetCollector = getTargetCollector();
CollectorGem otherTargetCollector = otherGemGraph.getTargetCollector();
// Don't add the target if it's not connected.
if (!otherTargetCollector.isConnected()) {
rootsToAdd.remove(otherTargetCollector);
// Set any collectors targeting the old target to the new target gem.
for (final Gem root : rootsToAdd) {
if (root instanceof CollectorGem && ((CollectorGem)root).getTargetCollector() == otherTargetCollector) {
((CollectorGem)root).setTargetCollector(thisTargetCollector);
}
}
}
// Defer to the other method.
addSubtrees(rootsToAdd);
}
/**
* Merges the contents of the specified gem trees into the current one.
* Collectors and code gems will be renamed so that they don't conflict with existing names.
* Inputs targeted at unadded collectors will be retargeted to the target gem.
* Note: unsafe.
* @param newRoots the roots of the gem trees which will be added to this gem graph.
*/
public void addSubtrees(Collection<Gem> newRoots) {
Set<Gem> gemsToAdd = new HashSet<Gem>();
Set<CollectorGem> collectorsToAdd = new HashSet<CollectorGem>();
Set<Connection> connectionsToAdd = new HashSet<Connection>();
// Get the items that need to be added.
for (final Gem nextRoot : newRoots) {
gemsToAdd.addAll(obtainSubTree(nextRoot));
if (nextRoot instanceof CollectorGem) {
collectorsToAdd.add((CollectorGem)nextRoot);
}
connectionsToAdd.addAll(getConnectionsInSubtree(nextRoot));
}
Set<String> existingCollectorNames = getCollectorNames();
Set<String> existingCodeGemNames = getCodeGemNames();
// Make a pass through the other gem graph and rename any collectors and code gems that would
// conflict with the naming in the existing gem graph
for (final Gem gem : gemsToAdd) {
if (gem instanceof CollectorGem) {
CollectorGem collector = (CollectorGem)gem;
setUniqueCollectorName(collector, existingCollectorNames);
existingCollectorNames.add(collector.getUnqualifiedName());
} else if (gem instanceof CodeGem) {
CodeGem codeGem = (CodeGem)gem;
setUniqueCodeGemName(codeGem, existingCodeGemNames);
existingCodeGemNames.add(codeGem.getUnqualifiedName());
// TODOEL: remove other code gem update listener, replace with this one.
// Maybe make the listener add and remove itself??
} else if (gem instanceof ReflectorGem) {
ReflectorGem reflectorGem = (ReflectorGem)gem;
reflectorGem.getCollector().addReflector(reflectorGem);
}
}
// Bulk copy all of the gems and connections into the current gem graph
gemSet.addAll(gemsToAdd);
connectionSet.addAll(connectionsToAdd);
collectorSet.addAll(collectorsToAdd);
// Ensure argument and collector targets are valid.
validateInputTargets();
// Ensure that reflected inputs are updated.
if (!collectorsToAdd.isEmpty()) {
for (final Gem gem : getCollectors()) {
CollectorGem collectorGem = (CollectorGem)gem;
collectorGem.updateReflectedInputs();
}
}
// Notify gem add listeners
for (final Gem addedGem : gemsToAdd) {
for (final GemGraphChangeListener listener : gemGraphChangeListeners) {
listener.gemAdded(new GemGraphAdditionEvent(addedGem));
}
}
// Notify connection listeners
for (final Gem addedRoot : newRoots) {
for (final Connection addedConnection : getConnectionsInSubtree(addedRoot)) {
for (final GemGraphChangeListener listener : gemGraphChangeListeners) {
listener.gemConnected(new GemGraphConnectionEvent(addedConnection));
}
}
}
}
/**
* Returns the set of connections referred to by the subtree starting at the specified gem.
* @param gem the gem at the head of the subtree.
* @return the connections in the subtree headed by the specified gem.
*/
static Set<Connection> getConnectionsInSubtree(Gem gem) {
Set<Connection> connections = new HashSet<Connection>();
getConnectionsInSubtreeHelper(gem, connections);
return connections;
}
/**
* Helper method for getConnectionsInSubtree().
* @param gem
* @param connections
*/
private static void getConnectionsInSubtreeHelper(Gem gem, Set<Connection> connections) {
// Add the connection for the gem's output, if any.
if (gem.getOutputPart() != null && gem.getOutputPart().isConnected()) {
connections.add(gem.getOutputPart().getConnection());
}
// Call this recursively for any connected inputs.
Gem.PartInput[] inputs = gem.getInputParts();
for (final PartInput input : inputs) {
if (input.isConnected()) {
getConnectionsInSubtreeHelper(input.getConnectedGem(), connections);
}
}
}
/**
* Sets a unique name for the collector.
* A suffix "_i" will be added to the current collector name, if necessary, to make
* the name unique from any collectors in the specified set.
* @param collector
* @param existingNames
*/
private static void setUniqueCollectorName(CollectorGem collector, Set<String> existingNames) {
// Find a unique name for the collector.
String collectorName = collector.getUnqualifiedName();
for (int i = 1; existingNames.contains(collectorName); ++i) {
collectorName = collector.getUnqualifiedName() + '_' + i;
}
collector.setName(collectorName);
}
/**
* Sets a unique name for the code gem.
* A suffix "_i" will be added to the current code gem name, if necessary, to make
* the name unique from any code gems in the specified set.
* @param codeGem
* @param existingNames
*/
private static void setUniqueCodeGemName(CodeGem codeGem, Set<String> existingNames) {
// Find a unique name for the code gem.
String codeGemName = codeGem.getUnqualifiedName();
for (int i = 1; existingNames.contains (codeGemName); ++i) {
codeGemName = codeGem.getUnqualifiedName() + '_' + i;
}
codeGem.setName(codeGemName);
}
/**
* Removes the specified gems and connections from the current gem graph.
* This assumes that nothing remaining in the gem graph will refer to these objects.
* @param gemsToRemove
* @param connectionsToRemove
*/
public void removeFromGemGraph(Collection<Gem> gemsToRemove, Collection<Connection> connectionsToRemove) {
// Bulk remove all of the gems and connections from the current gem graph
gemSet.removeAll(gemsToRemove);
connectionSet.removeAll(connectionsToRemove);
this.collectorSet.removeAll(gemsToRemove);
// Disallow deletion of the target gem.
if (!gemSet.contains(targetCollector)) {
// Warn and add the gem back.
GemCutter.CLIENT_LOGGER.log(Level.WARNING, "Attempt to delete the target gem from the gem graph.");
gemSet.add(targetCollector);
this.collectorSet.add(targetCollector);
}
// For every removed gem, notify the listeners
for (final Gem gem : gemsToRemove) {
for (final GemGraphChangeListener listener : gemGraphChangeListeners) {
listener.gemRemoved(new GemGraphRemovalEvent(gem));
}
}
// For every removed connection, notify the listeners
for (final Connection conn : connectionsToRemove) {
if (gemConnectionListener != null) {
gemConnectionListener.disconnectionOccurred(new GemConnectionEvent(conn, GemConnectionEvent.EventType.DISCONNECTION));
}
for (final GemGraphChangeListener listener : gemGraphChangeListeners) {
listener.gemDisconnected(new GemGraphDisconnectionEvent(conn));
}
}
}
/**
* Check whether an output part can be autoconnected to another gem.
* @param toGem the gem to which to connect
* @param fromPart the part from which to connect
* @param context the context in which the connection should be made
* @return PartInput the input part to which the toGem can connect unambiguously, if any.
*/
static public PartInput isAutoConnectable(Gem toGem, Gem.PartOutput fromPart, ConnectionContext context) {
// get the list of unbound, unburnt parts in toGem's tree
List<PartInput> inputList = obtainUnboundDescendantInputs(toGem, TraversalScope.TREE, InputCollectMode.UNBURNT_ONLY);
// iterate through the parts and look for a match
PartInput connectableInput = null;
for (int i = 0, numParts = inputList.size(); i < numParts; i++) {
PartInput input = inputList.get(i);
// check for parts which come from this gem, and can connect
if (input.getGem() == toGem &&
(isCompositionConnectionValid(fromPart, input, context.getContextModuleTypeInfo()) ||
isDefaultableValueGemSource(fromPart, input, context) ||
(isValidConnectionToRecordFieldSelection(fromPart, input, context.getContextModuleTypeInfo()) != null))) {
// Do we have more than one match?
if (connectableInput != null) {
return null;
}
connectableInput = input;
}
}
// unambiguous match
return connectableInput;
}
/**
* Return whether a source part belongs to a value gem that can be defaulted to a sink part's type.
* @param sourcePart the source part to check
* @param sinkPart the sink part against which to attempt defaulting
* @param context the context in which the connection should be made
* @return boolean true if the gem source is a value gem can be defaulted to the sink part's type
*/
public static boolean isDefaultableValueGemSource(Gem.PartConnectable sourcePart, Gem.PartInput sinkPart, ConnectionContext context) {
if (sinkPart.isBurnt() || sinkPart.isConnected()) {
return false;
}
TypeExpr sourceType = sourcePart.getType();
TypeExpr destType = sinkPart.getType();
if (destType == null || destType.isFunctionType()) {
return false;
}
Gem sourceGem = sourcePart.getGem();
// the source gem must be a value gem that contains parametric values.
if (sourceGem instanceof ValueGem && ((ValueGem)sourceGem).containsParametricValues()) {
// see if we can automatically specialize the source type to the destination type,
// and if value editors can handle the destination type
try {
if (TypeExpr.canUnifyType(destType, sourceType, context.getContextModuleTypeInfo()) &&
context.getValueEditorManager().canInputDefaultValue(TypeExpr.unify(destType, sourceType, context.getContextModuleTypeInfo()))) {
return true;
}
} catch (TypeException e) {
throw new IllegalStateException(e.getMessage());
}
}
return false;
}
/**
* Adds the specified gem connection listener to receive gem connection events from this gem .
* If l is null, no exception is thrown and no action is performed.
*
* @param l the gem connection listener.
*/
public synchronized void addGemConnectionListener(GemConnectionListener l) {
if (l == null) {
return;
}
gemConnectionListener = GemEventMulticaster.add(gemConnectionListener, l);
}
/**
* Removes the specified gem connection listener so that it no longer receives gem connection events from this gem.
* This method performs no function, nor does it throw an exception, if the listener specified by
* the argument was not previously added to this component.
* If l is null, no exception is thrown and no action is performed.
*
* @param l the gem connection listener.
*/
public synchronized void removeGemConnectionListener(GemConnectionListener l) {
if (l == null) {
return;
}
gemConnectionListener = GemEventMulticaster.remove(gemConnectionListener, l);
}
/**
* Adds a new gem GraphChangeListener to the list
* @param gemGraphChangeListener
*/
public void addGraphChangeListener(GemGraphChangeListener gemGraphChangeListener) {
if (!gemGraphChangeListeners.contains(gemGraphChangeListener)) {
gemGraphChangeListeners.add(gemGraphChangeListener);
}
}
}