// $Id: TabConstraints.java 15968 2008-11-05 21:57:13Z tfmorris $
// Copyright (c) 1996-2007 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
package org.argouml.uml.ui;
import java.awt.BorderLayout;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JOptionPane;
import javax.swing.JToolBar;
import javax.swing.event.EventListenerList;
import org.apache.log4j.Logger;
import org.argouml.application.api.AbstractArgoJPanel;
import org.argouml.model.Model;
import org.argouml.ocl.ArgoFacade;
import org.argouml.ocl.OCLUtil;
import org.argouml.swingext.UpArrowIcon;
import org.argouml.ui.TabModelTarget;
import org.argouml.ui.targetmanager.TargetEvent;
import org.tigris.gef.presentation.Fig;
import org.tigris.toolbar.ToolBarManager;
import tudresden.ocl.OclException;
import tudresden.ocl.OclTree;
import tudresden.ocl.check.OclTypeException;
import tudresden.ocl.gui.ConstraintRepresentation;
import tudresden.ocl.gui.EditingUtilities;
import tudresden.ocl.gui.OCLEditor;
import tudresden.ocl.gui.OCLEditorModel;
import tudresden.ocl.gui.events.ConstraintChangeEvent;
import tudresden.ocl.gui.events.ConstraintChangeListener;
import tudresden.ocl.parser.OclParserException;
import tudresden.ocl.parser.analysis.DepthFirstAdapter;
import tudresden.ocl.parser.node.AConstraintBody;
import tudresden.ocl.parser.node.TName;
/**
* Tab for OCL constraint editing.
*
* @author v1.0: Falk Finger
* @author v2.0: Steffen Zschaler
*/
public class TabConstraints extends AbstractArgoJPanel
implements TabModelTarget, ComponentListener {
private static final Logger LOG = Logger.getLogger(TabConstraints.class);
/**
* The actual editor pane.
*/
private OCLEditor mOcleEditor;
/**
* The current target element.
*/
private Object mMmeiTarget;
/**
* The constructor.
*/
public TabConstraints() {
super("tab.constraints");
setIcon(new UpArrowIcon());
setLayout(new BorderLayout(0, 0));
mOcleEditor = new OCLEditor();
mOcleEditor.setOptionMask(OCLEditor.OPTIONMASK_TYPECHECK
/*| //removed to workaround problems with autosplit
OCLEditor.OPTIONMASK_AUTOSPLIT*/);
mOcleEditor.setDoAutoSplit(false);
setToolbarRollover(true);
setToolbarFloatable(false);
getOclToolbar().setName("misc.toolbar.constraints");
add(mOcleEditor);
addComponentListener(this);
}
/**
* Set the toolbar rollover style.
*
* @param enable If true then button borders do not become visible
* until mouse rolls over button.
*/
private void setToolbarRollover(boolean enable) {
if (!ToolBarManager.alwaysUseStandardRollover()) {
getOclToolbar().putClientProperty(
"JToolBar.isRollover", Boolean.TRUE);
}
}
/**
* Set the toolbar floating style.
*
* @param enable If true then the toolbar can be floated and docked
*/
private void setToolbarFloatable(boolean enable) {
getOclToolbar().setFloatable(false);
}
/**
* Get a reference to the toolbar object contained in the
* OCLEditor component. This is currently a nasty hack. We really
* require an interface method on OCLEditor (Bob Tarling 8 Feb
* 2003).
*
* @return The toolbar
*/
private JToolBar getOclToolbar() {
return (JToolBar) mOcleEditor.getComponent(0);
}
//TabModelTarget interface methods
/**
* Should this tab be activated for the current target element?<p>
*
* Argo only supports constraints for Classes and Features
* (eg. Attributes and Operations) currently.
*
* {@inheritDoc}
*/
public boolean shouldBeEnabled(Object target) {
target = (target instanceof Fig) ? ((Fig) target).getOwner() : target;
return (Model.getFacade().isAClass(target)
|| Model.getFacade().isAFeature(target));
}
/*
* @see org.argouml.ui.TabTarget#getTarget()
*/
public Object getTarget() {
return mMmeiTarget;
}
/**
* Refresh the tab because the target has changed.
*/
public void refresh() {
setTarget(mMmeiTarget);
}
/**
* Set the target element to be displayed in this tab. Only model elements
* will be accepted by the constraint tab.
*
* {@inheritDoc}
*/
public void setTarget(Object oTarget) {
oTarget =
(oTarget instanceof Fig) ? ((Fig) oTarget).getOwner() : oTarget;
if (!(Model.getFacade().isAModelElement(oTarget))) {
mMmeiTarget = null;
return;
}
mMmeiTarget = oTarget;
if (isVisible()) {
setTargetInternal(mMmeiTarget);
}
}
private void setTargetInternal(Object oTarget) {
// Set editor's model
if (oTarget != null) {
mOcleEditor.setModel(new ConstraintModel(oTarget));
}
}
/**
* Adapter to provide information and a manipulation interface for a
* target element's set of constraints to the constraint editor.
*/
private static class ConstraintModel implements OCLEditorModel {
/**
* The target element being edited.
*/
private Object theMMmeiTarget;
/**
* A list of all the constraints in m_nmeiTarget. This is necessary to
* induce a consistent order on the constraints.
*/
private ArrayList theMAlConstraints;
/**
* List of listeners.
*/
private EventListenerList theMEllListeners = new EventListenerList();
/**
* Construct a new ConstraintModel.
*/
public ConstraintModel(Object mmeiTarget) {
super();
theMMmeiTarget = mmeiTarget;
theMAlConstraints =
new ArrayList(Model.getFacade().getConstraints(theMMmeiTarget));
}
/**
* Return the number of constraints in this model.
*/
public int getConstraintCount() {
return theMAlConstraints.size();
}
/**
* Return the constraint with the specified index.
*
* @param nIdx the index of the constraint to be returned.
* 0 <= nIdx < {@link #getConstraintCount}
*/
public ConstraintRepresentation getConstraintAt(int nIdx) {
return representationFor(nIdx);
}
/**
* Remove the specified constraint from the model.
*
* @param nIdx the index of the constraint to be removed.
* 0 <= nIdx < {@link #getConstraintCount}
*/
public void removeConstraintAt(int nIdx) {
if ((nIdx < 0) || (nIdx > theMAlConstraints.size())) {
return;
}
Object mc = theMAlConstraints.remove(nIdx);
if (mc != null) {
Model.getCoreHelper().removeConstraint(theMMmeiTarget, mc);
}
fireConstraintRemoved(mc, nIdx);
}
/**
* Add a fresh constraint to the model.<p>
*
* There are 2 restrictions on what can be parsed, given
* the current OCL grammar:
* <ol>
* <li>Class names must have a capital first letter.
* <li>Feature name must have a lower case first letter.
* </ol>
*/
public void addConstraint() {
// check ocl parsing constraints
Object mmeContext = OCLUtil
.getInnerMostEnclosingNamespace(theMMmeiTarget);
String contextName = Model.getFacade().getName(mmeContext);
String targetName = Model.getFacade().getName(theMMmeiTarget);
if ((contextName == null
|| contextName.equals (""))
|| // this is to fix issue #2056
(targetName == null
|| targetName.equals (""))
|| // this is to fix issue #2056
!Character.isUpperCase(contextName.charAt(0))
|| (Model.getFacade().isAClass (theMMmeiTarget)
&& !Character.isUpperCase(targetName.charAt(0)))
|| (Model.getFacade().isAFeature(theMMmeiTarget)
&& !Character.isLowerCase(targetName.charAt(0)))) {
// TODO: I18n
JOptionPane.showMessageDialog(
null,
"The OCL Toolkit requires that:\n\n"
+ "Class names have a capital first letter and\n"
+ "Attribute or Operation names have "
+ "a lower case first letter.",
"Require Correct name convention:",
JOptionPane.ERROR_MESSAGE);
// do not create a constraint:
return;
}
// null elements represent new constraints, which will be
// added to the target the first time any actual editing
// takes place. This is done to ensure syntactical
// correctness of constraints stored with the target.
theMAlConstraints.add(null);
fireConstraintAdded();
}
// TODO: - please add some javadoc - ugly classname also
private class CR implements ConstraintRepresentation {
/**
* The constraint being represented.
*/
private Object theMMcConstraint;
/**
* The constraint's index in the list of
* constraints. Necessary only for new constraints, where
* m_mcConstraint is still <tt>null</tt>.
*/
private int theMNIdx = -1;
public CR(Object mcConstraint, int nIdx) {
super();
theMMcConstraint = mcConstraint;
theMNIdx = nIdx;
}
public CR(int nIdx) {
this(null, nIdx);
}
/**
* Get the name of the constraint.
*/
public String getName() {
if (theMMcConstraint == null) {
return "newConstraint";
}
return Model.getFacade().getName(theMMcConstraint);
}
/**
* Get the constraint's body.
*/
public String getData() {
if (theMMcConstraint == null) {
return OCLUtil.getContextString(theMMmeiTarget);
}
return (String) Model.getFacade().getBody(
Model.getFacade().getBody(theMMcConstraint));
}
/**
* Set the constraint's body text. For the exceptions the
* detailed message must be human readable.
*
* @param sData the new body of the constraint
*
* @exception IllegalStateException if the constraint is
* not in a state to accept body changes.
* @exception OclParserException if the specified constraint is not
* syntactically correct.
* @exception OclTypeException if the specified constraint
* does not adhere by OCL type rules.
*/
public void setData(String sData, EditingUtilities euHelper)
throws OclParserException, OclTypeException {
// Parse and check specified constraint.
OclTree tree = null;
try {
Object mmeContext = OCLUtil
.getInnerMostEnclosingNamespace(theMMmeiTarget);
try {
tree =
euHelper.parseAndCheckConstraint(
sData,
new ArgoFacade(mmeContext));
} catch (IOException ioe) {
// Ignored: Highly unlikely, and what would we
// do anyway? log it
LOG.error("problem parsing And Checking Constraints",
ioe);
return;
}
// Split constraint body, if user wants us to
if (euHelper.getDoAutoSplit()) {
List lConstraints = euHelper.splitConstraint(tree);
if (lConstraints.size() > 0) {
removeConstraintAt(theMNIdx);
for (Iterator i = lConstraints.iterator();
i.hasNext();) {
OclTree ocltCurrent = (OclTree) i.next();
Object mc =
Model.getCoreFactory()
.createConstraint();
Model.getCoreHelper().setName(mc, ocltCurrent
.getConstraintName());
Model.getCoreHelper().setBody(mc,
Model.getDataTypesFactory()
.createBooleanExpression(
"OCL",
ocltCurrent
.getExpression()));
Model.getCoreHelper().addConstraint(
theMMmeiTarget,
mc);
// the constraint _must_ be owned by a namespace
if (Model.getFacade().getNamespace(
theMMmeiTarget)
!= null) {
Model.getCoreHelper().addOwnedElement(
Model.getFacade().getNamespace(
theMMmeiTarget),
mc);
} else if (Model.getFacade().getNamespace(
mmeContext) != null) {
Model.getCoreHelper().addOwnedElement(
Model.getFacade().getNamespace(
mmeContext),
theMMcConstraint);
}
theMAlConstraints.add(mc);
fireConstraintAdded();
}
return;
}
}
// Store constraint body
Object mcOld = null;
if (theMMcConstraint == null) {
// New constraint, first time setData is called
theMMcConstraint =
Model.getCoreFactory().createConstraint();
Model.getCoreHelper().setName(
theMMcConstraint,
"newConstraint");
Model.getCoreHelper().setBody(
theMMcConstraint,
Model.getDataTypesFactory()
.createBooleanExpression("OCL", sData));
Model.getCoreHelper().addConstraint(theMMmeiTarget,
theMMcConstraint);
// the constraint _must_ be owned by a namespace
Object targetNamespace =
Model.getFacade().getNamespace(theMMmeiTarget);
Object contextNamespace =
Model.getFacade().getNamespace(mmeContext);
if (targetNamespace != null) {
Model.getCoreHelper().addOwnedElement(
targetNamespace,
theMMcConstraint);
} else if (contextNamespace != null) {
Model.getCoreHelper().addOwnedElement(
contextNamespace,
theMMcConstraint);
}
theMAlConstraints.set(theMNIdx, theMMcConstraint);
} else {
mcOld = Model.getCoreFactory().createConstraint();
Model.getCoreHelper().setName(
mcOld,
Model.getFacade().getName(theMMcConstraint));
Model.getCoreHelper().setBody(
mcOld,
Model.getDataTypesFactory()
.createBooleanExpression("OCL",
(String) Model.getFacade()
.getBody(
Model.getFacade().getBody(
theMMcConstraint))));
Model.getCoreHelper().setBody(theMMcConstraint,
Model.getDataTypesFactory()
.createBooleanExpression("OCL", sData));
}
fireConstraintDataChanged(theMNIdx, mcOld,
theMMcConstraint);
} catch (OclTypeException pe) {
LOG.warn("There was some sort of OCL Type problem", pe);
throw pe;
} catch (OclParserException pe1) {
LOG.warn("Could not parse the constraint", pe1);
throw pe1;
} catch (OclException oclExc) {
// a runtime exception that occurs when some
// internal test fails
LOG.warn("There was some unidentified problem");
throw oclExc;
}
}
/**
* Set the constraint's name.
*/
public void setName(
final String sName,
final EditingUtilities euHelper) {
if (theMMcConstraint != null) {
// Check name for consistency with spec
if (!euHelper.isValidConstraintName(sName)) {
throw new IllegalArgumentException(
"Please specify a valid name.");
}
// Set name
Object mcOld =
Model.getCoreFactory().createConstraint();
Model.getCoreHelper().setName(mcOld,
Model.getFacade().getName(theMMcConstraint));
Object constraintBody =
Model.getFacade().getBody(theMMcConstraint);
Model.getCoreHelper().setBody(mcOld,
Model.getDataTypesFactory()
.createBooleanExpression(
"OCL",
(String) Model.getFacade().getBody(
constraintBody)));
Model.getCoreHelper().setName(theMMcConstraint, sName);
fireConstraintNameChanged(theMNIdx, mcOld,
theMMcConstraint);
// Also set name in constraint body -- Added 03/14/2001
try {
OclTree tree = null;
Object mmeContext = OCLUtil
.getInnerMostEnclosingNamespace(theMMmeiTarget);
constraintBody =
Model.getFacade().getBody(theMMcConstraint);
tree =
euHelper.parseAndCheckConstraint(
(String) Model.getFacade().getBody(
constraintBody),
new ArgoFacade(mmeContext));
if (tree != null) {
tree.apply(new DepthFirstAdapter() {
private int nameID = 0;
public void caseAConstraintBody(
AConstraintBody node) {
// replace name
if (nameID == 0) {
node.setName(new TName(sName));
} else {
node.setName(new TName(
sName + "_" + nameID));
}
nameID++;
}
});
setData(tree.getExpression(), euHelper);
}
} catch (Throwable t) {
// OK, so that didn't work out... Just ignore
// any problems and don't set the name in the
// constraint body better had log it.
LOG.error("some unidentified problem", t);
}
} else {
throw new IllegalStateException(
"Please define and submit a constraint body first.");
}
}
}
/**
* Create a representation adapter for the given constraint.
*/
private CR representationFor(int nIdx) {
if ((nIdx < 0) || (nIdx >= theMAlConstraints.size())) {
return null;
}
Object mc = theMAlConstraints.get(nIdx);
if (mc != null) {
return new CR(mc, nIdx);
}
return new CR(nIdx);
}
/**
* Add a listener to be informed of changes in the model.
*
* @param ccl the new listener
*/
public void addConstraintChangeListener(ConstraintChangeListener ccl) {
theMEllListeners.add(ConstraintChangeListener.class, ccl);
}
/**
* Remove a listener to be informed of changes in the model.
*
* @param ccl the listener to be removed
*/
public void removeConstraintChangeListener(
ConstraintChangeListener ccl) {
theMEllListeners.remove(ConstraintChangeListener.class, ccl);
}
protected void fireConstraintRemoved(
Object mc, int nIdx) {
// Guaranteed to return a non-null array
Object[] listeners = theMEllListeners.getListenerList();
ConstraintChangeEvent cce = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ConstraintChangeListener.class) {
// Lazily create the event:
if (cce == null) {
cce = new ConstraintChangeEvent(
this,
nIdx,
new CR(mc, nIdx),
null);
}
((ConstraintChangeListener) listeners[i + 1])
.constraintRemoved(cce);
}
}
}
protected void fireConstraintAdded() {
// Guaranteed to return a non-null array
Object[] listeners = theMEllListeners.getListenerList();
ConstraintChangeEvent cce = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ConstraintChangeListener.class) {
// Lazily create the event:
if (cce == null) {
int nIdx = theMAlConstraints.size() - 1;
cce =
new ConstraintChangeEvent(
this,
nIdx,
null,
representationFor(nIdx));
}
((ConstraintChangeListener) listeners[i + 1])
.constraintAdded(cce);
}
}
}
protected void fireConstraintDataChanged(
int nIdx,
Object mcOld,
Object mcNew) {
// Guaranteed to return a non-null array
Object[] listeners = theMEllListeners.getListenerList();
ConstraintChangeEvent cce = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ConstraintChangeListener.class) {
// Lazily create the event:
if (cce == null) {
cce = new ConstraintChangeEvent(
this,
nIdx,
new CR(mcOld, nIdx),
new CR(mcNew, nIdx));
}
((ConstraintChangeListener) listeners[i + 1])
.constraintDataChanged(cce);
}
}
}
protected void fireConstraintNameChanged(
int nIdx,
Object mcOld,
Object mcNew) {
// Guaranteed to return a non-null array
Object[] listeners = theMEllListeners.getListenerList();
ConstraintChangeEvent cce = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ConstraintChangeListener.class) {
// Lazily create the event:
if (cce == null) {
cce = new ConstraintChangeEvent(
this,
nIdx,
new CR(mcOld, nIdx),
new CR(mcNew, nIdx));
}
((ConstraintChangeListener) listeners[i + 1])
.constraintNameChanged(cce);
}
}
}
}
/*
* @see org.argouml.ui.targetmanager.TargetListener#targetAdded(
* org.argouml.ui.targetmanager.TargetEvent)
*/
public void targetAdded(TargetEvent e) {
// TODO: Why is this ignored? - tfm - 20070110
}
/*
* @see org.argouml.ui.targetmanager.TargetListener#targetRemoved(
* org.argouml.ui.targetmanager.TargetEvent)
*/
public void targetRemoved(TargetEvent e) {
setTarget(e.getNewTarget());
}
/*
* @see org.argouml.ui.targetmanager.TargetListener#targetSet(
* org.argouml.ui.targetmanager.TargetEvent)
*/
public void targetSet(TargetEvent e) {
setTarget(e.getNewTarget());
}
/*
* @see java.awt.event.ComponentListener#componentShown(java.awt.event.ComponentEvent)
*/
public void componentShown(ComponentEvent e) {
// Update our model with our saved target
setTargetInternal(mMmeiTarget);
}
/*
* @see java.awt.event.ComponentListener#componentHidden(java.awt.event.ComponentEvent)
*/
public void componentHidden(ComponentEvent e) {
// We have no model event listeners, so no need to do anything
}
public void componentMoved(ComponentEvent e) {
// ignored
}
public void componentResized(ComponentEvent e) {
// ignored
}
}