001: /*
002: * <copyright>
003: *
004: * Copyright 2000-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.tools.csmart.ui.experiment;
028:
029: import org.cougaar.tools.csmart.core.property.ModifiableComponent;
030: import org.cougaar.tools.csmart.experiment.DBExperiment;
031: import org.cougaar.tools.csmart.experiment.Experiment;
032: import org.cougaar.tools.csmart.recipe.RecipeComponent;
033: import org.cougaar.tools.csmart.society.AgentComponent;
034: import org.cougaar.tools.csmart.society.SocietyComponent;
035: import org.cougaar.tools.csmart.ui.tree.CSMARTDataFlavor;
036: import org.cougaar.tools.csmart.ui.tree.DMTNArray;
037: import org.cougaar.tools.csmart.ui.tree.DNDTree;
038: import org.cougaar.tools.csmart.ui.viewer.CSMART;
039: import org.cougaar.util.log.Logger;
040:
041: import javax.swing.*;
042: import javax.swing.tree.DefaultMutableTreeNode;
043: import javax.swing.tree.DefaultTreeModel;
044: import javax.swing.tree.TreeNode;
045: import javax.swing.tree.TreePath;
046: import java.awt.datatransfer.DataFlavor;
047: import java.awt.datatransfer.Transferable;
048: import java.awt.dnd.DnDConstants;
049: import java.io.IOException;
050: import java.io.ObjectInputStream;
051:
052: public class ExperimentTree extends DNDTree {
053: private transient Logger log;
054:
055: public static final String RECIPES = "Recipes";
056: public static final String SOCIETIES = "Societies";
057: public static final String COMPONENTS = "Components";
058: public static final CSMARTDataFlavor societyFlavor = new CSMARTDataFlavor(
059: SocietyComponent.class, null, ExperimentTree.class,
060: "Society");
061: public static final CSMARTDataFlavor recipeFlavor = new CSMARTDataFlavor(
062: RecipeComponent.class, null, ExperimentTree.class, "Recipe");
063: public static final CSMARTDataFlavor componentFlavor = new CSMARTDataFlavor(
064: ModifiableComponent.class, null, ExperimentTree.class,
065: "Modifiable Component");
066:
067: private DefaultTreeModel model;
068: private Experiment experiment;
069:
070: private static class MyTransferable implements Transferable {
071: Object theData;
072: DataFlavor[] flavors;
073:
074: public MyTransferable(DefaultMutableTreeNode aNode) {
075: theData = aNode.getUserObject();
076: if (theData instanceof RecipeComponent)
077: //else if (theData instanceof Recipe)
078: flavors = new DataFlavor[] { recipeFlavor };
079: else if (theData instanceof ModifiableComponent)
080: flavors = new DataFlavor[] { componentFlavor };
081: else
082: throw new IllegalArgumentException("Unknown node");
083: }
084:
085: public Object getTransferData(DataFlavor flavor) {
086: if (!flavor.equals(flavors[0]))
087: throw new IllegalArgumentException("Illegal DataFlavor");
088: return theData;
089: }
090:
091: public DataFlavor[] getTransferDataFlavors() {
092: return flavors;
093: }
094:
095: public boolean isDataFlavorSupported(DataFlavor flavor) {
096: return flavors[0].equals(flavor);
097: }
098: }
099:
100: public ExperimentTree(DefaultTreeModel model, Experiment experiment) {
101: super (model);
102: this .model = model;
103: this .experiment = experiment;
104: setExpandsSelectedPaths(true);
105: createLogger();
106: }
107:
108: private void createLogger() {
109: log = CSMART.createLogger(this .getClass().getName());
110: }
111:
112: public void setSelection(TreeNode treeNode) {
113: TreeNode[] nodes = model.getPathToRoot(treeNode);
114: TreePath path = new TreePath(nodes);
115: setSelectionPath(path);
116: }
117:
118: public Transferable makeDraggableObject(Object o) {
119: if (o instanceof DefaultMutableTreeNode) {
120: DefaultMutableTreeNode node = (DefaultMutableTreeNode) o;
121: try {
122: return new MyTransferable(node);
123: } catch (IllegalArgumentException iae) {
124: return null;
125: }
126: }
127: throw new IllegalArgumentException(
128: "Not a DefaultMutableTreeNode");
129: }
130:
131: private int testFlavors(DataFlavor[] possibleFlavors,
132: CSMARTDataFlavor testFlavor) {
133: for (int i = 0; i < possibleFlavors.length; i++) {
134: DataFlavor flavor = possibleFlavors[i];
135: if (flavor instanceof CSMARTDataFlavor) {
136: CSMARTDataFlavor cflavor = (CSMARTDataFlavor) flavor;
137: if (cflavor.equals(testFlavor)) {
138: if (getClass().getName().equals(
139: cflavor.getSourceClassName())) {
140: return DnDConstants.ACTION_MOVE;
141: } else {
142: return DnDConstants.ACTION_COPY;
143: }
144: }
145: }
146: }
147: return DnDConstants.ACTION_NONE;
148: }
149:
150: public int isDroppable(DataFlavor[] possibleFlavors,
151: DefaultMutableTreeNode target) {
152: if (!isEditable())
153: return DnDConstants.ACTION_NONE;
154: Object userObject = target.getUserObject();
155: if (userObject instanceof String) {
156: if (userObject.equals(SOCIETIES)) {
157: // only allow one society per experiment for now
158: if (target.getChildCount() > 0)
159: return DnDConstants.ACTION_NONE;
160: return testFlavors(possibleFlavors, societyFlavor);
161: }
162: if (userObject.equals(RECIPES)) {
163: return testFlavors(possibleFlavors, recipeFlavor);
164: }
165: } else if (target == model.getRoot()) {
166: return DnDConstants.ACTION_COPY
167: & (testFlavors(possibleFlavors, societyFlavor) | testFlavors(
168: possibleFlavors, recipeFlavor));
169: }
170: return DnDConstants.ACTION_NONE;
171: }
172:
173: public boolean isDraggable(Object o) {
174: if (o instanceof DefaultMutableTreeNode) {
175: DefaultMutableTreeNode node = (DefaultMutableTreeNode) o;
176: if (node == getModel().getRoot()) {
177: return false; // not draggable if it's the root node
178: }
179: return true;
180: }
181:
182: return false;
183: }
184:
185: /**
186: * Add a dropped element to the tree.
187: * Rejects duplicates. An object is a duplicate
188: * if it has the same name as a child of the target,
189: * and is not from the same tree.
190: * @param t the transferable object to drop
191: * @param target where to drop the object
192: * @param before before which node to drop it or null
193: * @return int the action as defined in DnDConstants
194: */
195: public int addElement(Transferable t,
196: DefaultMutableTreeNode target, DefaultMutableTreeNode before) {
197: DataFlavor[] flavors = t.getTransferDataFlavors();
198: // perhaps isDroppable should more rigorously check all the
199: // transferables, as it stands, the next addElement method
200: // does checks for individual elements when an array of them is added
201: int action = isDroppable(flavors, target);
202: if (action == DnDConstants.ACTION_NONE)
203: return DnDConstants.ACTION_NONE;
204: DataFlavor flavor = flavors[0];
205: Object data = null;
206: try {
207: data = t.getTransferData(flavors[0]);
208: } catch (Exception e) {
209: if (log.isErrorEnabled()) {
210: log.error("Exception adding dropped element:", e);
211: return DnDConstants.ACTION_NONE;
212: }
213: }
214: if (data == null) {
215: if (log.isErrorEnabled()) {
216: log.error("Attempting to add null dropped element");
217: }
218: return DnDConstants.ACTION_NONE;
219: }
220: if (data instanceof DMTNArray) {
221: DMTNArray nodes = (DMTNArray) data;
222: for (int i = 0; i < nodes.nodes.length; i++)
223: addElement(nodes.nodes[i].getUserObject(), flavor,
224: target, before);
225: } else
226: addElement(data, flavor, target, before);
227: return action;
228: }
229:
230: /**
231: * Add the element; this does some additional checks which may
232: * reject the element. In particular, this checks each element
233: * when adding an array of elements.
234: */
235: private void addElement(Object userData, DataFlavor flavor,
236: DefaultMutableTreeNode target, DefaultMutableTreeNode before) {
237: DefaultMutableTreeNode root = (DefaultMutableTreeNode) model
238: .getRoot();
239: // if target is root, try to find the right place to drop the element
240: if (target == root) {
241: for (int i = 0, n = root.getChildCount(); i < n; i++) {
242: DefaultMutableTreeNode node = (DefaultMutableTreeNode) root
243: .getChildAt(i);
244: Object o = node.getUserObject();
245: if (SOCIETIES.equals(o)
246: && userData instanceof SocietyComponent) {
247: target = node;
248: before = null;
249: break;
250: } else if (RECIPES.equals(o)
251: && userData instanceof RecipeComponent) {
252: target = node;
253: before = null;
254: break;
255: }
256: }
257: if (target == root)
258: return; // no place to put the new element
259: }
260: // make sure we don't add a recipe to the societies or vice versa
261: if (target.getUserObject().equals(SOCIETIES)
262: && userData instanceof RecipeComponent) {
263: return;
264: }
265: if (target.getUserObject().equals(RECIPES)
266: && userData instanceof SocietyComponent) {
267: return;
268: }
269: // don't add more than one society
270: if (target.getUserObject().equals(SOCIETIES)
271: && target.getChildCount() > 0) {
272: return;
273: }
274: // if we're adding a recipe that adds an agent that's
275: // in the experiment, then reject the recipe
276: if (userData instanceof RecipeComponent) {
277: if (!checkAdd((RecipeComponent) userData))
278: return;
279: }
280: if (userData instanceof ModifiableComponent) {
281: ModifiableComponent mcc = (ModifiableComponent) userData;
282: // check for duplicates, if not dragging from same tree
283: String this ClassName = getClass().getName();
284: if (flavor instanceof CSMARTDataFlavor
285: && !((CSMARTDataFlavor) flavor)
286: .getSourceClassName().equals(this ClassName)) {
287: String newName = mcc.getShortName();
288: int ix = target.getChildCount();
289: for (int i = 0; i < ix; i++) {
290: DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) target
291: .getChildAt(i);
292: Object targetChildData = childNode.getUserObject();
293: if (targetChildData instanceof ModifiableComponent
294: && ((ModifiableComponent) targetChildData)
295: .getShortName().equals(newName))
296: return;
297: }
298: }
299: }
300: DefaultMutableTreeNode node = new DefaultMutableTreeNode(
301: userData, false);
302: int ix = target.getChildCount();
303: if (before != null) {
304: ix = model.getIndexOfChild(target, before);
305: }
306: if (log.isDebugEnabled()) {
307: log.debug("Insert into " + target + " at " + ix
308: + " before " + before);
309: }
310: model.insertNodeInto(node, target, ix);
311: selectNode(node);
312: }
313:
314: private boolean checkAdd(RecipeComponent rc) {
315: AgentComponent[] recAgents = rc.getAgents();
316: if (recAgents != null && recAgents.length > 0) {
317: for (int j = 0; j < recAgents.length; j++) {
318: if (!experiment.agentNameUnique(recAgents[j]
319: .getShortName())) {
320: final String dupName = recAgents[j].getShortName();
321: // Need to remove recipe and inform user.
322: javax.swing.SwingUtilities
323: .invokeLater(new Runnable() {
324: public void run() {
325: JOptionPane.showMessageDialog(null,
326: "Experiment cannot contain two agents with the same name: "
327: + dupName,
328: "Recipe Add Aborted!",
329: JOptionPane.ERROR_MESSAGE);
330: }
331: });
332: return false;
333: }
334: }
335: }
336: return true;
337: }
338:
339: private void readObject(ObjectInputStream ois) throws IOException,
340: ClassNotFoundException {
341: ois.defaultReadObject();
342: createLogger();
343: }
344:
345: }
|