001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.visualweb.gravy.nodes;
043:
044: import java.awt.Component;
045: import java.awt.event.KeyEvent;
046: import java.util.ArrayList;
047: import javax.swing.tree.TreePath;
048: import org.netbeans.jellytools.Bundle;
049: import org.netbeans.modules.visualweb.gravy.actions.Action;
050: import org.netbeans.modules.visualweb.gravy.actions.ActionNoBlock;
051: import org.netbeans.jemmy.*;
052: import org.netbeans.jemmy.operators.*;
053: import org.openide.explorer.view.Visualizer;
054:
055: /** Ancestor class for all nodes.<p>
056: * Nodes should help to easier testing of JTree's.
057: * The most frequent usage in IDE is in the Explorer Window but nodes can be
058: * used in any component which includes a JTree instance.
059: * Nodes are also used as parameters for action's performing.
060: * <p>
061: * Example:<p>
062: * <pre>
063: * Node node = new Node(RepositoryTabOperator.invoke().getRootNode(), "jellytools/src|org|netbeans|jellytools");
064: * System.out.println(node.getText());
065: * new NewTemplateAction().performAPI(node);
066: * </pre>
067: */
068: public class Node {
069:
070: static final String linkSuffix = Bundle.getString(
071: "org.openide.loaders.Bundle", "FMT_shadowName",
072: new String[] { "" });
073:
074: /** JTreeOperator of tree where node lives */
075: protected JTreeOperator treeOperator;
076: /** TreePath of node */
077: protected TreePath treePath;
078: /** Comparator used for this node instance. */
079: private Operator.StringComparator comparator;
080:
081: /** creates new Node instance
082: * @param treeOperator JTreeOperator of tree where node lives
083: * @param treePath String tree path of node */
084: public Node(JTreeOperator treeOperator, String treePath) {
085: this (treeOperator, treeOperator.findPath(treePath, "|"));
086: }
087:
088: /** creates new Node instance
089: * @param treeOperator JTreeOperator of tree where node lives
090: * @param treePath String tree path of node
091: * @param indexes String list of indexes of nodes in each level */
092: public Node(JTreeOperator treeOperator, String treePath,
093: String indexes) {
094: this (treeOperator, treeOperator
095: .findPath(treePath, indexes, "|"));
096: }
097:
098: /** creates new Node instance
099: * @param parent parent Node
100: * @param treeSubPath String tree sub-path from parent */
101: public Node(Node parent, String treeSubPath) {
102: this (parent.tree(), parent.findSubPath(treeSubPath, "|"));
103: }
104:
105: /** creates new Node instance
106: * @param parent parent Node
107: * @param childIndex int index of child under parent node */
108: public Node(Node parent, int childIndex) {
109: this (parent.tree(), parent.tree().getChildPath(
110: parent.getTreePath(), childIndex));
111: }
112:
113: /** creates new Node instance
114: * @param treeOperator JTreeOperator of tree where node lives
115: * @param path TreePath of node */
116: public Node(JTreeOperator treeOperator, TreePath path) {
117: this .treeOperator = treeOperator;
118: this .treePath = path;
119: }
120:
121: /** Sets comparator fot this node. Comparator is used for all methods
122: * after this method is called.
123: * @param comparator new comparator to be set (e.g.
124: * new Operator.DefaultStringComparator(true, true);
125: * to search string item exactly and case sensitive)
126: */
127: public void setComparator(Operator.StringComparator comparator) {
128: this .comparator = comparator;
129: tree().setComparator(comparator);
130: }
131:
132: /** Gets comparator for this node instance.
133: * @return comparator for this node instance.
134: */
135: public Operator.StringComparator getComparator() {
136: if (comparator == null) {
137: comparator = tree().getComparator();
138: }
139: return comparator;
140: }
141:
142: /** Getter for JTreeOperator of tree where node lives
143: * @return JTreeOperator of tree where node lives */
144: public JTreeOperator tree() {
145: return (treeOperator);
146: }
147:
148: /** Getter for TreePath of node.
149: * @return TreePath of node */
150: public TreePath getTreePath() {
151: return (treePath);
152: }
153:
154: /** Getter for node text
155: * @return String node text */
156: public String getText() {
157: return (treePath.getLastPathComponent().toString());
158: }
159:
160: /** Convert TreePath to string
161: * @return String TreePath converted to string */
162: private static String convertPath(TreePath path) {
163: if (path == null)
164: return null;
165: int pathCount = path.getPathCount();
166: if (pathCount < 2)
167: return "";
168: String result = path.getPathComponent(1).toString();
169: for (int i = 2; i < pathCount; i++) {
170: result += "|" + path.getPathComponent(i).toString();
171: }
172: return result;
173: }
174:
175: /** Getter for node path
176: * @return String node path */
177: public String getPath() {
178: return convertPath(treePath);
179: }
180:
181: /** Getter for path of parent node
182: * @return String path of parent node */
183: public String getParentPath() {
184: return convertPath(treePath.getParentPath());
185: }
186:
187: /** Returns Object instance which represents org.openide.nodes.Node
188: * for this jellytools node.
189: * @return Object instance which represents org.openide.nodes.Node
190: */
191: public Object getOpenideNode() {
192: return Visualizer.findNode(this .getTreePath()
193: .getLastPathComponent());
194: }
195:
196: /** calls popup menu on node
197: * @return JPopupMenuOperator */
198: public JPopupMenuOperator callPopup() {
199: return new JPopupMenuOperator(treeOperator
200: .callPopupOnPath(treePath));
201: }
202:
203: /** performs action on node through main menu
204: * @param menuPath main menu path of action */
205: public void performMenuAction(String menuPath) {
206: new Action(menuPath, null).performMenu(this );
207: }
208:
209: /** performs action on node through popup menu
210: * @param popupPath popup menu path of action */
211: public void performPopupAction(String popupPath) {
212: new Action(null, popupPath).performPopup(this );
213: }
214:
215: /** performs action on node through API menu
216: * @param systemActionClass String class name of SystemAction (use null value if API mode is not supported) */
217: public void performAPIAction(String systemActionClass) {
218: new Action(null, null, systemActionClass).performAPI(this );
219: }
220:
221: /** performs action on node through main menu
222: * @param menuPath main menu path of action */
223: public void performMenuActionNoBlock(String menuPath) {
224: new ActionNoBlock(menuPath, null).performMenu(this );
225: }
226:
227: /** performs action on node through popup menu
228: * @param popupPath popup menu path of action */
229: public void performPopupActionNoBlock(String popupPath) {
230: new ActionNoBlock(null, popupPath).performPopup(this );
231: }
232:
233: /** performs action on node through API menu
234: * @param systemActionClass String class name of SystemAction (use null value if API mode is not supported) */
235: public void performAPIActionNoBlock(String systemActionClass) {
236: new ActionNoBlock(null, null, systemActionClass)
237: .performAPI(this );
238: }
239:
240: /**
241: * Selects node.
242: */
243: public void select() {
244: tree().selectPath(getTreePath());
245: // sleep to workaround IDE's behavior. IDE consider as double click
246: // two single clicks on the same position with delay shorter than 300 ms.
247: // See org.openide.awt.MouseUtils.isDoubleClick().
248: try {
249: Thread.sleep(300);
250: } catch (Exception e) {
251: throw new JemmyException("Sleeping interrupted", e);
252: }
253: }
254:
255: /** adds node into set of selected nodes */
256: public void addSelectionPath() {
257: tree().addSelectionPath(getTreePath());
258: }
259:
260: /** tests if node is leaf
261: * @return boolean true when node does not have children */
262: public boolean isLeaf() {
263: return tree().getChildCount(treePath) < 1;
264: }
265:
266: /** returns list of names of children
267: * @return String[] list of names of children */
268: public String[] getChildren() {
269: tree().expandPath(treePath);
270: Object o[] = tree()
271: .getChildren(treePath.getLastPathComponent());
272: if (o == null)
273: return new String[0];
274: String s[] = new String[o.length];
275: for (int i = 0; i < o.length; i++)
276: s[i] = o[i].toString();
277: return s;
278: }
279:
280: /** determines if current node is link
281: * @return boolean true if node is link */
282: public boolean isLink() {
283: return getText().endsWith(linkSuffix);
284: }
285:
286: /** verifies if node is still present. It expands parent path of the node
287: * during verification.
288: * @return boolean true when node is still present */
289: public boolean isPresent() {
290: tree().expandPath(treePath.getParentPath());
291: return tree().getRowForPath(treePath) >= 0;
292: }
293:
294: /** verifies node's popup path for presence (without invocation)
295: * @param popupPath String popup path */
296: public void verifyPopup(String popupPath) {
297: verifyPopup(new String[] { popupPath });
298: }
299:
300: /** verifies node's popup paths for presence (without invocation)
301: * @param popupPaths String[] popup paths
302: */
303: public void verifyPopup(String[] popupPaths) {
304: //invocation of root popup
305: final JPopupMenuOperator popup = callPopup();
306: for (int i = 0; i < popupPaths.length; i++) {
307: try {
308: popup.showMenuItem(popupPaths[i], "|");
309: } catch (NullPointerException npe) {
310: throw new JemmyException("Popup path [" + popupPaths[i]
311: + "] not found.");
312: }
313: }
314: //closing popup
315: popup.waitState(new ComponentChooser() {
316: public boolean checkComponent(Component comp) {
317: try {
318: popup.pushKey(KeyEvent.VK_ESCAPE);
319: return false;
320: } catch (JemmyException e) {
321: }
322: return true;
323: }
324:
325: public String getDescription() {
326: return "Popup menu closer";
327: }
328: });
329: }
330:
331: class StringArraySubPathChooser implements
332: JTreeOperator.TreePathChooser {
333: String[] arr;
334: int[] indices;
335: JTreeOperator.StringComparator comparator;
336: TreePath parentPath;
337: int parentPathCount;
338:
339: StringArraySubPathChooser(TreePath parentPath, String[] arr,
340: int[] indices, JTreeOperator.StringComparator comparator) {
341: this .arr = arr;
342: this .comparator = comparator;
343: this .indices = indices;
344: this .parentPath = parentPath;
345: this .parentPathCount = parentPath.getPathCount();
346: }
347:
348: /** implementation of JTreeOperator.TreePathChooser
349: * @param path TreePath
350: * @param indexInParent int
351: * @return boolean */
352: public boolean checkPath(TreePath path, int indexInParent) {
353: return (path.getPathCount() == arr.length + parentPathCount && hasAsParent(
354: path, indexInParent));
355: }
356:
357: /** implementation of JTreeOperator.TreePathChooser
358: * @param path TreePath
359: * @param indexInParent int
360: * @return boolean */
361: public boolean hasAsParent(TreePath path, int indexInParent) {
362: if (path.getPathCount() <= parentPathCount)
363: return path.isDescendant(parentPath);
364: if (arr.length + parentPathCount < path.getPathCount()) {
365: return (false);
366: }
367: if (indices.length >= path.getPathCount() - parentPathCount
368: && indices[path.getPathCount() - parentPathCount
369: - 1] != indexInParent) {
370: return (false);
371: }
372: Object[] comps = path.getPath();
373: for (int i = parentPathCount; i < comps.length; i++) {
374: if (!comparator.equals(comps[i].toString(), arr[i
375: - parentPathCount])) {
376: return (false);
377: }
378: }
379: return (true);
380: }
381:
382: /** implementation of JTreeOperator.TreePathChooser
383: * @return String description */
384: public String getDescription() {
385: String desc = "";
386: Object parr[] = parentPath.getPath();
387: for (int i = 0; i < parr.length; i++) {
388: desc = desc + parr[i].toString() + ", ";
389: }
390: for (int i = 0; i < arr.length; i++) {
391: desc = desc + arr[i] + ", ";
392: }
393: if (desc.length() > 0) {
394: desc = desc.substring(0, desc.length() - 2);
395: }
396: return ("[ " + desc + " ]");
397: }
398:
399: }
400:
401: TreePath findSubPath(String subPath, String delimiter) {
402: return findSubPath(subPath, "", delimiter);
403: }
404:
405: TreePath findSubPath(String subPath, String indexes,
406: String delimiter) {
407: JTreeOperator o = tree();
408: String indexStr[] = o.parseString(indexes, delimiter);
409: int indexInt[] = new int[indexStr.length];
410: for (int i = 0; i < indexStr.length; i++)
411: indexInt[i] = Integer.parseInt(indexStr[i]);
412: return o.findPath(new Node.StringArraySubPathChooser(treePath,
413: o.parseString(subPath, delimiter), indexInt,
414: getComparator()));
415: }
416:
417: /** Expands current node to see children */
418: public void expand() {
419: treeOperator.expandPath(treePath);
420: waitExpanded();
421: }
422:
423: /** Collapse current node to hide children */
424: public void collapse() {
425: treeOperator.collapsePath(treePath);
426: waitCollapsed();
427: }
428:
429: /** Waits for node to be expanded */
430: public void waitExpanded() {
431: treeOperator.waitExpanded(treePath);
432: }
433:
434: /** Waits for node to be collapsed */
435: public void waitCollapsed() {
436: treeOperator.waitCollapsed(treePath);
437: }
438:
439: /** Informs if current node is expanded
440: * @return boolean true when node is expanded
441: */
442: public boolean isExpanded() {
443: return treeOperator.isExpanded(treePath);
444: }
445:
446: /** Informs if current node is collapsed
447: * @return boolean true when node is collapsed
448: */
449: public boolean isCollapsed() {
450: return treeOperator.isCollapsed(treePath);
451: }
452:
453: /*protected Action[] getActions() {
454: return null;
455: }
456:
457: public boolean hasAction(Class actionClass) {
458: Action actions[] = getActions();
459: for (int i=0; actions!=null && i<actions.length; i++)
460: if (actionClass.equals(actions[i].getClass()))
461: return true;
462: return false;
463: }*/
464:
465: /** verifies node's popup paths (of all actions) for presence (without invocation)
466: * @param actions array of actions to be verified
467: */
468: public void verifyPopup(Action actions[]) {
469: ArrayList popupPaths = new ArrayList();
470: String path;
471: for (int i = 0; i < actions.length; i++) {
472: path = actions[i].getPopupPath();
473: if (path != null) {
474: popupPaths.add(path);
475: }
476: }
477: verifyPopup((String[]) popupPaths.toArray(new String[0]));
478: }
479:
480: /** Checks whether child with specified name is present under this node.
481: * @param childName name of child node
482: * @return true if child is present; false otherwise
483: */
484: public boolean isChildPresent(String childName) {
485: String[] children = this .getChildren();
486: for (int i = 0; i < children.length; i++) {
487: if (getComparator().equals(children[i], childName)) {
488: return true;
489: }
490: }
491: return false;
492: }
493:
494: /** Waits until child with specified name is not present under this node.
495: * It can throw TimeoutExpiredException, if child is still present.
496: * @param childName name of child node
497: */
498: public void waitChildNotPresent(final String childName) {
499: try {
500: new Waiter(new Waitable() {
501: public Object actionProduced(Object anObject) {
502: return isChildPresent(childName) ? null
503: : Boolean.TRUE;
504: }
505:
506: public String getDescription() {
507: return ("Child \"" + childName
508: + "\" not present under parent \""
509: + getPath() + "\"");
510: }
511: }).waitAction(null);
512: } catch (InterruptedException e) {
513: throw new JemmyException("Interrupted.", e);
514: }
515: }
516:
517: /** Waits until this node is no longer present.
518: * It can throw TimeoutExpiredException, if the node is still present.
519: */
520: public void waitNotPresent() {
521: try {
522: new Waiter(new Waitable() {
523: public Object actionProduced(Object anObject) {
524: return isPresent() ? null : Boolean.TRUE;
525: }
526:
527: public String getDescription() {
528: return ("Wait node " + getPath() + " not present.");
529: }
530: }).waitAction(null);
531: } catch (InterruptedException e) {
532: throw new JemmyException("Interrupted.", e);
533: }
534: }
535: }
|