/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.ui.internal.text.editor;
import com.google.common.collect.Lists;
import com.google.dart.engine.ast.AssignmentExpression;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.BinaryExpression;
import com.google.dart.engine.ast.ClassDeclaration;
import com.google.dart.engine.ast.ClassMember;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.CompilationUnitMember;
import com.google.dart.engine.ast.ConstructorDeclaration;
import com.google.dart.engine.ast.FieldDeclaration;
import com.google.dart.engine.ast.FormalParameterList;
import com.google.dart.engine.ast.FunctionDeclaration;
import com.google.dart.engine.ast.FunctionExpression;
import com.google.dart.engine.ast.FunctionTypeAlias;
import com.google.dart.engine.ast.InstanceCreationExpression;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.MethodInvocation;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.TopLevelVariableDeclaration;
import com.google.dart.engine.ast.TypeName;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.ast.VariableDeclarationList;
import com.google.dart.engine.ast.VariableDeclarationStatement;
import com.google.dart.engine.ast.visitor.RecursiveAstVisitor;
import com.google.dart.engine.element.Element;
import com.google.dart.tools.ui.DartElementImageDescriptor;
import com.google.dart.tools.ui.DartPluginImages;
import com.google.dart.tools.ui.DartToolsPlugin;
import com.google.dart.tools.ui.internal.viewsupport.ImageDescriptorRegistry;
import org.dartlang.analysis.server.protocol.Outline;
import org.eclipse.core.resources.IFile;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.TreeItem;
import java.util.List;
/**
* Helper for creating and displaying {@link LightNodeElement}s.
*/
public class LightNodeElements {
/**
* {@link ViewerComparator} for {@link LightNodeElement} names.
*/
public static class NameComparator extends ViewerComparator {
private static final int NOT_ELEMENT = 2;
private static final int PRIVATE_ELEMENT = 1;
private static final int PUBLIC_ELEMENT = 0;
@Override
public int category(Object e) {
if (!(e instanceof LightNodeElement)) {
return NOT_ELEMENT;
}
LightNodeElement element = (LightNodeElement) e;
if (element.isPrivate()) {
return PRIVATE_ELEMENT;
}
return PUBLIC_ELEMENT;
}
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
// compare categories
int cat1 = category(e1);
int cat2 = category(e2);
if (cat1 != cat2) {
return cat1 - cat2;
}
// check types
if (!(e1 instanceof LightNodeElement)) {
return 0;
}
if (!(e2 instanceof LightNodeElement)) {
return 0;
}
// compare names
String name1 = ((LightNodeElement) e1).getName();
String name2 = ((LightNodeElement) e2).getName();
if (name1 == null || name2 == null) {
return 0;
}
return name1.compareTo(name2);
}
}
/**
* {@link ViewerComparator} for {@link LightNodeElement} positions.
*/
public static class PositionComparator extends ViewerComparator {
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
if (!(e1 instanceof LightNodeElement)) {
return 0;
}
if (!(e2 instanceof LightNodeElement)) {
return 0;
}
int offset1 = ((LightNodeElement) e1).getNameOffset();
int offset2 = ((LightNodeElement) e2).getNameOffset();
return offset1 - offset2;
}
}
/**
* {@link ITreeContentProvider} for {@link LightNodeElement}s in {@link CompilationUnit}.
*/
private static class NodeContentProvider implements ITreeContentProvider {
private final IFile contextFile;
private final List<LightNodeElement> elements = Lists.newArrayList();
public NodeContentProvider(IFile contextFile) {
this.contextFile = contextFile;
}
@Override
public void dispose() {
}
@Override
public Object[] getChildren(Object parentElement) {
List<LightNodeElement> children = ((LightNodeElement) parentElement).children;
return children.toArray(new LightNodeElement[children.size()]);
}
@Override
public Object[] getElements(Object inputElement) {
return elements.toArray(new LightNodeElement[elements.size()]);
}
@Override
public Object getParent(Object element) {
return ((LightNodeElement) element).getParent();
}
@Override
public boolean hasChildren(Object element) {
return getChildren(element).length != 0;
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
elements.clear();
// prepare CompilationUnit
CompilationUnit unit = (CompilationUnit) newInput;
if (unit == null) {
return;
}
// create elements
NodeList<CompilationUnitMember> unitDeclarations = unit.getDeclarations();
for (CompilationUnitMember unitMember : unitDeclarations) {
if (unitMember instanceof TopLevelVariableDeclaration) {
TopLevelVariableDeclaration topVarDecl = (TopLevelVariableDeclaration) unitMember;
List<VariableDeclaration> variables = topVarDecl.getVariables().getVariables();
for (VariableDeclaration variable : variables) {
LightNodeElement element = createLightNodeElement(contextFile, null, variable, true);
if (element != null) {
elements.add(element);
}
}
} else {
LightNodeElement element = createLightNodeElement(contextFile, null, unitMember, true);
if (element != null) {
elements.add(element);
}
}
if (elements.size() > MAX_UNIT_MEMBER_COUNT) {
elements.add(new LightNodeElement(contextFile, null, unit, unit, String.format(
MAX_CHILDREN_TEXT,
MAX_UNIT_MEMBER_COUNT,
unitDeclarations.size())));
break;
}
}
}
}
/**
* {@link LabelProvider} for {@link LightNodeElement}.
*/
private static class NodeLabelProvider extends LabelProvider implements IStyledLabelProvider {
private static final Point SIZE = new Point(22, 16);
private static final ImageDescriptorRegistry registry = DartToolsPlugin.getImageDescriptorRegistry();
private static ImageDescriptor getBaseImageDescriptor(AstNode node, boolean isPrivate) {
if (node instanceof ClassDeclaration) {
return isPrivate ? DartPluginImages.DESC_DART_CLASS_PRIVATE
: DartPluginImages.DESC_DART_CLASS_PUBLIC;
}
if (node instanceof FunctionTypeAlias) {
return isPrivate ? DartPluginImages.DESC_DART_FUNCTIONTYPE_PRIVATE
: DartPluginImages.DESC_DART_FUNCTIONTYPE_PUBLIC;
}
if (node instanceof VariableDeclaration) {
return isPrivate ? DartPluginImages.DESC_DART_FIELD_PRIVATE
: DartPluginImages.DESC_DART_FIELD_PUBLIC;
}
if (node instanceof FunctionDeclaration || node instanceof ConstructorDeclaration
|| node instanceof MethodDeclaration) {
return isPrivate ? DartPluginImages.DESC_DART_METHOD_PRIVATE
: DartPluginImages.DESC_DART_METHOD_PUBLIC;
}
return null;
}
private static ImageDescriptor getImageDescriptor(AstNode node, boolean isPrivate) {
ImageDescriptor base = getBaseImageDescriptor(node, isPrivate);
if (base == null) {
return null;
}
int flags = 0;
// ClassDeclaration
if (node instanceof ClassDeclaration) {
ClassDeclaration classDeclaration = (ClassDeclaration) node;
if (classDeclaration.isAbstract()) {
flags |= DartElementImageDescriptor.ABSTRACT;
}
}
// ConstructorDeclaration
if (node instanceof ConstructorDeclaration) {
flags |= DartElementImageDescriptor.CONSTRUCTOR;
}
// MethodDeclaration
if (node instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) node;
if (method.isAbstract()) {
flags |= DartElementImageDescriptor.ABSTRACT;
}
if (method.isStatic()) {
flags |= DartElementImageDescriptor.STATIC;
}
if (method.isGetter()) {
flags |= DartElementImageDescriptor.GETTER;
}
if (method.isSetter()) {
flags |= DartElementImageDescriptor.SETTER;
}
}
// done
return new DartElementImageDescriptor(base, flags, SIZE);
}
@Override
public Image getImage(Object o) {
LightNodeElement element = (LightNodeElement) o;
boolean isPrivate = element.isPrivate();;
AstNode node = element.getNode();
ImageDescriptor descriptor = getImageDescriptor(node, isPrivate);
if (descriptor != null) {
return registry.get(descriptor);
}
return null;
}
@Override
public StyledString getStyledText(Object obj) {
StyledString styledString = new StyledString(getText(obj));
// prepare object elements
LightNodeElement lightElement = (LightNodeElement) obj;
AstNode node = lightElement.getNode();
// prepare parameters
FormalParameterList parameters = null;
TypeName returnType = null;
String returnTypeSeparator = Element.RIGHT_ARROW;
if (node instanceof VariableDeclaration
&& node.getParent() instanceof VariableDeclarationList) {
VariableDeclarationList declaration = (VariableDeclarationList) node.getParent();
returnType = declaration.getType();
returnTypeSeparator = " : ";
}
if (node instanceof FunctionDeclaration) {
FunctionDeclaration function = (FunctionDeclaration) node;
FunctionExpression functionExpression = function.getFunctionExpression();
if (functionExpression != null) {
parameters = functionExpression.getParameters();
returnType = function.getReturnType();
}
}
if (node instanceof FunctionTypeAlias) {
FunctionTypeAlias functionTypeAlias = (FunctionTypeAlias) node;
parameters = functionTypeAlias.getParameters();
returnType = functionTypeAlias.getReturnType();
}
if (node instanceof ConstructorDeclaration) {
ConstructorDeclaration constructor = (ConstructorDeclaration) node;
parameters = constructor.getParameters();
}
if (node instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) node;
parameters = method.getParameters();
returnType = method.getReturnType();
}
// may be append parameters
if (parameters != null) {
styledString.append(parameters.toSource(), StyledString.DECORATIONS_STYLER);
}
if (returnType != null) {
styledString.append(
returnTypeSeparator + returnType.toSource(),
StyledString.QUALIFIER_STYLER);
}
// done
return styledString;
}
@Override
public String getText(Object element) {
return ((LightNodeElement) element).getName();
}
}
/**
* The maximum number of children we want to show in any compilation unit.
*/
private static final int MAX_UNIT_MEMBER_COUNT = 750;
/**
* The maximum number of children we want to show in any class.
*/
private static final int MAX_CLASS_MEMBER_COUNT = 250;
/**
* The text to show if there are too many children.
*/
private static final String MAX_CHILDREN_TEXT = "<First %d of %d children are displayed>";
public static final ViewerComparator NAME_COMPARATOR = new NameComparator();
public static final ViewerComparator POSITION_COMPARATOR = new PositionComparator();
/**
* @return the {@link LightNodeElement} for given {@link AstNode}, may be <code>null</code> if
* given is not declaration and does not have reasonable declaration child.
*/
public static LightNodeElement createLightNodeElement(IFile contextFile, AstNode node) {
if (node == null) {
return null;
}
// prepare "childNode"
AstNode childNode = null;
AstNode parentNode = null;
if (childNode == null) {
FunctionDeclaration function = node.getAncestor(FunctionDeclaration.class);
if (function != null) {
childNode = function;
}
}
if (childNode == null) {
ConstructorDeclaration constructor = node.getAncestor(ConstructorDeclaration.class);
if (constructor != null) {
childNode = constructor;
}
}
if (childNode == null) {
MethodDeclaration method = node.getAncestor(MethodDeclaration.class);
if (method != null) {
childNode = method;
}
}
if (childNode == null) {
FunctionTypeAlias function = node.getAncestor(FunctionTypeAlias.class);
if (function != null) {
childNode = function;
}
}
if (childNode == null) {
childNode = node.getAncestor(VariableDeclaration.class);
}
{
FieldDeclaration fieldDeclaration = node.getAncestor(FieldDeclaration.class);
if (fieldDeclaration != null) {
parentNode = fieldDeclaration.getParent();
List<VariableDeclaration> fields = fieldDeclaration.getFields().getVariables();
if (!fields.isEmpty()) {
childNode = fields.get(0);
}
}
}
{
TopLevelVariableDeclaration decl = node.getAncestor(TopLevelVariableDeclaration.class);
if (decl != null) {
parentNode = decl.getParent();
List<VariableDeclaration> vars = decl.getVariables().getVariables();
if (!vars.isEmpty()) {
childNode = vars.get(0);
}
}
}
if (childNode == null) {
ClassDeclaration clazz = node.getAncestor(ClassDeclaration.class);
if (clazz != null) {
childNode = clazz;
}
}
// prepare "parent"
LightNodeElement parent = null;
if (childNode != null) {
if (parentNode == null) {
parentNode = childNode.getParent();
}
parent = createLightNodeElement(contextFile, parentNode);
}
// try to create LightNodeElement
LightNodeElement element = createLightNodeElement(contextFile, parent, childNode, false);
if (element == null) {
element = parent;
}
return element;
}
/**
* Expands {@link #viewer} us much as possible while still in the given time budget.
*/
public static void expandTreeItemsTimeBoxed(TreeViewer viewer, long nanoBudget) {
int numIterations = 10;
int childrenLimit = 10;
TreeItem[] rootTreeItems = viewer.getTree().getItems();
for (int i = 0; i < numIterations; i++) {
if (nanoBudget < 0) {
break;
}
nanoBudget = expandTreeItemsTimeBoxed(viewer, rootTreeItems, childrenLimit, nanoBudget);
childrenLimit *= 2;
}
}
/**
* @return the root {@link LightNodeElement}s created by {@link #newTreeContentProvider()}.
*/
public static List<LightNodeElement> getRootElements(TreeViewer viewer) {
return ((NodeContentProvider) viewer.getContentProvider()).elements;
}
/**
* @return the new label provider instance to use for displaying {@link LightNodeElement}s.
*/
public static IBaseLabelProvider newLabelProvider() {
return new DelegatingStyledCellLabelProvider(new NodeLabelProvider());
}
/**
* @return {@link ITreeContentProvider} for {@link TreeViewer} of {@link LightNodeElement}s.
*/
public static ITreeContentProvider newTreeContentProvider(DartEditor editor) {
IFile contextFile = editor.getInputResourceFile();
return newTreeContentProvider(contextFile);
}
/**
* @return {@link ITreeContentProvider} for {@link TreeViewer} of {@link LightNodeElement}s.
*/
public static ITreeContentProvider newTreeContentProvider(IFile contextFile) {
return new NodeContentProvider(contextFile);
}
private static void addLocalFunctions(final IFile contextFile, final LightNodeElement parent,
AstNode in) {
if (in == null) {
return;
}
in.accept(new RecursiveAstVisitor<Void>() {
@Override
public Void visitAssignmentExpression(AssignmentExpression node) {
return null;
}
@Override
public Void visitBinaryExpression(BinaryExpression node) {
return null;
}
@Override
public Void visitFunctionDeclaration(FunctionDeclaration node) {
createLightNodeElement(contextFile, parent, node, true);
return null;
}
@Override
public Void visitInstanceCreationExpression(InstanceCreationExpression node) {
return null;
}
@Override
public Void visitMethodInvocation(MethodInvocation node) {
return null;
}
@Override
public Void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
return null;
}
});
}
private static LightNodeElement createLightNodeElement(final IFile contextFile,
LightNodeElement parent, AstNode node, boolean withChildren) {
// VariableDeclaration
if (node instanceof VariableDeclaration) {
VariableDeclaration variable = (VariableDeclaration) node;
SimpleIdentifier nameNode = variable.getName();
String name = nameNode.getName();
return new LightNodeElement(contextFile, parent, variable, nameNode, name);
}
// ConstructorDeclaration
if (node instanceof ConstructorDeclaration) {
ConstructorDeclaration constructor = (ConstructorDeclaration) node;
String name = parent.getName();
SimpleIdentifier constructorName = constructor.getName();
LightNodeElement result;
if (constructorName != null) {
name += "." + constructorName.getName();
result = new LightNodeElement(contextFile, parent, node, constructorName, name);
} else {
result = new LightNodeElement(contextFile, parent, node, constructor.getReturnType(), name);
}
if (withChildren) {
addLocalFunctions(contextFile, result, constructor.getBody());
}
return result;
}
// method
if (node instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) node;
SimpleIdentifier nameNode = method.getName();
String name = nameNode.getName();
if (method.isSetter()) {
name += "=";
}
LightNodeElement result = new LightNodeElement(contextFile, parent, node, nameNode, name);
if (withChildren) {
addLocalFunctions(contextFile, result, method.getBody());
}
return result;
}
// ClassDeclaration
if (node instanceof ClassDeclaration) {
ClassDeclaration classDeclaration = (ClassDeclaration) node;
SimpleIdentifier nameNode = classDeclaration.getName();
LightNodeElement classElement = new LightNodeElement(
contextFile,
null,
node,
nameNode,
nameNode.getName());
if (withChildren) {
NodeList<ClassMember> classMembers = classDeclaration.getMembers();
for (ClassMember classMember : classMembers) {
if (classMember instanceof FieldDeclaration) {
FieldDeclaration fieldDeclaration = (FieldDeclaration) classMember;
List<VariableDeclaration> fields = fieldDeclaration.getFields().getVariables();
for (VariableDeclaration field : fields) {
createLightNodeElement(contextFile, classElement, field, true);
}
} else {
createLightNodeElement(contextFile, classElement, classMember, true);
}
if (classElement.children.size() > MAX_CLASS_MEMBER_COUNT) {
new LightNodeElement(
contextFile,
classElement,
classDeclaration,
classDeclaration.getName(),
String.format(MAX_CHILDREN_TEXT, MAX_CLASS_MEMBER_COUNT, classMembers.size()));
break;
}
}
}
return classElement;
}
// FunctionDeclaration
if (node instanceof FunctionDeclaration) {
FunctionDeclaration functionDeclaration = (FunctionDeclaration) node;
SimpleIdentifier nameNode = functionDeclaration.getName();
final LightNodeElement result = new LightNodeElement(
contextFile,
parent,
functionDeclaration,
nameNode,
nameNode.getName());
if (withChildren) {
addLocalFunctions(contextFile, result, functionDeclaration.getFunctionExpression());
}
return result;
}
// FunctionTypeAlias
if (node instanceof FunctionTypeAlias) {
FunctionTypeAlias alias = (FunctionTypeAlias) node;
SimpleIdentifier nameNode = alias.getName();
return new LightNodeElement(contextFile, null, alias, nameNode, nameNode.getName());
}
// unknown
return null;
}
/**
* Expands given {@link TreeItem}s if they have not too much children and we have time budget.
*/
private static long expandTreeItemsTimeBoxed(TreeViewer viewer, TreeItem[] items,
int childrenLimit, long nanoBudget) {
if (nanoBudget < 0) {
return -1;
}
for (TreeItem item : items) {
Object itemData = item.getData();
// prepare number of children
int numChildren = 0;
{
if (itemData instanceof LightNodeElement) {
numChildren = ((LightNodeElement) itemData).children.size();
}
if (itemData instanceof Outline) {
numChildren = ((Outline) itemData).getChildren().size();
}
}
// has children, not too many?
if (numChildren == 0 || numChildren > childrenLimit) {
continue;
}
// expand single item
{
long startNano = System.nanoTime();
viewer.setExpandedState(itemData, true);
nanoBudget -= System.nanoTime() - startNano;
}
// expand children
nanoBudget = expandTreeItemsTimeBoxed(viewer, item.getItems(), childrenLimit, nanoBudget);
if (nanoBudget < 0) {
break;
}
}
return nanoBudget;
}
}