/*******************************************************************************
* Copyright (c) 2007 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Robert Fuhrer (rfuhrer@watson.ibm.com) - initial API and implementation
*******************************************************************************/
package com.redhat.ceylon.eclipse.code.outline;
import static com.redhat.ceylon.compiler.typechecker.tree.TreeUtil.formatPath;
import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getImageKeyForNode;
import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getNodeDecorationAttributes;
import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getStyledLabelForNode;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.compileToJava;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.compileToJs;
import static com.redhat.ceylon.eclipse.util.EditorUtil.getCurrentEditor;
import static com.redhat.ceylon.eclipse.util.Nodes.findNode;
import static com.redhat.ceylon.eclipse.util.Nodes.findStatement;
import static com.redhat.ceylon.eclipse.util.Nodes.getIdentifyingNode;
import static java.lang.System.identityHashCode;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.swt.graphics.Font;
import org.eclipse.ui.IActionFilter;
import org.eclipse.ui.IEditorPart;
import com.redhat.ceylon.common.Backends;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.TreeUtil;
import com.redhat.ceylon.eclipse.code.editor.CeylonEditor;
import com.redhat.ceylon.ide.common.model.SourceFile;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Unit;
public class CeylonOutlineNode implements IAdaptable {
private static final class ActionFilter implements IActionFilter {
@Override
public boolean testAttribute(Object object, String name, String value) {
CeylonOutlineNode target = (CeylonOutlineNode) object;
boolean result = false;
if (target.runnable) {
if (name.equals("javaRunnable")) {
IResource resource = target.getResource();
result = resource!=null &&
compileToJava(resource.getProject());
}
else if (name.equals("jsRunnable")) {
IResource resource = target.getResource();
result = resource!=null &&
compileToJs(resource.getProject());
}
else {
return false;
}
}
if (value.equals("true")) {
return result;
}
else if (value.equals("false")) {
return !result;
}
else {
return false;
}
}
}
public static final int ROOT_CATEGORY = -4;
public static final int PACKAGE_CATEGORY = -3;
public static final int UNIT_CATEGORY = -2;
public static final int IMPORT_LIST_CATEGORY = -1;
public static final int DEFAULT_CATEGORY = 0;
private final List<CeylonOutlineNode> children =
new ArrayList<CeylonOutlineNode>();
private CeylonOutlineNode parent;
private final int category;
private final String id;
private IResource resource;
private boolean declaration;
private boolean shared;
private int startOffset;
private int endOffset;
private String imageKey;
private StyledString label;
private int decorations;
private String name;
private int realStartOffset;
private int realEndOffset;
private boolean runnable;
CeylonOutlineNode(Node treeNode) {
this(treeNode, DEFAULT_CATEGORY);
}
CeylonOutlineNode(Node treeNode, int category) {
this(treeNode, null, category);
}
CeylonOutlineNode(Node treeNode, CeylonOutlineNode parent) {
this(treeNode, parent, DEFAULT_CATEGORY);
}
CeylonOutlineNode(Node treeNode, CeylonOutlineNode parent,
int category) {
this(treeNode, parent, category, null);
}
CeylonOutlineNode(Node treeNode, int category, IResource resource) {
this(treeNode, null, category, resource);
}
CeylonOutlineNode(Node treeNode, CeylonOutlineNode parent,
int category, IResource resource) {
this.parent = parent;
this.category = category;
this.resource = resource;
id = createIdentifier(treeNode);
declaration = treeNode instanceof Tree.Declaration;
if (declaration) {
Tree.Declaration treeDeclaration =
(Tree.Declaration) treeNode;
Declaration model =
treeDeclaration.getDeclarationModel();
if (model!=null) {
shared = model.isShared();
}
Tree.Identifier identifier =
treeDeclaration.getIdentifier();
name = identifier==null ?
null : identifier.getText();
}
else if (treeNode instanceof Tree.SpecifierStatement) {
Tree.SpecifierStatement treeSpecifier =
(Tree.SpecifierStatement) treeNode;
Tree.Identifier id =
getIdentifier(treeSpecifier);
name = id==null ? null : id.getText();
shared = false;
declaration = true;
}
else {
shared = true;
}
if (category==DEFAULT_CATEGORY) {
//span of the "identifying" node
Node identifyingNode = getIdentifyingNode(treeNode);
if (identifyingNode!=null) {
startOffset = identifyingNode.getStartIndex();
endOffset = identifyingNode.getEndIndex();
}
}
if (treeNode!=null &&
!(treeNode instanceof PackageNode) &&
!(treeNode instanceof ModuleNode)) {
//whole span of the complete construct
realStartOffset = treeNode.getStartIndex();
realEndOffset = treeNode.getEndIndex();
}
label = getStyledLabelForNode(treeNode);
imageKey = getImageKeyForNode(treeNode);
decorations = getNodeDecorationAttributes(treeNode);
if (shared && treeNode instanceof Tree.AnyMethod) {
Tree.AnyMethod am = (Tree.AnyMethod) treeNode;
List<Tree.ParameterList> lists =
am.getParameterLists();
runnable =
lists.size()==1 &&
lists.get(0).getParameters().isEmpty();
}
else if (shared && treeNode instanceof Tree.AnyClass) {
Tree.AnyClass ac = (Tree.AnyClass) treeNode;
Tree.ParameterList list = ac.getParameterList();
runnable =
list!=null &&
list.getParameters().isEmpty();
}
else {
runnable = false;
}
}
private Tree.Identifier getIdentifier(Tree.SpecifierStatement treeNode) {
Tree.Term t = treeNode.getBaseMemberExpression();
if (t instanceof Tree.BaseMemberExpression) {
Tree.BaseMemberExpression bme =
(Tree.BaseMemberExpression) t;
return bme.getIdentifier();
}
else if (t instanceof Tree.ParameterizedExpression) {
Tree.ParameterizedExpression pe =
(Tree.ParameterizedExpression) t;
Tree.BaseMemberExpression bme =
(Tree.BaseMemberExpression)
pe.getPrimary();
return bme.getIdentifier();
}
else {
throw new RuntimeException("unexpected node type");
}
}
void addChild(CeylonOutlineNode child) {
children.add(child);
}
public List<CeylonOutlineNode> getChildren() {
return children;
}
public CeylonOutlineNode getParent() {
return parent;
}
public boolean isShared() {
return shared;
}
public String getName() {
return name;
}
public int getCategory() {
return category;
}
public boolean isDeclaration() {
return declaration;
}
public int getStartOffset() {
return startOffset;
}
public int getEndOffset() {
return endOffset;
}
public int getRealStartOffset() {
return realStartOffset;
}
public int getRealEndOffset() {
return realEndOffset;
}
String getImageKey() {
return imageKey;
}
StyledString getLabel() {
return getLabel(null, null);
}
StyledString getLabel(String prefix, Font font) {
if (category==DEFAULT_CATEGORY && declaration) {
IEditorPart currentEditor = getCurrentEditor();
if (currentEditor instanceof CeylonEditor) {
CeylonEditor ce =
(CeylonEditor) currentEditor;
Tree.CompilationUnit rootNode =
ce.getParseController()
.getTypecheckedRootNode();
if (rootNode!=null) {
Node node =
findNode(rootNode,
startOffset, endOffset);
if (!(node instanceof Tree.Declaration)) {
node = findStatement(rootNode, node);
}
if (node!=null &&
node.getStartIndex()==realStartOffset &&
node.getEndIndex()==realEndOffset) {
return getStyledLabelForNode(node, prefix, font);
}
}
}
}
return label;
}
int getDecorations() {
if (category==DEFAULT_CATEGORY && declaration) {
IEditorPart currentEditor = getCurrentEditor();
if (currentEditor instanceof CeylonEditor) {
CeylonEditor ce =
(CeylonEditor) currentEditor;
Tree.CompilationUnit rootNode =
ce.getParseController()
.getTypecheckedRootNode();
if (rootNode!=null) {
Node node =
findNode(rootNode,
startOffset, endOffset);
if (!(node instanceof Tree.Declaration)) {
node = findStatement(rootNode, node);
}
if (node!=null) {
return getNodeDecorationAttributes(node);
}
}
}
}
return decorations;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CeylonOutlineNode) {
CeylonOutlineNode that = (CeylonOutlineNode) obj;
return that.id.equals(id);
}
else {
return false;
}
}
@Override
public int hashCode() {
return getIdentifier().hashCode();
}
public String getIdentifier() {
return id;
}
public String createIdentifier(Node treeNode) {
try {
//note: we actually have two different outline
// nodes that both represent the same
// tree node, so we need to use the
// category to distinguish them!
switch (category) {
case ROOT_CATEGORY:
String path;
Unit unit = treeNode.getUnit();
if (unit instanceof SourceFile) {
SourceFile sf = (SourceFile) unit;
path = sf.getRelativePath();
}
else {
path = unit.getFilename();
}
return "@root:" + path;
case PACKAGE_CATEGORY:
if (treeNode instanceof PackageNode) {
PackageNode pn = (PackageNode) treeNode;
String packageName = pn.getPackageName();
return "@package:" + packageName;
}
else {
ModuleNode mn = (ModuleNode) treeNode;
String moduleName = mn.getModuleName();
return "@module:" + moduleName;
}
case UNIT_CATEGORY:
return "@unit:" +
treeNode.getUnit().getFilename();
case IMPORT_LIST_CATEGORY:
return "@importlist:" +
treeNode.getUnit().getFilename();
case DEFAULT_CATEGORY:
default:
if (treeNode instanceof Tree.Import) {
Tree.Import imp = (Tree.Import) treeNode;
Tree.ImportPath packageName =
imp.getImportPath();
return "@import:" +
pathToName(packageName, treeNode);
}
else if (treeNode instanceof Tree.Declaration) {
Tree.Declaration dec =
(Tree.Declaration) treeNode;
Tree.Identifier id =
dec.getIdentifier();
Backends backends =
TreeUtil.getNativeBackend(
dec.getAnnotationList(),
dec.getUnit());
String name = id==null ?
String.valueOf(identityHashCode(treeNode)) :
id.getText();
if (backends != null && ! backends.none()) {
name = new StringBuilder(name)
.append("(")
.append(backends.names())
.append(")")
.toString();
}
if (parent!=null && parent.isDeclaration()) {
return getParent().getIdentifier()
+ ":" + name;
}
else {
return "@declaration:" + name;
}
}
else if (treeNode instanceof Tree.ImportModule) {
Tree.ImportModule im =
(Tree.ImportModule) treeNode;
Tree.QuotedLiteral ql = im.getQuotedLiteral();
if (ql!=null) {
return "@importmodule:" + ql.getText();
}
else {
Tree.ImportPath moduleName = im.getImportPath();
return "@importmodule:" +
pathToName(moduleName, treeNode);
}
}
else if (treeNode instanceof Tree.ModuleDescriptor) {
Tree.ModuleDescriptor md =
(Tree.ModuleDescriptor) treeNode;
Tree.ImportPath moduleName = md.getImportPath();
return "@moduledescriptor:" +
pathToName(moduleName, treeNode);
}
else if (treeNode instanceof Tree.PackageDescriptor) {
Tree.PackageDescriptor pd =
(Tree.PackageDescriptor) treeNode;
Tree.ImportPath packageName = pd.getImportPath();
return "@packagedescriptor:" +
pathToName(packageName, treeNode);
}
else if (treeNode instanceof Tree.SpecifierStatement) {
Tree.SpecifierStatement ss =
(Tree.SpecifierStatement) treeNode;
Tree.Identifier id = getIdentifier(ss);
String name = id==null ?
String.valueOf(identityHashCode(treeNode)) :
id.getText();
if (parent!=null && parent.isDeclaration()) {
return getParent().getIdentifier() + ":" + name;
}
else {
return "@declaration:" + name;
}
}
else {
throw new RuntimeException("unexpected node type");
}
}
}
catch (RuntimeException re) {
re.printStackTrace();
return "";
}
}
private static String pathToName(Tree.ImportPath importPath,
Node treeNode) {
return importPath==null ?
String.valueOf(identityHashCode(treeNode)) :
formatPath(importPath.getIdentifiers());
}
@Override
public String toString() {
return getIdentifier();
}
private IResource getResource() {
if (resource!=null) {
return resource;
}
else if (parent!=null) {
return parent.getResource();
}
else {
return null;
}
}
@Override
public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
if (adapter.equals(IFile.class) ||
adapter.equals(IResource.class)) {
return resource;
}
else if (adapter.equals(IJavaElement.class) &&
resource instanceof IFolder) {
return JavaCore.create(resource);
}
else if (adapter.equals(IActionFilter.class)) {
return new ActionFilter();
}
else {
return null;
}
}
}