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: /*
043: * GraphHelper.java
044: *
045: * Created on January 29, 2007, 4:38 PM
046: *
047: * To change this template, choose Tools | Template Manager
048: * and open the template in the editor.
049: */
050:
051: package org.netbeans.modules.xml.refactoring.ui;
052:
053: import java.awt.Color;
054: import java.awt.Image;
055: import java.beans.BeanInfo;
056: import java.text.MessageFormat;
057: import java.util.ArrayList;
058: import java.util.HashMap;
059: import java.util.Hashtable;
060: import java.util.Iterator;
061: import java.util.List;
062: import java.util.Map;
063: import javax.swing.ImageIcon;
064: import org.netbeans.api.project.Project;
065: import org.netbeans.api.project.SourceGroup;
066: import org.netbeans.modules.refactoring.api.RefactoringElement;
067: import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
068: import org.netbeans.modules.refactoring.spi.SimpleRefactoringElementImplementation;
069: import org.netbeans.modules.refactoring.spi.ui.TreeElement;
070: import org.netbeans.modules.refactoring.spi.ui.TreeElementFactory;
071: import org.netbeans.modules.xml.nbprefuse.AnalysisConstants;
072: import org.netbeans.modules.xml.refactoring.FauxRefactoringElement;
073: import org.netbeans.modules.xml.refactoring.spi.AnalysisUtilities;
074: import org.netbeans.modules.xml.refactoring.spi.UIHelper;
075: import org.netbeans.modules.xml.refactoring.spi.SharedUtils;
076: import org.netbeans.modules.xml.xam.Component;
077: import org.netbeans.modules.xml.xam.Model;
078: import org.netbeans.modules.xml.xam.Named;
079: import org.netbeans.modules.xml.xam.Referenceable;
080: import org.netbeans.modules.xml.xam.dom.DocumentModel;
081: import org.openide.filesystems.FileObject;
082: import org.openide.filesystems.FileUtil;
083: import org.openide.util.NbBundle;
084: import org.openide.util.Utilities;
085: import prefuse.data.Edge;
086: import prefuse.data.Graph;
087: import prefuse.data.Node;
088:
089: /**
090: *
091: * @author Sonali
092: */
093: public class GraphHelper {
094:
095: private static final ImageIcon FIND_USAGES_ICON = new ImageIcon(
096: Utilities
097: .loadImage("org/netbeans/modules/refactoring/api/resources/"
098: + "findusages.png"));
099:
100: public static final prefuse.data.Schema FIND_USAGES_NODES_SCHEMA = new prefuse.data.Schema(); // prefuse graph schema
101: static {
102: FIND_USAGES_NODES_SCHEMA.addColumn(AnalysisConstants.LABEL,
103: String.class, AnalysisConstants.EMPTY_STRING);
104: FIND_USAGES_NODES_SCHEMA.addColumn(AnalysisConstants.ID,
105: String.class, AnalysisConstants.EMPTY_STRING);
106: FIND_USAGES_NODES_SCHEMA.addColumn(
107: AnalysisConstants.COMPONENT_TYPE_NAME, String.class,
108: AnalysisConstants.EMPTY_STRING);
109: FIND_USAGES_NODES_SCHEMA.addColumn(
110: AnalysisConstants.ELEMENT_TYPE, String.class,
111: AnalysisConstants.EMPTY_STRING);
112: // type of a GE, LE, GA, or LA
113:
114: FIND_USAGES_NODES_SCHEMA.addColumn(
115: AnalysisConstants.XML_FILENAME, String.class,
116: AnalysisConstants.EMPTY_STRING);
117: FIND_USAGES_NODES_SCHEMA.addColumn(
118: AnalysisConstants.FILE_OBJECT, FileObject.class);
119: FIND_USAGES_NODES_SCHEMA.addColumn(AnalysisConstants.FILE_TYPE,
120: String.class, AnalysisConstants.EMPTY_STRING);
121: FIND_USAGES_NODES_SCHEMA.addColumn(AnalysisConstants.TOOLTIP,
122: String.class, AnalysisConstants.EMPTY_STRING); // name
123: FIND_USAGES_NODES_SCHEMA.addColumn(
124: AnalysisConstants.USER_OBJECT, Object.class);
125: // FIND_USAGES_NODES_SCHEMA.addColumn(AnalysisConstants.OPENIDE_NODE,
126: // org.openide.nodes.Node.class);
127: FIND_USAGES_NODES_SCHEMA.addColumn(
128: AnalysisConstants.JAVA_AWT_IMAGE, Image.class);
129: FIND_USAGES_NODES_SCHEMA.addColumn(
130: AnalysisConstants.REFACTORING_ELEMENT,
131: RefactoringElement.class);
132:
133: // used to set node visible or not visible, depending on whether the
134: // file node is expanded or collapsed
135: // -1 = always visible, any other value matches a
136: // FILE_NODE_FILE_GROUP
137: // the file node itself has a FILE_GROUP of -1 because it is always
138: // visible
139: FIND_USAGES_NODES_SCHEMA.addColumn(
140: AnalysisConstants.FILE_GROUP, int.class, -1);
141: // a File Node has a positive value for this column, other nodes
142: // have a -1 value
143: FIND_USAGES_NODES_SCHEMA.addColumn(
144: AnalysisConstants.FILE_NODE_FILE_GROUP, int.class, -1);
145: // assigned number of schema file group
146:
147: FIND_USAGES_NODES_SCHEMA.addColumn(
148: AnalysisConstants.IS_PRIMITIVE, boolean.class, false); // is builtin type
149: FIND_USAGES_NODES_SCHEMA.addColumn(
150: AnalysisConstants.IS_QUERY_NODE, boolean.class, false);
151: FIND_USAGES_NODES_SCHEMA.addColumn(
152: AnalysisConstants.IS_USAGE_NODE, boolean.class, false);
153: FIND_USAGES_NODES_SCHEMA.addColumn(
154: AnalysisConstants.IS_FILE_NODE, boolean.class, false);
155: FIND_USAGES_NODES_SCHEMA.addColumn(
156: AnalysisConstants.IS_EXPANDED, boolean.class, false);
157: FIND_USAGES_NODES_SCHEMA.addColumn(AnalysisConstants.MOUSEOVER,
158: boolean.class, false);
159: }
160:
161: public static final prefuse.data.Schema FIND_USAGES_EDGES_SCHEMA = new prefuse.data.Schema(); // prefuse graph schema
162: static {
163: FIND_USAGES_EDGES_SCHEMA.addColumn(AnalysisConstants.LABEL,
164: String.class, AnalysisConstants.EMPTY_STRING);
165: FIND_USAGES_EDGES_SCHEMA.addColumn(AnalysisConstants.EDGE_TYPE,
166: String.class, AnalysisConstants.EMPTY_STRING);
167: // "file-edge-type" "generalization", "reference" "composition"
168:
169: FIND_USAGES_EDGES_SCHEMA.addColumn(AnalysisConstants.TOOLTIP,
170: String.class, AnalysisConstants.EMPTY_STRING);
171: // used to set edge visible or not visible, depending on whether the
172: // file node is expanded or collapsed
173: // -1 = always visible, any other value matches a
174: // FILE_NODE_FILE_GROUP
175: // the file node itself has a FILE_GROUP of -1 because it is
176: // always visible
177: FIND_USAGES_EDGES_SCHEMA.addColumn(
178: AnalysisConstants.FILE_GROUP, int.class, -1);
179: }
180:
181: public enum Type {
182: REFERENCE, GENERALIZATION;
183: }
184:
185: Graph graph;
186: Referenceable queryComponent;
187: boolean isPrimitive;
188:
189: /** Creates a new instance of GraphHelper */
190: public GraphHelper(Referenceable query) {
191: this .queryComponent = query;
192: }
193:
194: public Graph loadGraph(ArrayList<TreeElement> elements) {
195: graph = new Graph(true); // isDirected
196: graph.getNodeTable().addColumns(FIND_USAGES_NODES_SCHEMA);
197: graph.getEdgeTable().addColumns(FIND_USAGES_EDGES_SCHEMA);
198:
199: List componentsInGraph = new ArrayList();
200: List<FileObject> files = new ArrayList<FileObject>();
201: Map<FileObject, Node> fileNodes = new Hashtable<FileObject, Node>();
202: int fileGroupNumber = 0;
203: int usagesCount = 0;
204:
205: Node queryNode = createQueryNode(graph, queryComponent, false,
206: componentsInGraph, false, fileGroupNumber);
207:
208: // *****************************
209: // *** FILE NODE
210: // *****************************
211: // create file node and attach it to the query node
212: for (int i = 0; i < elements.size(); i++) {
213:
214: TreeElement usageNode = elements.get(i);
215:
216: //get the RefactoringElement
217: RefactoringElement usageElement = (RefactoringElement) usageNode
218: .getUserObject();
219:
220: //For the 0 usages case, we get a FauxTreeElement. Dont graph this map
221: //the only way of finding its a faux element is by looking at its composite object
222:
223: if (usageElement.getLookup().lookup(
224: SimpleRefactoringElementImplementation.class) != null) {
225: break;
226: }
227:
228: //First lets create a file node
229: //one file node per file object
230:
231: FileObject fo = usageElement.getParentFile();
232: Node parent = null;
233: if (!(files.contains(fo))) {
234: Node fileNode = createFileNode(graph, queryComponent,
235: fo, queryNode, ++fileGroupNumber);
236:
237: // create a special edge from the file node to the query node
238: // that will be visible when the file node is collapsed
239: // This edge uses the default prefuse renderer, e.g.,
240: // a small solid arrow head
241: // When the file node is expanded, this edge will be
242: // hidden. See FindUsagesFocusControl (double click)
243: Edge fileEdge = graph.addEdge(fileNode, queryNode);
244: fileEdge.setString(AnalysisConstants.EDGE_TYPE,
245: AnalysisConstants.FILE_EDGE_TYPE);
246: fileEdge.setInt(AnalysisConstants.FILE_GROUP,
247: fileGroupNumber);
248:
249: files.add(fo);
250: parent = fileNode;
251: fileNodes.put(fo, fileNode);
252: } else {
253: parent = fileNodes.get(fo);
254: }
255:
256: Node child = null;
257: TreeElement leaf = usageNode;
258:
259: while ((leaf.getParent(true)) instanceof TreeElement) {
260: Node pn = null;
261:
262: Object userObject = leaf.getUserObject();
263:
264: //we dont want to draw grap beyond the file unlike the Jtree that goes all the way to project
265: if ((userObject instanceof FileObject)) {
266: break;
267: }
268: if (componentsInGraph.contains(userObject)) {
269: //there's already a node for this
270: pn = findDup(graph, userObject);
271: assert pn != null : "Cannot find node for User Object"
272: + userObject;
273: }
274:
275: if (pn == null) {
276: pn = createNode(leaf, graph, componentsInGraph,
277: queryComponent, fileGroupNumber);
278: }
279:
280: //// To find out if this tree element is a usage node, we would need to check if there's a corresponding
281: //// refactoring element. only leaf nodes have corresponding refactoring elements
282: AnalysisUtilities.ToolTipLine topLine = null;
283: if (userObject instanceof RefactoringElement) {
284: pn
285: .setBoolean(
286: AnalysisConstants.IS_USAGE_NODE,
287: true);
288: pn.set(AnalysisConstants.REFACTORING_ELEMENT,
289: (RefactoringElement) userObject);
290:
291: topLine = new AnalysisUtilities.ToolTipLine(
292: (MessageFormat
293: .format(
294: NbBundle
295: .getMessage(
296: GraphHelper.class,
297: "LBL_Uses_Component"),
298: new Object[] { queryNode
299: .getString(AnalysisConstants.LABEL) })),
300: 100,
301: Color.BLACK.getRGB(),
302: AnalysisUtilities.ToolTipLine.HorizontalAlignment.CENTER);
303: // Connect this usage node to the Query Node
304: // with the appropriate edge (composition or reference)
305: Component obj = ((RefactoringElement) userObject)
306: .getLookup().lookup(Component.class);
307: if (obj != null)
308: addApppropriateEdge(graph, pn, queryNode,
309: fileGroupNumber, Type.REFERENCE);
310: else
311: addApppropriateEdge(graph, pn, queryNode,
312: fileGroupNumber, null);
313:
314: child = pn;
315: leaf = leaf.getParent(true);
316: continue;
317: }
318:
319: AnalysisUtilities.ToolTipLine typeLine = new AnalysisUtilities.ToolTipLine(
320: getCompTypeDisplayName(pn),
321: 100,
322: Color.BLACK.getRGB(),
323: AnalysisUtilities.ToolTipLine.HorizontalAlignment.CENTER);
324: String toolTip = AnalysisUtilities
325: .createHTMLToolTip(new AnalysisUtilities.ToolTipLine[] {
326: topLine, typeLine });
327: pn.setString(AnalysisConstants.TOOLTIP, toolTip);
328:
329: // connect it to its parent
330: addApppropriateEdge(graph, child, pn, fileGroupNumber,
331: null);
332: child = pn;
333: leaf = leaf.getParent(true);
334: }
335:
336: //Connect last node to file node
337: addApppropriateEdge(graph, child, parent, fileGroupNumber,
338: null);
339: }
340:
341: return graph;
342:
343: }
344:
345: private static Node createQueryNode(Graph graph,
346: Referenceable query, boolean isPrimitive,
347: List<Component> componentsInGraph,
348: boolean showOnlyDerivations, int fileGroupNumber) {
349:
350: Node queryNode = null;
351: Component queryComponent = null;
352: if (query instanceof Component) {
353: queryComponent = (Component) query;
354: } else {
355: if (query instanceof DocumentModel) {
356: queryComponent = ((DocumentModel) query)
357: .getRootComponent();
358: }
359: }
360: String name = "";
361: if (query instanceof Named) {
362: name = ((Named) Named.class.cast(query)).getName();
363: } else {
364: //TEMP hack :: Since I dont have access to UI helper, the only way to get the display node is to call
365: //the tree element factory
366: TreeElement elem = TreeElementFactory
367: .getTreeElement(queryComponent);
368: name = elem.getText(true);
369: }
370:
371: queryNode = graph.addNode();
372: queryNode.setBoolean(AnalysisConstants.IS_PRIMITIVE,
373: isPrimitive);
374: queryNode.setString(AnalysisConstants.LABEL, name);
375: queryNode.setString(AnalysisConstants.COMPONENT_TYPE_NAME, ""); //NOI18N
376: queryNode.set(AnalysisConstants.USER_OBJECT, query);
377: queryNode.setInt(AnalysisConstants.FILE_GROUP, fileGroupNumber);
378:
379: queryNode.setBoolean(AnalysisConstants.IS_QUERY_NODE, true);
380: // unset the FILE_GROUP because this node is always visible
381: queryNode.setInt(AnalysisConstants.FILE_GROUP, -1);
382: // reset IS_PRIMITIVE in case it is
383: queryNode.setBoolean(AnalysisConstants.IS_PRIMITIVE,
384: isPrimitive);
385:
386: AnalysisUtilities.ToolTipLine topLine = new AnalysisUtilities.ToolTipLine(
387: (showOnlyDerivations ? NbBundle.getMessage(
388: GraphHelper.class, "LBL_Base_Complex_Type")
389: : NbBundle.getMessage(GraphHelper.class,
390: "LBL_Query_Component")),
391: 100,
392: Color.BLACK.getRGB(),
393: AnalysisUtilities.ToolTipLine.HorizontalAlignment.CENTER);
394: String compType = getCompTypeDisplayName(queryNode);
395: AnalysisUtilities.ToolTipLine typeLine = new AnalysisUtilities.ToolTipLine(
396: compType,
397: 100,
398: Color.BLACK.getRGB(),
399: AnalysisUtilities.ToolTipLine.HorizontalAlignment.CENTER);
400: String toolTip = AnalysisUtilities
401: .createHTMLToolTip(new AnalysisUtilities.ToolTipLine[] {
402: topLine, typeLine });
403:
404: queryNode.setString(AnalysisConstants.TOOLTIP, toolTip);
405: return queryNode;
406: }
407:
408: private static Node createNode(TreeElement displayNode,
409: Graph graph, List componentsInGraph,
410: Referenceable queryComponent, int fileGroupNumber) {
411: Node n = graph.addNode();
412:
413: if (componentsInGraph != null) {
414: componentsInGraph.add(displayNode.getUserObject());
415: }
416:
417: n.setBoolean(AnalysisConstants.IS_PRIMITIVE, false);
418: n.setString(AnalysisConstants.LABEL, displayNode.getText(true));
419: n.set(AnalysisConstants.USER_OBJECT, displayNode
420: .getUserObject());
421: n.setInt(AnalysisConstants.FILE_GROUP, fileGroupNumber);
422:
423: if (displayNode.getIcon() != null) {
424: n.set(AnalysisConstants.JAVA_AWT_IMAGE,
425: ((ImageIcon) displayNode.getIcon()).getImage());
426: }
427:
428: return n;
429: }
430:
431: private static String getCompTypeDisplayName(final Node pn) {
432: String compType = null;
433: if (pn.canGetBoolean(AnalysisConstants.IS_PRIMITIVE)
434: && pn.getBoolean(AnalysisConstants.IS_PRIMITIVE)) {
435: compType = NbBundle.getMessage(GraphHelper.class,
436: "LBL_Primitive_Type");
437: } else {
438: compType = pn
439: .getString(AnalysisConstants.COMPONENT_TYPE_NAME);
440: }
441: return compType;
442: }
443:
444: /**
445: * Adds a Reference edge or Generalization edge from
446: * "from" node to the queryNode
447: * or adds composition edge if edgeType is null
448: *
449: */
450: private static void addApppropriateEdge(Graph graph, Node from,
451: Node queryNode, int fileGroupNumber, Type edgeType) {
452: Edge edge = graph.addEdge(from, queryNode);
453: // edge.setBoolean(AnalysisConstants.SHOW, isVisible);
454: edge.setInt(AnalysisConstants.FILE_GROUP, Integer
455: .valueOf(fileGroupNumber));
456: if (edgeType == Type.GENERALIZATION) {
457: edge.setString(AnalysisConstants.EDGE_TYPE,
458: AnalysisConstants.GENERALIZATION);
459: } else if (edgeType == Type.REFERENCE) {
460: edge.setString(AnalysisConstants.EDGE_TYPE,
461: AnalysisConstants.REFERENCE);
462: from
463: .setString(
464: AnalysisConstants.LABEL,
465: MessageFormat
466: .format(
467: NbBundle
468: .getMessage(
469: GraphHelper.class,
470: "LBL_References_Ref"),
471: new Object[] { queryNode
472: .getString(AnalysisConstants.LABEL) }));
473: } else {
474: edge.setString(AnalysisConstants.EDGE_TYPE,
475: AnalysisConstants.COMPOSITION);
476: }
477: }
478:
479: private static Node createFileNode(Graph graph,
480: Referenceable queryComp, FileObject fobj, Node queryNode,
481: int fileGroupNumber) {
482: if (queryComp == null || fobj == null) {
483: return null;
484: }
485: String fileType = SharedUtils.getXmlFileType(fobj);
486: Node n = graph.addNode();
487: n.setString(AnalysisConstants.FILE_TYPE, fileType);
488: n.setInt(AnalysisConstants.FILE_NODE_FILE_GROUP,
489: fileGroupNumber);
490: n.setBoolean(AnalysisConstants.IS_EXPANDED, false);
491: n.setBoolean(AnalysisConstants.IS_FILE_NODE, true);
492: n.set(AnalysisConstants.JAVA_AWT_IMAGE, SharedUtils
493: .getImage(fobj));
494: if (fobj != null) {
495: n.setString(AnalysisConstants.LABEL, fobj.getName() + "."
496: + fobj.getExt()); // NOI18N
497: n.setString(AnalysisConstants.XML_FILENAME, fobj.getName()
498: + "." + fobj.getExt()); // NOI18N
499: n.set(AnalysisConstants.FILE_OBJECT, fobj);
500:
501: // "Schema file containing usages of XYZ"
502: AnalysisUtilities.ToolTipLine topLine = new AnalysisUtilities.ToolTipLine(
503: MessageFormat
504: .format(
505: NbBundle.getMessage(
506: GraphHelper.class,
507: "LBL_Xml_File_With_Usages"),
508: new Object[] {
509: SharedUtils
510: .getXmlFileTypeDisplayName(fileType),
511: queryNode
512: .getString(AnalysisConstants.LABEL) }),
513: 100,
514: Color.BLACK.getRGB(),
515: AnalysisUtilities.ToolTipLine.HorizontalAlignment.CENTER);
516: AnalysisUtilities.ToolTipLine typeLine = new AnalysisUtilities.ToolTipLine(
517: FileUtil.getFileDisplayName(fobj),
518: 100,
519: Color.BLACK.getRGB(),
520: AnalysisUtilities.ToolTipLine.HorizontalAlignment.CENTER);
521: String toolTip = AnalysisUtilities
522: .createHTMLToolTip(new AnalysisUtilities.ToolTipLine[] {
523: topLine, typeLine });
524:
525: n.setString(AnalysisConstants.TOOLTIP, toolTip);
526: }
527: return n;
528: }
529:
530: private static Node findDup(Graph graph, Object sc) {
531: Iterator it = graph.nodes();
532: while (it.hasNext()) {
533: Node n = Node.class.cast(it.next());
534: Object nodeSC = n.get(AnalysisConstants.USER_OBJECT);
535: if ((n.canGetBoolean(AnalysisConstants.IS_FILE_NODE) && n
536: .getBoolean(AnalysisConstants.IS_FILE_NODE) == false)
537: && nodeSC != null) {
538: if (nodeSC == sc) {
539: return n;
540: }
541: }
542: }
543: return null;
544: }
545:
546: }
|