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.community;
028:
029: import org.cougaar.tools.csmart.experiment.HostComponent;
030: import org.cougaar.tools.csmart.society.AgentComponent;
031: import org.cougaar.tools.csmart.ui.tree.CSMARTDataFlavor;
032: import org.cougaar.tools.csmart.ui.tree.DMTNArray;
033: import org.cougaar.tools.csmart.ui.tree.DNDTree;
034: import org.cougaar.tools.csmart.ui.viewer.CSMART;
035: import org.cougaar.util.log.Logger;
036:
037: import javax.swing.tree.DefaultMutableTreeNode;
038: import javax.swing.tree.DefaultTreeModel;
039: import javax.swing.tree.TreePath;
040: import java.awt.datatransfer.DataFlavor;
041: import java.awt.datatransfer.Transferable;
042: import java.awt.dnd.DnDConstants;
043: import java.awt.event.MouseEvent;
044: import java.io.IOException;
045: import java.io.ObjectInputStream;
046:
047: /**
048: * Provides method definitions for abstract methods in DNDTree
049: * to support Community tree in the Community Panel of
050: * the Experiment Builder.
051: */
052:
053: public class CommunityDNDTree extends DNDTree {
054:
055: private transient Logger log;
056:
057: protected static CSMARTDataFlavor agentFlavor = new CSMARTDataFlavor(
058: DefaultMutableTreeNode.class, AgentComponent.class,
059: CommunityDNDTree.class, "CSMART Agent");
060: protected static CSMARTDataFlavor hostFlavor = new CSMARTDataFlavor(
061: DefaultMutableTreeNode.class, HostComponent.class,
062: CommunityDNDTree.class, "CSMART Host");
063: protected static CSMARTDataFlavor communityFlavor = new CSMARTDataFlavor(
064: DefaultMutableTreeNode.class, CommunityTreeObject.class,
065: CommunityDNDTree.class, "CSMART Community");
066: protected static CSMARTDataFlavor agentArrayFlavor = new CSMARTDataFlavor(
067: DMTNArray.class, AgentComponent.class,
068: CommunityDNDTree.class, "CSMART Agent");
069: protected static CSMARTDataFlavor hostArrayFlavor = new CSMARTDataFlavor(
070: DMTNArray.class, HostComponent.class,
071: CommunityDNDTree.class, "CSMART Host");
072: protected static CSMARTDataFlavor communityArrayFlavor = new CSMARTDataFlavor(
073: DMTNArray.class, CommunityTreeObject.class,
074: CommunityDNDTree.class, "CSMART Community");
075:
076: public CommunityDNDTree(DefaultTreeModel model) {
077: super (model);
078: setToolTipText("");
079: createLogger();
080: }
081:
082: private void createLogger() {
083: log = CSMART.createLogger(this .getClass().getName());
084: }
085:
086: public String getToolTipText(MouseEvent evt) {
087: if (getRowForLocation(evt.getX(), evt.getY()) == -1)
088: return null;
089: TreePath path = getPathForLocation(evt.getX(), evt.getY());
090: DefaultMutableTreeNode node = (DefaultMutableTreeNode) path
091: .getLastPathComponent();
092: Object o = node.getUserObject();
093: if (o != null && o instanceof CommunityTreeObject)
094: return ((CommunityTreeObject) o).getToolTip();
095: else
096: return "";
097: }
098:
099: /**
100: * Add a new node to the tree; used to build trees when
101: * user displays a new tree.
102: */
103:
104: protected DefaultMutableTreeNode addNode(
105: DefaultMutableTreeNode node, String name, String type,
106: String communityName) {
107: DefaultTreeModel model = (DefaultTreeModel) getModel();
108: DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(
109: new CommunityTreeObject(name, type, communityName),
110: (type.equals("Root") || type.equals("Host") || type
111: .equalsIgnoreCase("Community")));
112: model.insertNodeInto(newNode, node, node.getChildCount());
113: return newNode;
114: }
115:
116: protected boolean supportsMultiDrag() {
117: return true;
118: }
119:
120: /**
121: * Return true if object can be dragged and false otherwise.
122: * Anything but roots can be dragged.
123: */
124:
125: public boolean isDraggable(Object selected) {
126: DefaultMutableTreeNode node = (DefaultMutableTreeNode) selected;
127: return (node.getParent() != null);
128: }
129:
130: /**
131: * Make a draggable object from a DefaultMutableTreeNode.
132: */
133:
134: public Transferable makeDraggableObject(Object o) {
135: if (o instanceof DefaultMutableTreeNode) {
136: return new CommunityTreeObjectTransferable(
137: (DefaultMutableTreeNode) o);
138: } else if (o instanceof DMTNArray) {
139: DMTNArray nodeArray = (DMTNArray) o;
140: CommunityTreeObject userObject = (CommunityTreeObject) nodeArray.nodes[0]
141: .getUserObject();
142: if (userObject == null)
143: throw new IllegalArgumentException("null user object");
144: CSMARTDataFlavor[] theFlavors = null;
145: if (userObject.isCommunity())
146: theFlavors = new CSMARTDataFlavor[] { communityArrayFlavor };
147: else if (userObject.isHost())
148: theFlavors = new CSMARTDataFlavor[] { hostArrayFlavor };
149: else if (userObject.isAgent())
150: theFlavors = new CSMARTDataFlavor[] { agentArrayFlavor };
151: return new CommunityArrayTransferable(nodeArray, theFlavors);
152: }
153: return null;
154: }
155:
156: /**
157: * Uses CommunityTreeObject class to enforce dropping rules.
158: */
159:
160: protected int isDroppable(DataFlavor[] flavors,
161: DefaultMutableTreeNode target) {
162: Object userObject = target.getUserObject();
163: if (userObject instanceof CommunityTreeObject) {
164: CommunityTreeObject cto = (CommunityTreeObject) userObject;
165: for (int i = 0; i < flavors.length; i++) {
166: if (cto.allowsDataFlavor(flavors[i])) {
167: return testFlavors(flavors,
168: (CSMARTDataFlavor) flavors[i]);
169: }
170: }
171: }
172: return DnDConstants.ACTION_NONE;
173: }
174:
175: // if the move is allowed
176: // returns ACTION_MOVE if the object is being moved in the same tree
177: // and ACTION_COPY if the object is being moved between trees
178:
179: private int testFlavors(DataFlavor[] possibleFlavors,
180: CSMARTDataFlavor testFlavor) {
181: for (int i = 0; i < possibleFlavors.length; i++) {
182: DataFlavor flavor = possibleFlavors[i];
183: if (flavor instanceof CSMARTDataFlavor) {
184: CSMARTDataFlavor cflavor = (CSMARTDataFlavor) flavor;
185: if (cflavor.equals(testFlavor)) {
186: if (getClass().getName().equals(
187: cflavor.getSourceClassName())) {
188: return DnDConstants.ACTION_MOVE;
189: } else {
190: return DnDConstants.ACTION_COPY;
191: }
192: }
193: }
194: }
195: return DnDConstants.ACTION_NONE;
196: }
197:
198: /**
199: * Adds dropped object to this tree; called by drop method.
200: * Returns true if successful.
201: */
202:
203: public int addElement(Transferable transferable,
204: DefaultMutableTreeNode target, DefaultMutableTreeNode before) {
205: DataFlavor[] flavors = transferable.getTransferDataFlavors();
206: int action = isDroppable(flavors, target);
207: if (action == DnDConstants.ACTION_NONE) {
208: return action;
209: }
210: Object data = null;
211: try {
212: data = transferable.getTransferData(transferable
213: .getTransferDataFlavors()[0]);
214: } catch (Exception e) {
215: if (log.isErrorEnabled()) {
216: log.error("Exception", e);
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: if (!addElement(nodes.nodes[i], target, before))
224: return DnDConstants.ACTION_NONE;
225: }
226: } else {
227: if (!addElement((DefaultMutableTreeNode) data, target,
228: before))
229: return DnDConstants.ACTION_NONE;
230: }
231: return action;
232: }
233:
234: /**
235: * Add an element by copying the user object in the source
236: * and creating a new tree node for it.
237: * Hosts are not added, only their children.
238: * Nodes (node agents) are added and their children are
239: * added at the same level (i.e. the hierarchy is flattened).
240: */
241:
242: private boolean addElement(DefaultMutableTreeNode source,
243: DefaultMutableTreeNode target, DefaultMutableTreeNode before) {
244: // disallow move if source is an ancestor of target
245: if (target.isNodeAncestor(source)) {
246: return false;
247: }
248: DefaultTreeModel model = (DefaultTreeModel) getModel();
249: // drop new node at end by default, unless before target is specified
250: int ix = target.getChildCount();
251: if (before != null)
252: ix = model.getIndexOfChild(target, before);
253: CommunityTreeObject cto = (CommunityTreeObject) source
254: .getUserObject();
255: // if node is a host, then just add its descendants
256: if (cto.isHost()) {
257: copyChildren(source, target, ix);
258: DefaultMutableTreeNode insertionPointNode = (DefaultMutableTreeNode) target
259: .getChildAt(ix);
260: scrollPathToVisible(new TreePath(insertionPointNode
261: .getPath()));
262: return true;
263: }
264: CommunityTreeObject newCTO = cto.copy();
265: DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(
266: newCTO, false);
267: if (isDuplicate(newNode, target)
268: || isInCommunity(newNode, target)) {
269: return false;
270: }
271: model.insertNodeInto(newNode, target, ix++);
272: // copy the source node's descendants, recursively
273: copyChildren(source, target, ix);
274: scrollPathToVisible(new TreePath(newNode.getPath()));
275: return true;
276: }
277:
278: /**
279: * Add an element by getting the user object out of the source node
280: * and creating a new tree node for it,
281: * and adding it as a child of the parent node;
282: * recurse for all the node's descendants, flattening the hierarchy,
283: * i.e. all nodes are added to the parentNode.
284: * nodes must be added "top-down" in order for the model listener
285: * to correctly add them to the database.
286: * Reject duplicates. Sibling nodes and nodes in the same community
287: * must be unique.
288: */
289: private void copyChildren(DefaultMutableTreeNode sourceNode,
290: DefaultMutableTreeNode parentNode, int insertionIndex) {
291: DefaultTreeModel model = (DefaultTreeModel) getModel();
292: int nChildren = sourceNode.getChildCount();
293: for (int i = 0; i < nChildren; i++) {
294: DefaultMutableTreeNode oldChildNode = (DefaultMutableTreeNode) sourceNode
295: .getChildAt(i);
296: CommunityTreeObject cto = (CommunityTreeObject) oldChildNode
297: .getUserObject();
298: CommunityTreeObject newCTO = cto.copy();
299: DefaultMutableTreeNode newChildNode = new DefaultMutableTreeNode(
300: newCTO, false);
301: if (isDuplicate(newChildNode, parentNode)
302: || isInCommunity(newChildNode, parentNode))
303: continue;
304: model.insertNodeInto(newChildNode, parentNode,
305: insertionIndex++);
306: copyChildren(oldChildNode, parentNode, insertionIndex);
307: }
308: }
309:
310: /**
311: * Return true if the given node is a duplicate of a node in the parent,
312: * i.e. has the same label.
313: */
314: private boolean isDuplicate(DefaultMutableTreeNode node,
315: DefaultMutableTreeNode parent) {
316: String s = node.getUserObject().toString();
317: int nChildren = parent.getChildCount();
318: for (int i = 0; i < nChildren; i++)
319: if (((DefaultMutableTreeNode) parent.getChildAt(i))
320: .getUserObject().toString().equals(s))
321: return true;
322: return false;
323: }
324:
325: /**
326: * Return true if the given node is already in the community to which
327: * it is being dragged;
328: * community is determined by tracing up the tree from the parent
329: * until the community is found.
330: */
331: private boolean isInCommunity(DefaultMutableTreeNode node,
332: DefaultMutableTreeNode parent) {
333: // find the community starting with parent
334: DefaultMutableTreeNode communityNode = null;
335: while (parent != null) {
336: CommunityTreeObject cto = (CommunityTreeObject) parent
337: .getUserObject();
338: if (cto.isCommunity()) {
339: communityNode = parent;
340: break;
341: }
342: parent = (DefaultMutableTreeNode) parent.getParent();
343: }
344: if (communityNode == null)
345: return false;
346: String nodeName = ((CommunityTreeObject) node.getUserObject())
347: .toString();
348: // get the descendants of the community
349: // and make sure they don't include this node
350: // TODO: you can't move a node within a community; is this a problem?
351: return isNodeNameInCommunity(communityNode, nodeName);
352: }
353:
354: /**
355: * Return true if the node name is in the community starting at node.
356: */
357: private boolean isNodeNameInCommunity(DefaultMutableTreeNode node,
358: String nodeName) {
359: int nChildren = node.getChildCount();
360: for (int i = 0; i < nChildren; i++) {
361: DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node
362: .getChildAt(i);
363: CommunityTreeObject cto = (CommunityTreeObject) childNode
364: .getUserObject();
365: if (cto.toString().equals(nodeName)) {
366: return true;
367: } else {
368: if (childNode.getChildCount() != 0
369: && !cto.isCommunity()) {
370: boolean result = isNodeNameInCommunity(childNode,
371: nodeName);
372: if (result)
373: return true;
374: }
375: }
376: }
377: return false;
378: }
379:
380: private void readObject(ObjectInputStream ois) throws IOException,
381: ClassNotFoundException {
382: ois.defaultReadObject();
383: createLogger();
384: }
385:
386: }
|