/*
* 03/21/2010
*
* Copyright (C) 2010 Robert Futrell
* robert_futrell at users.sourceforge.net
* http://fifesoft.com/rsyntaxtextarea
*
* This library is distributed under a modified BSD license. See the included
* RSTALanguageSupport.License.txt file for details.
*/
package org.fife.rsta.ac.java.tree;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Iterator;
import javax.swing.BorderFactory;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import org.fife.rsta.ac.AbstractSourceTree;
import org.fife.rsta.ac.LanguageSupport;
import org.fife.rsta.ac.LanguageSupportFactory;
import org.fife.rsta.ac.java.IconFactory;
import org.fife.rsta.ac.java.JavaLanguageSupport;
import org.fife.rsta.ac.java.JavaParser;
import org.fife.rsta.ac.java.rjc.ast.ASTNode;
import org.fife.rsta.ac.java.rjc.ast.CodeBlock;
import org.fife.rsta.ac.java.rjc.ast.CompilationUnit;
import org.fife.rsta.ac.java.rjc.ast.Field;
import org.fife.rsta.ac.java.rjc.ast.ImportDeclaration;
import org.fife.rsta.ac.java.rjc.ast.LocalVariable;
import org.fife.rsta.ac.java.rjc.ast.Member;
import org.fife.rsta.ac.java.rjc.ast.Method;
import org.fife.rsta.ac.java.rjc.ast.Package;
import org.fife.rsta.ac.java.rjc.ast.NormalClassDeclaration;
import org.fife.rsta.ac.java.rjc.ast.NormalInterfaceDeclaration;
import org.fife.rsta.ac.java.rjc.ast.TypeDeclaration;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
/**
* A tree view showing the outline of Java source, similar to the "Outline"
* view in the Eclipse JDT. It also uses Eclipse's icons, just like the rest
* of this code completion library.<p>
*
* You can get this tree automatically updating in response to edits in an
* <code>RSyntaxTextArea</code> with {@link JavaLanguageSupport} installed by
* calling {@link #listenTo(RSyntaxTextArea)}. Note that an instance of this
* class can only listen to a single editor at a time, so if your application
* contains multiple instances of RSyntaxTextArea, you'll either need a separate
* <code>JavaOutlineTree</code> for each one, or call <code>uninstall()</code>
* and <code>listenTo(RSyntaxTextArea)</code> each time a new RSTA receives
* focus.
*
* @author Robert Futrell
* @version 1.0
*/
public class JavaOutlineTree extends AbstractSourceTree {
private DefaultTreeModel model;
private RSyntaxTextArea textArea;
private JavaParser parser;
private Listener listener;
/**
* Constructor. The tree created will not have its elements sorted
* alphabetically.
*/
public JavaOutlineTree() {
this(false);
}
/**
* Constructor.
*
* @param sorted Whether the tree should sort its elements alphabetically.
* Note that outline trees will likely group nodes by type before
* sorting (i.e. methods will be sorted in one group, fields in
* another group, etc.).
*/
public JavaOutlineTree(boolean sorted) {
setSorted(sorted);
setBorder(BorderFactory.createEmptyBorder(0,8,0,8));
setRootVisible(false);
setCellRenderer(new AstTreeCellRenderer());
model = new DefaultTreeModel(new DefaultMutableTreeNode("Nothing"));
setModel(model);
listener = new Listener();
addTreeSelectionListener(listener);
}
/**
* Refreshes this tree.
*
* @param cu The parsed compilation unit. If this is <code>null</code>
* then the tree is cleared.
*/
private void update(CompilationUnit cu) {
JavaTreeNode root = new JavaTreeNode("Remove me!",
IconFactory.SOURCE_FILE_ICON);
root.setSortable(false);
if (cu==null) {
model.setRoot(root);
return;
}
Package pkg = cu.getPackage();
if (pkg!=null) {
String iconName = IconFactory.PACKAGE_ICON;
root.add(new JavaTreeNode(pkg, iconName, false));
}
if (!getShowMajorElementsOnly()) {
JavaTreeNode importNode = new JavaTreeNode("Imports",
IconFactory.IMPORT_ROOT_ICON);
for (Iterator i=cu.getImportIterator(); i.hasNext(); ) {
ImportDeclaration idec = (ImportDeclaration)i.next();
JavaTreeNode iNode = new JavaTreeNode(idec,
IconFactory.IMPORT_ICON);
importNode.add(iNode);
}
root.add(importNode);
}
for (Iterator i=cu.getTypeDeclarationIterator(); i.hasNext(); ) {
TypeDeclaration td = (TypeDeclaration)i.next();
TypeDeclarationTreeNode dmtn = createTypeDeclarationNode(td);
root.add(dmtn);
}
model.setRoot(root);
root.setSorted(isSorted());
refresh();
}
/**
* Refreshes listeners on the text area when its syntax style changes.
*/
private void checkForJavaParsing() {
// Remove possible listener on old Java parser (in case they're just
// changing syntax style AWAY from Java)
if (parser!=null) {
parser.removePropertyChangeListener(
JavaParser.PROPERTY_COMPILATION_UNIT, listener);
parser = null;
}
// Get the Java language support (shared by all RSTA instances editing
// Java that were registered with the LanguageSupportFactory).
LanguageSupportFactory lsf = LanguageSupportFactory.get();
LanguageSupport support = lsf.getSupportFor(SyntaxConstants.
SYNTAX_STYLE_JAVA);
JavaLanguageSupport jls = (JavaLanguageSupport)support;
// Listen for re-parsing of the editor, and update the tree accordingly
parser = jls.getParser(textArea);
if (parser!=null) { // Should always be true
parser.addPropertyChangeListener(
JavaParser.PROPERTY_COMPILATION_UNIT, listener);
// Populate with any already-existing CompilationUnit
CompilationUnit cu = parser.getCompilationUnit();
update(cu);
}
else {
update((CompilationUnit)null); // Clear the tree
}
}
private MemberTreeNode createMemberNode(Member member) {
MemberTreeNode node = null;
if (member instanceof CodeBlock) {
node = new MemberTreeNode((CodeBlock)member);
}
else if (member instanceof Field) {
node = new MemberTreeNode((Field)member);
}
else {
node = new MemberTreeNode((Method)member);
}
CodeBlock body = null;
if (member instanceof CodeBlock) {
body = (CodeBlock)member;
}
else if (member instanceof Method) {
body = ((Method)member).getBody();
}
if (body!=null && !getShowMajorElementsOnly()) {
for (int i=0; i<body.getLocalVarCount(); i++) {
LocalVariable var = body.getLocalVar(i);
LocalVarTreeNode varNode = new LocalVarTreeNode(var);
node.add(varNode);
}
}
return node;
}
private TypeDeclarationTreeNode createTypeDeclarationNode(
TypeDeclaration td) {
TypeDeclarationTreeNode dmtn = new TypeDeclarationTreeNode(td);
if (td instanceof NormalClassDeclaration) {
NormalClassDeclaration ncd = (NormalClassDeclaration)td;
for (int j=0; j<ncd.getChildTypeCount(); j++) {
TypeDeclaration td2 = ncd.getChildType(j);
TypeDeclarationTreeNode tdn = createTypeDeclarationNode(td2);
dmtn.add(tdn);
}
for (Iterator j=ncd.getMemberIterator(); j.hasNext(); ) {
dmtn.add(createMemberNode((Member)j.next()));
}
}
else if (td instanceof NormalInterfaceDeclaration) {
NormalInterfaceDeclaration nid = (NormalInterfaceDeclaration)td;
for (int j=0; j<nid.getChildTypeCount(); j++) {
TypeDeclaration td2 = nid.getChildType(j);
TypeDeclarationTreeNode tdn = createTypeDeclarationNode(td2);
dmtn.add(tdn);
}
for (Iterator j=nid.getMemberIterator(); j.hasNext(); ) {
dmtn.add(createMemberNode((Member)j.next()));
}
}
return dmtn;
}
/**
* {@inheritDoc}
*/
public void expandInitialNodes() {
// First, collapse all rows.
int j=0;
while (j<getRowCount()) {
collapseRow(j++);
}
// Expand only type declarations
expandRow(0);
j = 1;
while (j<getRowCount()) {
TreePath path = getPathForRow(j);
Object comp = path.getLastPathComponent();
if (comp instanceof TypeDeclarationTreeNode) {
expandPath(path);
}
j++;
}
}
private void gotoElementAtPath(TreePath path) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.
getLastPathComponent();
Object obj = node.getUserObject();
if (obj instanceof ASTNode) {
ASTNode astNode = (ASTNode)obj;
int start = astNode.getNameStartOffset();
int end = astNode.getNameEndOffset();
textArea.select(start, end);
}
}
/**
* {@inheritDoc}
*/
public boolean gotoSelectedElement() {
TreePath path = getLeadSelectionPath();//e.getNewLeadSelectionPath();
if (path != null) {
gotoElementAtPath(path);
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public void listenTo(RSyntaxTextArea textArea) {
if (this.textArea!=null) {
uninstall();
}
// Nothing new to listen to
if (textArea==null) {
return;
}
// Listen for future language changes in the text editor
this.textArea = textArea;
textArea.addPropertyChangeListener(
RSyntaxTextArea.SYNTAX_STYLE_PROPERTY, listener);
// Check whether we're currently editing Java
checkForJavaParsing();
}
/**
*{@inheritDoc}
*/
public void uninstall() {
if (parser!=null) {
parser.removePropertyChangeListener(
JavaParser.PROPERTY_COMPILATION_UNIT, listener);
parser = null;
}
if (textArea!=null) {
textArea.removePropertyChangeListener(
RSyntaxTextArea.SYNTAX_STYLE_PROPERTY, listener);
textArea = null;
}
}
/**
* Overridden to also update the UI of the child cell renderer.
*/
public void updateUI() {
super.updateUI();
// DefaultTreeCellRenderer caches colors, so we can't just call
// ((JComponent)getCellRenderer()).updateUI()...
setCellRenderer(new AstTreeCellRenderer());
}
/**
* Listens for events this tree is interested in (events in the associated
* editor, for example), as well as events in this tree.
*/
private class Listener implements PropertyChangeListener,
TreeSelectionListener {
/**
* Called whenever the text area's syntax style changes, as well as
* when it is re-parsed.
*/
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
// If the text area is changing the syntax style it is editing
if (RSyntaxTextArea.SYNTAX_STYLE_PROPERTY.equals(name)) {
checkForJavaParsing();
}
else if (JavaParser.PROPERTY_COMPILATION_UNIT.equals(name)) {
CompilationUnit cu = (CompilationUnit)e.getNewValue();
update(cu);
}
}
/**
* Selects the corresponding element in the text editor when a user
* clicks on a node in this tree.
*/
public void valueChanged(TreeSelectionEvent e) {
if (getGotoSelectedElementOnClick()) {
//gotoSelectedElement();
TreePath newPath = e.getNewLeadSelectionPath();
if (newPath!=null) {
gotoElementAtPath(newPath);
}
}
}
}
}