/*
* 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.
*/
/*
* ReflectorGem.java
* Creation date: Mar 21, 2003.
* By: Edward Lam
*/
package org.openquark.gems.client;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import javax.swing.undo.StateEditable;
import org.openquark.cal.compiler.CompositionNode;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.metadata.ArgumentMetadata;
import org.openquark.gems.client.GemGraph.GemVisitor;
import org.openquark.util.Pair;
import org.openquark.util.UnsafeCast;
import org.openquark.util.xml.BadXMLDocumentException;
import org.openquark.util.xml.XMLPersistenceHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* A ReflectorGem represents a local function definition -
* the part of the expression where the CAL definition of the let is actually used (the let function in the "in" part of the let definition).
* Because Reflectors are defined by the gem tree connected to the associated collector,
* we maintain the invariant that a reflector cannot exist without an associated collector.
* However, multiple reflectors may exist to use the definition of the let in their associated collector.
*
* @author Edward Lam
*/
public final class ReflectorGem extends Gem implements CompositionNode.Emitter, NamedGem, StateEditable {
/** The CollectorGem to which this reflector refers. */
private CollectorGem collectorGem; // should be final once assigned
/** Gem broken state. */
private boolean broken = false;
/** Map from input in this gem to the corresponding input within the associated collector's definition. */
private final transient Map<PartInput, PartInput> inputToReflectedInputMap = new WeakHashMap<PartInput, PartInput>();
/*
* Keys for fields in map used with state editable interface
*/
private static final String BROKEN_KEY = "BrokenStateKey";
private static final String INPUT_PARTS_KEY = "InputPartsStateKey";
private static final String REFLECTED_INPUT_MAP_KEY = "InputToReflectedInputMapStateKey";
/**
* Construct an empty reflector gem.
* A gem created using this constructor is appropriate for use in deserialization.
*/
private ReflectorGem() {
// Initialize with no input parts.
super(0);
}
/**
* Construct a ReflectorGem from a collector.
* @param collectorGem the Collector with which this reflector is associated.
*/
public ReflectorGem(CollectorGem collectorGem) {
// Initialize with no input parts.
super(0);
// Set the collector associated with this reflector.
this.collectorGem = collectorGem;
// Add a listener to reflect collector name changes.
addCollectorNameChangeListener();
// Set the output type.
getOutputPart().setType(collectorGem.getResultType());
// Update for collector definition. This creates the inputParts
updateForCollector(getCollector().getReflectedInputs());
}
/**
* Construct a ReflectorGem from a collector, having a certain number of inputs.
* The resulting gem will be in an incomplete/inconsistent state --
* a gem created using this constructor is appropriate for use in deserialization.
* @param collectorGem the Collector with which this reflector is associated.
* @param nInputs the number of inputs on this reflector.
*/
ReflectorGem(CollectorGem collectorGem, int nInputs) {
// Initialize with the appropriate number of input parts.
super(nInputs);
// Set the collector associated with this reflector.
this.collectorGem = collectorGem;
// Add a listener to reflect collector name changes.
addCollectorNameChangeListener();
// Missing: metadata, part types, reflected input correspondence map.
}
/**
* Add a listener to reflect collector name changes.
* This should be called on instantiation, after the collector has been set.
*/
private void addCollectorNameChangeListener() {
collectorGem.addNameChangeListener(new NameChangeListener() {
public void nameChanged(NameChangeEvent e) {
if (nameChangeListener != null) {
nameChangeListener.nameChanged(new NameChangeEvent(ReflectorGem.this, e.getOldName()));
}
}
});
}
/**
* Update the types and number of inputs on this reflector to reflect the state of the associated collector gem's subtree.
* If deserializing, this should be called only when the entire reflector definition subtree is created.
* Internal bookkeeping structures will be brought up to date.
* @param reflectedInputList the inputs for which arguments appear on the corresponding collector.
* @return whether the inputs changed. ie. whether the new inputs are different from the old ones.
*/
boolean updateForCollector(List<PartInput> reflectedInputList) {
// TODO: figure out what kind of argument metadata to use.
// Create a set to keep track of which inputs are orphaned - ie. connected, but whose inputs should no longer
// appear on this reflector. First get all of the connected reflector inputs.
Set<PartInput> orphanedInputs = new LinkedHashSet<PartInput>();
for (int i = 0, nArgs = getNInputs(); i < nArgs; i++) {
PartInput input = getInputPart(i);
if (input.isConnected()) {
orphanedInputs.add(input);
}
}
List<PartInput> newInputParts = new ArrayList<PartInput>(reflectedInputList.size());
int index = 0;
for (final PartInput partInput : reflectedInputList) {
PartInput reflectedInput = partInput;
// reuse any existing inputs if possible, otherwise create a new one.
PartInput input = getReflectingInput(reflectedInput);
if (input == null) {
input = createInputPart(index);
ArgumentMetadata inputMetadata = reflectedInput.getDesignMetadata();
inputMetadata.setDisplayName(reflectedInput.getArgumentName().getCompositeName());
input.setDesignMetadata(inputMetadata);
input.setArgumentName(new ArgumentName(reflectedInput.getArgumentName()));
} else {
// update the input num.
input.setInputNum(index);
// Remove this input from the set of orphaned inputs.
orphanedInputs.remove(input);
}
newInputParts.add(input);
// update the input type.
TypeExpr inputType = reflectedInput.getType();
input.setType(inputType);
// update the reflection map
inputToReflectedInputMap.put(input, reflectedInput);
index++;
}
// Now add any orphaned inputs.
broken = !orphanedInputs.isEmpty();
if (broken) {
for (final PartInput orphanedInput : orphanedInputs) {
orphanedInput.setInputNum(index);
newInputParts.add(orphanedInput);
index++;
}
}
// update the input parts
boolean inputsChanged = setInputParts(newInputParts.toArray(new PartInput[newInputParts.size()]));
return inputsChanged;
}
/**
* Get the input on the collector subtree reflected by an input on this reflector.
* @param reflectingInput the input on this reflector
* @return the reflected input corresponding to reflectingInput
*/
public PartInput getReflectedInput(PartInput reflectingInput) {
return inputToReflectedInputMap.get(reflectingInput);
}
/**
* Get the input on this reflector which reflects an input on the reflected collector's subtree.
* @param reflectedInput the input on the reflected collector's subtree.
* @return the reflecting input corresponding to reflectedInput. Null if reflectedInput is not actually a reflected input.
*/
public PartInput getReflectingInput(PartInput reflectedInput) {
for (int partN = 0, nParts = getNInputs(); partN < nParts; ++partN) {
PartInput inputPart = getInputPart(partN);
if (reflectedInput.equals(getReflectedInput(inputPart))) {
return inputPart;
}
}
return null;
}
/**
* Get the name of the Gem (the name of the underlying let)
* @return QualifiedName the name of the gem
*/
public String getUnqualifiedName() {
return getCollector().getUnqualifiedName();
}
/**
* Get the collector associated with this reflector.
* @return CollectorGem the collector associated with this reflector.
*/
public final CollectorGem getCollector() {
return collectorGem;
}
/**
* {@inheritDoc}
*/
public CompositionNode.Collector getCollectorNode(){
// Return the collector.
return getCollector();
}
/**
* Returns whether this gem is broken
* @return boolean true if this gem is broken
*/
@Override
public boolean isBroken() {
return broken;
}
/**
* Set the broken state of this Gem
* @param newBroken boolean the new broken state of the Gem
*/
void setBroken(boolean newBroken) {
if (broken != newBroken) {
broken = newBroken;
if (gemStateListener != null) {
gemStateListener.brokenStateChanged(new GemStateEvent(this, GemStateEvent.EventType.BROKEN));
}
}
}
/**
* Describe this Gem
* @return the description
*/
@Override
public String toString() {
return "Emitter " + getUnqualifiedName();
}
/**
* {@inheritDoc}
*/
@Override
void visitDescendants(GemVisitor gemVisitor, Set<Gem> gemsVisited){
if (gemsVisited.contains(this)) {
return;
}
super.visitDescendants(gemVisitor, gemsVisited);
if (gemVisitor.getTraversalScope() == GemGraph.TraversalScope.FOREST) {
// also check whether we would escape the target scope of the visitor
CollectorGem visitorTargetCollector = gemVisitor.getTargetCollector();
CollectorGem collectorGem = getCollector();
if (visitorTargetCollector != null && !visitorTargetCollector.enclosesCollector(collectorGem)) {
return;
}
collectorGem.visitDescendants(gemVisitor, gemsVisited);
}
}
/**
* {@inheritDoc}
*/
@Override
void visitConnectedTrees(GemVisitor gemVisitor, Set<Gem> gemsVisited){
if (gemsVisited.contains(this)) {
return;
}
super.visitConnectedTrees(gemVisitor, gemsVisited);
if (gemVisitor.getTraversalScope() == GemGraph.TraversalScope.FOREST) {
// also check whether we would escape the target scope level of the visitor
CollectorGem visitorTargetCollector = gemVisitor.getTargetCollector();
CollectorGem collectorGem = getCollector();
if (visitorTargetCollector != null && !visitorTargetCollector.enclosesCollector(collectorGem)) {
return;
}
collectorGem.visitConnectedTrees(gemVisitor, gemsVisited);
}
}
/*
* Methods supporting javax.swing.undo.StateEditable ********************************************
*/
/**
* Restore the stored gem state.
* @param state the stored state
*/
public void restoreState(Hashtable<?, ?> state) {
Object stateValue;
stateValue = state.get(new Pair<ReflectorGem, String>(ReflectorGem.this, INPUT_PARTS_KEY));
if (stateValue != null) {
PartInput[] inputParts = (PartInput[])stateValue;
for (int i = 0; i < inputParts.length; i++) {
inputParts[i].setInputNum(i);
}
setInputParts((PartInput[])stateValue);
}
stateValue = state.get(new Pair<ReflectorGem, String>(ReflectorGem.this, REFLECTED_INPUT_MAP_KEY));
if (stateValue != null) {
inputToReflectedInputMap.clear();
inputToReflectedInputMap.putAll(UnsafeCast.<Map<PartInput, PartInput>>unsafeCast(stateValue));
}
stateValue = state.get(new Pair<ReflectorGem, String>(ReflectorGem.this, BROKEN_KEY));
if (stateValue != null) {
broken = ((Boolean)stateValue).booleanValue();
}
}
/**
* Save the current gem state.
* @param state the table in which to store the current gem state
*/
public void storeState(Hashtable<Object, Object> state) {
// Now store all the states. The state key we use is a pair: this code gem and the field key.
state.put(new Pair<ReflectorGem, String>(ReflectorGem.this, INPUT_PARTS_KEY), getInputParts());
state.put(new Pair<ReflectorGem, String>(ReflectorGem.this, BROKEN_KEY), Boolean.valueOf(isBroken()));
state.put(new Pair<ReflectorGem, String>(ReflectorGem.this, REFLECTED_INPUT_MAP_KEY), new HashMap<PartInput, PartInput>(inputToReflectedInputMap));
}
/*
* Methods supporting XMLPersistable ********************************************
*/
/**
* {@inheritDoc}
*/
@Override
public void saveXML(Node parentNode, GemContext gemContext) {
Document document = (parentNode instanceof Document) ? (Document)parentNode : parentNode.getOwnerDocument();
// Create the reflector gem element
Element resultElement = document.createElementNS(GemPersistenceConstants.GEM_NS, GemPersistenceConstants.EMITTER_GEM_TAG);
resultElement.setPrefix(GemPersistenceConstants.GEM_NS_PREFIX);
parentNode.appendChild(resultElement);
// Add info for the superclass gem.
super.saveXML(resultElement, gemContext);
// Now add ReflectorGem-specific info
// Add an attribute for the name.
resultElement.setAttribute(GemPersistenceConstants.REFLECTOR_GEM_COLLECTOR_ID_ATTR, gemContext.getIdentifier(collectorGem, true));
// Add an element for the orphaned inputs.
Element orphanedInputsElement = document.createElement(GemPersistenceConstants.REFLECTOR_GEM_ORPHANED_INPUTS_TAG);
resultElement.appendChild(orphanedInputsElement);
for (int i = 0, nInputs = getNInputs(); i < nInputs; i++) {
Gem.PartInput input = getInputPart(i);
Gem.PartInput reflectedInput = inputToReflectedInputMap.get(input);
// orphaned if connected.
if (reflectedInput.isConnected()) {
orphanedInputsElement.appendChild(GemCutterPersistenceHelper.inputToArgumentElement(reflectedInput, document, gemContext));
}
}
}
/**
* Create a new ReflectorGem and loads its state from the specified XML element.
* @param gemElement Element the element representing the structure to deserialize.
* @param gemContext the context in which the gem is being instantiated.
* @param loadInfo the argument info for this load session.
* @return ReflectorGem
* @throws BadXMLDocumentException
*/
public static ReflectorGem getFromXML(Element gemElement, GemContext gemContext, Argument.LoadInfo loadInfo) throws BadXMLDocumentException {
ReflectorGem gem = new ReflectorGem();
gem.loadXML(gemElement, gemContext, loadInfo);
return gem;
}
/**
* Load this object's state.
* Note: serialization will be incomplete until the first call to updateForCollector() is called.
* @param gemElement Element the element representing the structure to deserialize.
* @param gemContext the context in which the gem is being instantiated.
* @param loadInfo the argument info for this load session.
*/
void loadXML(Element gemElement, GemContext gemContext, Argument.LoadInfo loadInfo) throws BadXMLDocumentException {
XMLPersistenceHelper.checkTag(gemElement, GemPersistenceConstants.EMITTER_GEM_TAG);
XMLPersistenceHelper.checkPrefix(gemElement, GemPersistenceConstants.GEM_NS_PREFIX);
List<Element> childElems = XMLPersistenceHelper.getChildElements(gemElement);
int nChildElems = childElems.size();
// Get info for the underlying gem.
Element superGemElem = (nChildElems < 1) ? null : (Element) childElems.get(0);
XMLPersistenceHelper.checkIsElement(superGemElem);
super.loadXML(superGemElem, gemContext);
// Get the name
String collectorId = gemElement.getAttribute(GemPersistenceConstants.REFLECTOR_GEM_COLLECTOR_ID_ATTR);
// Get the collector associated with this reflector
Gem gemFromCollectorId = gemContext.getGem(collectorId);
if (gemFromCollectorId == null) {
XMLPersistenceHelper.handleBadDocument(gemElement, "No corresponding collector found for reflector.");
} else if (!(gemFromCollectorId instanceof CollectorGem)) {
XMLPersistenceHelper.handleBadDocument(gemElement, "Collector id for reflector does not correspond to a collector.");
}
this.collectorGem = (CollectorGem)gemFromCollectorId;
// Add a listener to reflect collector name changes.
addCollectorNameChangeListener();
// Now get the orphaned inputs.
Element orphanedInputsElement = (nChildElems < 2) ? null : (Element) childElems.get(1);
XMLPersistenceHelper.checkIsElement(orphanedInputsElement);
List<Element> orphanedInputChildElems = XMLPersistenceHelper.getChildElements(orphanedInputsElement);
int nOrphanedInputChildElems = orphanedInputChildElems.size();
for (int i = 0; i < nOrphanedInputChildElems; i++) {
Element orphanedInputChildElem = orphanedInputChildElems.get(i);
Pair<String, Integer> orphanedInputInfoPair = GemCutterPersistenceHelper.argumentElementToInputInfo(orphanedInputChildElem);
loadInfo.addArgument(this, orphanedInputInfoPair.fst(), orphanedInputInfoPair.snd());
}
// Take care of orphaned inputs.
}
/**
* Populate internal argument state during loading.
* @param loadInfo
* @param gemContext
* @throws BadXMLDocumentException
*/
public void loadArguments(Argument.LoadInfo loadInfo, GemContext gemContext) throws BadXMLDocumentException {
// Create a list to represent the inputs reflected by this reflector.
// Start off with the reflected inputs dictated by the target.
List<PartInput> reflectedInputs = new ArrayList<PartInput>(collectorGem.getReflectedInputs());
// Now add orphaned inputs.
int nOrphanedInputs = loadInfo.getNArguments(this);
for (int i = 0; i < nOrphanedInputs; i++) {
Pair<String, Integer> inputInfo = loadInfo.getInputInfo(this, i);
Gem inputGem = gemContext.getGem(inputInfo.fst());
int inputIndex = inputInfo.snd().intValue();
reflectedInputs.add(inputGem.getInputPart(inputIndex));
}
// Check that the number of reflected inputs is the same as the number of reflector inputs.
int nReflectedInputs = reflectedInputs.size();
if (nReflectedInputs != getNInputs()) {
// What to do?
throw new BadXMLDocumentException(null,
"Number of loaded reflector arguments is inconsistent: " + nReflectedInputs + " != " + getNInputs());
}
// Populate the inputToReflectedInput map
for (int i = 0; i < nReflectedInputs; i++) {
inputToReflectedInputMap.put(getInputPart(i), reflectedInputs.get(i));
}
}
/**
* Copy a reflector's argument mappings onto this one. Used during gem copying.
* This method should only be called when all gems referred to by the original gem have been copied,
* and therefore exist in the map that's passed in.
*
* @param originalReflector the original reflector, of which this is a copy.
* @param oldToNewGemMap map from original gem to gem copy.
*/
void copyArgumentState(ReflectorGem originalReflector, Map<Gem, Gem> oldToNewGemMap) {
inputToReflectedInputMap.clear();
Map<PartInput, PartInput> originalInputToReflectedInputMap = originalReflector.inputToReflectedInputMap;
for (final Map.Entry<PartInput, PartInput> mapEntry : originalInputToReflectedInputMap.entrySet()) {
Gem.PartInput originalReflectorInput = mapEntry.getKey();
int originalReflectorInputIndex = originalReflectorInput.getInputNum();
Gem.PartInput originalReflectedInput = mapEntry.getValue();
Gem originalReflectedInputGem = originalReflectedInput.getGem();
int originalReflectedInputIndex = originalReflectedInput.getInputNum();
Gem newReflectedInputGem = oldToNewGemMap.get(originalReflectedInputGem);
if (newReflectedInputGem == null) {
throw new IllegalArgumentException("Mapping not found for copied gem.");
}
Gem.PartInput newReflectedInput = newReflectedInputGem.getInputPart(originalReflectedInputIndex);
inputToReflectedInputMap.put(getInputPart(originalReflectorInputIndex), newReflectedInput);
}
}
}