/*
* 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.
*/
/*
* NavigatorAdapter.java
* Creation date: Jul 23, 2003
* By: Frank Worsley
*/
package org.openquark.gems.client;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.event.UndoableEditListener;
import javax.swing.tree.TreeNode;
import javax.swing.undo.StateEdit;
import javax.swing.undo.StateEditable;
import org.openquark.cal.compiler.ScopedEntity;
import org.openquark.cal.compiler.TypeChecker;
import org.openquark.cal.metadata.ArgumentMetadata;
import org.openquark.cal.metadata.CALFeatureMetadata;
import org.openquark.cal.metadata.FunctionalAgentMetadata;
import org.openquark.cal.services.GemEntity;
import org.openquark.cal.services.MetaModule;
import org.openquark.cal.services.Perspective;
import org.openquark.gems.client.Gem.PartInput;
import org.openquark.gems.client.explorer.TableTopExplorer;
import org.openquark.gems.client.navigator.NavAddress;
import org.openquark.gems.client.navigator.NavAddressHelper;
import org.openquark.gems.client.navigator.NavFrame;
import org.openquark.gems.client.navigator.NavFrameOwner;
import org.openquark.gems.client.navigator.NavAddress.NavAddressMethod;
import org.openquark.gems.client.utilities.ExtendedUndoableEditSupport;
import org.openquark.util.Pair;
/**
* The GemCutter's implementation of the NavFrameOwner interface. This class is basically
* a thin wrapper around the NavFrame class. It delegates to an instance of NavFrame for all
* functionality and simply adds undo support on top of NavFrame.
*
* @author Frank Worsley
*/
class NavigatorAdapter implements NavFrameOwner {
/**
* Implements a state editable for metadata objects.
* @author Frank Worsley
*/
private class MetadataStateEditable implements StateEditable {
/** The metadata key in the state maps. */
private static final String METADATA_STATE_KEY = "MetadataStateKey";
/** The address of the metadata object this editable is for. */
private final NavAddress address;
/**
* Constructs a new state editable for the given metadata object.
* @param address the address of the metadata object
*/
public MetadataStateEditable(NavAddress address) {
if (address == null) {
throw new NullPointerException();
}
this.address = address;
}
/**
* @see javax.swing.undo.StateEditable#storeState(java.util.Hashtable)
*/
public void storeState(Hashtable<Object, Object> state) {
CALFeatureMetadata metadata = NavAddressHelper.getMetadata(NavigatorAdapter.this, address);
state.put(getKey(), metadata);
}
/**
* @see javax.swing.undo.StateEditable#restoreState(java.util.Hashtable)
*/
public void restoreState(Hashtable<?, ?> state) {
CALFeatureMetadata savedState = (CALFeatureMetadata) state.get(getKey());
if (savedState != null) {
boolean success = NavAddressHelper.saveMetadata(NavigatorAdapter.this, address, savedState);
if (!success) {
throw new IllegalStateException("failed to restore saved metadata state");
}
// Update the state edit if the currently edited metadata was changed.
if (address.equals(currentEditAddress)) {
stateEdit = new StateEdit(new MetadataStateEditable(address));
}
// Check the table top names once the undo completes.
// We need to do it after the undo so GemGraph checking is enabled again.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updateInputNames(address);
nameCheckTableTop();
navFrame.refreshMetadata(address);
}
});
}
}
/**
* @return the key to use in the state maps for the metadata
*/
private Object getKey() {
return new Pair<NavAddress, String>(address, METADATA_STATE_KEY);
}
}
/** The undoable edit support for the navigator adapter. */
private final ExtendedUndoableEditSupport undoableEditSupport;
/** The GemCutter this adapter is for. */
private final GemCutter gemCutter;
/** The navigator frame this adapter is using. */
private NavFrame navFrame = null;
/** The state edit for the metadata object being edited. */
private StateEdit stateEdit = null;
/** The address of the metadata object currently being edited. */
private NavAddress currentEditAddress = null;
/**
* Constructor for a new navigator adapter for a GemCutter.
* @param gemCutter the GemCutter this adapter is for
*/
public NavigatorAdapter(GemCutter gemCutter) {
if (gemCutter == null) {
throw new NullPointerException();
}
this.gemCutter = gemCutter;
this.undoableEditSupport = new ExtendedUndoableEditSupport(this);
}
/**
* Add an undoable edit listener.
* @param listener the listener to add
*/
public void addUndoableEditListener(UndoableEditListener listener) {
undoableEditSupport.addUndoableEditListener(listener);
}
/**
* Remove an undoable edit listener.
* @param listener the listener to remove
*/
public void removeUndoableEditListener(UndoableEditListener listener) {
undoableEditSupport.removeUndoableEditListener(listener);
}
/**
* @return whether or not the navigator instance if visible on screen
*/
private boolean navigatorCanView() {
return navFrame.isVisible() && !navFrame.isEditing();
}
/**
* Refreshed the navigatior tree by reloading the CAL entities from the perspective.
* This has to be done after a gem is saved so that the new gem shows up in the tree.
*/
public void refresh() {
if (navFrame == null) {
navFrame = new NavFrame(this);
}
navFrame.refreshTree();
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#getParent()
*/
public JFrame getParent() {
return gemCutter;
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#getTypeChecker()
*/
public TypeChecker getTypeChecker() {
return gemCutter.getTypeChecker();
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#getValueRunner()
*/
public ValueRunner getValueRunner() {
return gemCutter.getValueRunner();
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#getPerspective()
*/
public Perspective getPerspective() {
return gemCutter.getPerspective();
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#getCollector(java.lang.String)
*/
public CollectorGem getCollector(String name) {
Set<CollectorGem> collectors = gemCutter.getTableTop().getGemGraph().getCollectorsForName(name);
return (collectors.size() == 0) ? null : collectors.iterator().next();
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#displayNavigator(boolean)
*/
public void displayNavigator(boolean newNavigator) {
navFrame.setVisible(true);
navFrame.refreshViewer();
navFrame.requestFocus();
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#displayMetadata(org.openquark.gems.client.navigator.NavAddress, boolean)
*/
public void displayMetadata(NavAddress address, boolean newViewer) {
if (navigatorCanView() || newViewer) {
navFrame.displayMetadata(address);
}
}
/**
* Display metadata for the given entity.
* @param entity the entity to display metadata for
* @param newViewer true if a new window should be opened
*/
public void displayMetadata(GemEntity entity, boolean newViewer) {
displayMetadata(NavAddress.getAddress(entity), newViewer);
}
/**
* Display metadata for the given entity.
* @param entity the entity to display metadata for
* @param newViewer true if a new window should be opened
*/
public void displayMetadata(ScopedEntity entity, boolean newViewer) {
displayMetadata(NavAddress.getAddress(entity), newViewer);
}
/**
* Display metadata for the given module.
* @param module the module to display metadata for
* @param newViewer true if a new window should be opened
*/
public void displayMetadata(MetaModule module, boolean newViewer) {
displayMetadata(NavAddress.getAddress(module), newViewer);
}
/**
* Display metadata for the given collector.
* @param collector the collector to display metadata for
* @param newViewer true if a new window should be opened
*/
public void displayMetadata(CollectorGem collector, boolean newViewer) {
displayMetadata(NavAddress.getAddress(collector), newViewer);
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#editMetadata(org.openquark.gems.client.navigator.NavAddress)
*/
public void editMetadata(NavAddress address) {
navFrame.editMetadata(address);
currentEditAddress = address;
stateEdit = new StateEdit(new MetadataStateEditable(address));
}
/**
* Edit metadata for the given entity.
* @param entity the entity to edit metadata for
*/
public void editMetadata(ScopedEntity entity) {
editMetadata(NavAddress.getAddress(entity));
}
/**
* Edit metadata for the given module.
* @param module the module to edit metadata for
*/
public void editMetadata(MetaModule module) {
editMetadata(NavAddress.getAddress(module));
}
/**
* Edit metadata for the given collector.
* @param collector the collector to edit metadata for
*/
public void editMetadata(CollectorGem collector) {
editMetadata(NavAddress.getAddress(collector));
}
/**
* Edit argument metadata for the given entity.
* @param entity the entity to edit argument metadata for
* @param argNum the number of the argument whose metadata to edit
*/
public void editMetadata(GemEntity entity, int argNum) {
NavAddress address = NavAddress.getAddress(entity).withParameter(NavAddress.ARGUMENT_PARAMETER, Integer.toString(argNum));
editMetadata(address);
}
/**
* Edit argument metadata for the given collector.
* @param collector the collector to edit argument metadata for
* @param argNum the number of the argument whose metadata to edit
*/
public void editMetadata(CollectorGem collector, int argNum) {
NavAddress address = NavAddress.getAddress(collector).withParameter(NavAddress.ARGUMENT_PARAMETER, Integer.toString(argNum));
editMetadata(address);
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#editComplete(org.openquark.gems.client.navigator.NavAddress)
*/
public void editComplete(NavAddress address) {
if (!address.equals(currentEditAddress)) {
throw new IllegalStateException("address for completed edit does not equal current edit address");
}
currentEditAddress = null;
stateEdit = null;
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#metadataChanged(org.openquark.gems.client.navigator.NavAddress)
*/
public void metadataChanged(NavAddress address) {
if (!address.equals(currentEditAddress)) {
throw new IllegalStateException("address for changed edit does not equal current edit address");
}
stateEdit.end();
undoableEditSupport.beginUpdate();
undoableEditSupport.setEditName(GemCutter.getResourceString("UndoText_ChangeGemProperties"));
undoableEditSupport.postEdit(stateEdit);
undoableEditSupport.endUpdate();
stateEdit = new StateEdit(new MetadataStateEditable(address));
updateInputNames(address);
nameCheckTableTop();
navFrame.refreshMetadata(currentEditAddress);
}
/**
* Updates the names of any PartInputs affected by the metadata changing for the
* entity with the given address. Does nothing if no updates are required.
* @param address the address of the entity whose metadata changed
*/
private void updateInputNames(NavAddress address) {
NavAddressMethod method = address.getMethod();
if (method != NavAddress.FUNCTION_METHOD &&
method != NavAddress.CLASS_METHOD_METHOD &&
method != NavAddress.DATA_CONSTRUCTOR_METHOD) {
return;
}
// If this was a functional agent, then we need to update its gems on the table top.
GemEntity entity = getPerspective().getWorkspace().getGemEntity(address.toFeatureName().toQualifiedName());
FunctionalAgentMetadata metadata = (FunctionalAgentMetadata) NavAddressHelper.getMetadata(this, address.withAllStripped());
ArgumentMetadata[] argMetadata = metadata.getArguments();
for (final Gem gem : gemCutter.getTableTop().getGemGraph().getGems()) {
if (gem instanceof FunctionalAgentGem) {
GemEntity gemEntity = ((FunctionalAgentGem) gem).getGemEntity();
if (gemEntity.equals(entity)) {
// We need to update the inputs of this gem
PartInput[] inputs = gem.getInputParts();
for (int i = 0; i < inputs.length; i++) {
// Update the original name for this input.
String displayName = i < argMetadata.length ? argMetadata[i].getDisplayName() : null;
String nameFromCode = i < entity.getNNamedArguments() ? entity.getNamedArgument(i) : null;
inputs[i].setOriginalInputName(displayName != null ? displayName : nameFromCode);
}
}
}
}
}
/**
* Name checks the table top to disambiguate any input names that may have become ambiguous
* as a result of the metadata changing. Also updates the TableTopExplorer to correctly
* display the new input names.
*/
private void nameCheckTableTop() {
// Update the tabletop for the new gem graph state. Among other things, this will ensure arg name disambiguation.
gemCutter.getTableTop().updateForGemGraph();
// Since we don't know which input names actually changed as a result of
// name checking the table top, we simply have to update all nodes in the explorer.
TableTopExplorer tableTopExplorer = gemCutter.getTableTopExplorer();
Set<DisplayedGem> displayedGemSet = gemCutter.getTableTop().getDisplayedGems();
for (final DisplayedGem gem : displayedGemSet) {
PartInput[] inputs = gem.getGem().getInputParts();
for (final PartInput input : inputs) {
TreeNode node = tableTopExplorer.getInputNode(input);
if (node != null) {
tableTopExplorer.getExplorerTreeModel().nodeChanged(node);
}
}
}
}
/**
* @see org.openquark.gems.client.navigator.NavFrameOwner#searchMetadata(java.lang.String)
*/
public List<NavAddress> searchMetadata(String searchString) {
return navFrame.searchMetadata(searchString);
}
/**
* Tests all metadata examples.
*/
public void testMetadataExamples() {
String conciseTitle = "Concise Output?";
String conciseMessage = "Would you like to suppress the output of successful metadata tests?";
int conciseResponse = JOptionPane.showOptionDialog(gemCutter,
conciseMessage,
conciseTitle,
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE,
null, null, null);
// If the user closed the dialog or hit cancel, do nothing
if (conciseResponse == JOptionPane.CANCEL_OPTION || conciseResponse == JOptionPane.CLOSED_OPTION) {
return;
}
String autoTitle = "Skip non-automatic examples?";
String autoMessage = "Would you like to run only those examples set to run automatically?";
int autoResponse = JOptionPane.showOptionDialog(gemCutter,
autoMessage,
autoTitle,
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE,
null, null, null);
// If the user closed the dialog or hit cancel, do nothing
if (autoResponse == JOptionPane.CANCEL_OPTION || autoResponse == JOptionPane.CLOSED_OPTION) {
return;
}
navFrame.testMetadataExamples(conciseResponse == JOptionPane.YES_OPTION, autoResponse==JOptionPane.YES_OPTION);
}
/**
* {@inheritDoc}
*/
public Locale getLocaleForMetadata() {
return GemCutter.getLocaleFromPreferences();
}
}