/* $Id: WizAssocComposite.java 17849 2010-01-12 19:50:34Z linus $ ***************************************************************************** * Copyright (c) 2009 Contributors - see below * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * tfmorris ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // 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.cognitive.critics; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.JPanel; import org.apache.log4j.Logger; import org.argouml.cognitive.ui.WizStepChoice; import org.argouml.i18n.Translator; import org.argouml.model.Model; /** * A non-modal wizard to assist the user in changing aggregation of an * association. * <p> * * Earlier version always imposed composite aggregation. This version allows the * user to choose. * <p> * * <em>Note</em>. This only applies to binary associations. A separate wizard * is needed for 3-way (or more) associations. * <p> * * @see "ArgoUML User Manual: Two Aggregate ends (roles) in binary Association" * @author jrobbins@ics.uci.edu */ public class WizAssocComposite extends UMLWizard { /** * Logger. */ private static final Logger LOG = Logger.getLogger(WizAssocComposite.class); /** * The initial instructions on the Step 1 screen. May be set to a different * string through {@link #setInstructions(String)}. * <p> */ private String instructions = Translator .localize("critics.WizAssocComposite-ins"); /** * Contains the {@link WizStepChoice} that is used to get the user's desired * options. Not created until we get to that step. * <p> */ private WizStepChoice step1Choice = null; /** * The Association {@link WizStepChoice} that triggered the critic. Null * until set when it is first needed. * <p> */ private Object triggerAssociation = null; /** * Constructor for the wizard. Currently does nothing. * <p> */ public WizAssocComposite() { } /** * Tries to identify the Association that triggered the critic. * <p> * * The first time it is called, it will initialise the trigger from the * ToDoItem. If there, it is assumed to be the first trigger of the ToDoItem * and to be an association. If found, the value is stored in the private * field {@link #triggerAssociation}. * <p> * * On all subsequent calls, if a non-null value is found in {@link * #triggerAssociation} that is returned. * <p> * * @return the Association that triggered the critic, or <code>null</code> * if there was none. */ private Object getTriggerAssociation() { // If we don't have it, find the trigger. If this fails it will keep // its default value of null if ((triggerAssociation == null) && (getToDoItem() != null)) { triggerAssociation = getModelElement(); } return triggerAssociation; } /** * Returns a list of options to be used in creating a {@link * WizStepChoice} that will exercise the options. * <p> * * We provide five options, shared aggregation in each direction, composite * aggregation in each direction and no aggregation at all. * <p> * * It is possible that a very malicious user could delete the triggering * association just before we get to this point. For now we don't bother to * trap this. It will raise an exception, and then everything will carry on * happily. * <p> * * @return A {@link List} of the options or <code>null</code> if the * association that triggered the critic is no longer there. */ private List<String> buildOptions() { // The association that triggered the critic. Its just possible the // association is no longer there, in which case we return null Object asc = getTriggerAssociation(); if (asc == null) { return null; } List<String> result = new ArrayList<String>(); // Get the ends from the association (we know there are two), and the // types associated with them. Iterator iter = Model.getFacade().getConnections(asc).iterator(); Object ae0 = iter.next(); Object ae1 = iter.next(); Object cls0 = Model.getFacade().getType(ae0); Object cls1 = Model.getFacade().getType(ae1); // Get the names of the two ends. If there are none (i.e they are // currently anonymous), use the ArgoUML convention of "(anon)" for the // names String start = Translator.localize("misc.name.anon"); String end = Translator.localize("misc.name.anon"); if ((cls0 != null) && (Model.getFacade().getName(cls0) != null) && (!(Model.getFacade().getName(cls0).equals("")))) { start = Model.getFacade().getName(cls0); } if ((cls1 != null) && (Model.getFacade().getName(cls1) != null) && (!(Model.getFacade().getName(cls1).equals("")))) { end = Model.getFacade().getName(cls1); } // Now create the five options result.add(start + Translator.localize("critics.WizAssocComposite-option1") + end); result.add(start + Translator.localize("critics.WizAssocComposite-option2") + end); result.add(end + Translator.localize("critics.WizAssocComposite-option1") + start); result.add(end + Translator.localize("critics.WizAssocComposite-option2") + start); result.add(Translator.localize("critics.WizAssocComposite-option3")); return result; } /** * Set the initial instruction string for the choice. May be called by the * creator of the wizard to override the default. * <p> * * @param s * The new instructions. */ public void setInstructions(String s) { instructions = s; } /** * Create a {@link JPanel} for the given step. * <p> * We use a {@link WizStepChoice} to handle the choice selection for the * user. We only create the panel once, saving it in a private field * (<code>_step1Choice</code>) for subsequent use. * <p> * <em>Note</em>. If the association has been deleted, then we may not be * able to create a list of options. Under these circumstances we also * return null. * <p> * * @param newStep The index of the step for which a panel is needed. * @return The created {@link JPanel} or <code>null</code> if no options * were available. * @see org.argouml.cognitive.critics.Wizard */ public JPanel makePanel(int newStep) { switch (newStep) { case 1: // First step. Create the panel if not already done and options are // available. Otherwise it retains its default value of null. if (step1Choice == null) { List<String> opts = buildOptions(); if (opts != null) { step1Choice = new WizStepChoice(this, instructions, opts); step1Choice.setTarget(getToDoItem()); } } return step1Choice; default: } // Default (any other step) is to return nothing return null; } /** * Take action at the completion of a step. * <p> * * The guideline for ArgoUML non-modal wizards is to act immediately, not * wait for the finish. This method may also be invoked when finish is * triggered for any steps whose panels didn't get created. * <p> * * The observation is that this seems to be trigged when there is any change * on the panel (e.g choosing an option), not just when "next" is pressed. * Coded accordingly * <p> * * We allow for the association that caused the problem having by now been * deleted, and hence an exception may be raised. We catch this politely. * <p> * * @param oldStep * The index of the step just completed (0 for the first * information panel) * * @see org.argouml.cognitive.critics.Wizard */ public void doAction(int oldStep) { switch (oldStep) { case 1: // Just completed the first step where we make our choices. First // see if we have a choice. We always should, so print a rude // message if we don't int choice = -1; if (step1Choice != null) { choice = step1Choice.getSelectedIndex(); } if (choice == -1) { LOG.warn("WizAssocComposite: nothing selected, " + "should not get here"); return; } // It is quite possible that the cause of the problem has by now // been deleted, in which case we will throw an exception if we try // to change things. Catch this tidily. try { // Set the appropriate aggregation on each end Iterator iter = Model.getFacade().getConnections( getTriggerAssociation()).iterator(); Object ae0 = iter.next(); Object ae1 = iter.next(); switch (choice) { case 0: // Start is a composite aggregation of end Model.getCoreHelper().setAggregation(ae0, Model.getAggregationKind().getComposite()); Model.getCoreHelper().setAggregation(ae1, Model.getAggregationKind().getNone()); break; case 1: // Start is a shared aggregation of end Model.getCoreHelper().setAggregation(ae0, Model.getAggregationKind().getAggregate()); Model.getCoreHelper().setAggregation(ae1, Model.getAggregationKind().getNone()); break; case 2: // End is a composite aggregation of start Model.getCoreHelper().setAggregation(ae0, Model.getAggregationKind().getNone()); Model.getCoreHelper().setAggregation(ae1, Model.getAggregationKind().getComposite()); break; case 3: // End is a shared aggregation of start Model.getCoreHelper().setAggregation(ae0, Model.getAggregationKind().getNone()); Model.getCoreHelper().setAggregation(ae1, Model.getAggregationKind().getAggregate()); break; case 4: // No aggregation Model.getCoreHelper().setAggregation(ae0, Model.getAggregationKind().getNone()); Model.getCoreHelper().setAggregation(ae1, Model.getAggregationKind().getNone()); break; default: } } catch (Exception pve) { // Someone took our association away. LOG.error("WizAssocComposite: could not set " + "aggregation.", pve); } default: } } /** * Determine if we have sufficient information to finish. * <p> * We can't finish if our parent * {@link org.argouml.cognitive.critics.Wizard} can't finish. * <p> * We can finish if we're on step 0. * <p> * We can finish if we're on step 1 and have made a choice. * <p> * * @return <code>true</code> if we can finish, otherwise * <code>false</code>. * @see org.argouml.cognitive.critics.Wizard */ @Override public boolean canFinish() { // Can't finish if our parent can't if (!super.canFinish()) { return false; } // Can finish if it's step 0 if (getStep() == 0) { return true; } // Can finish if we're on step1 and have actually made a choice if ((getStep() == 1) && (step1Choice != null) && (step1Choice.getSelectedIndex() != -1)) { return true; } // Otherwise we can't finish return false; } }