/* * 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); } } }