/*
* 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.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.visitor.GeneralizingAstVisitor;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ElementKind;
import com.google.dart.engine.element.ExecutableElement;
import com.google.dart.engine.element.MethodElement;
import com.google.dart.engine.type.InterfaceType;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.DartCoreDebug;
import com.google.dart.tools.core.analysis.model.AnalysisServerOverridesListener;
import com.google.dart.tools.ui.DartToolsPlugin;
import com.google.dart.tools.ui.DartUI;
import com.google.dart.tools.ui.Messages;
import com.google.dart.tools.ui.internal.text.dart.IDartReconcilingListener;
import com.google.dart.tools.ui.internal.util.ExceptionHandler;
import org.dartlang.analysis.server.protocol.OverriddenMember;
import org.dartlang.analysis.server.protocol.OverrideMember;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.ISynchronizable;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.ui.texteditor.ITextEditor;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Manages the override and overwrite indicators for the given Dart element and annotation model.
*/
public class OverrideIndicatorManager {
public static class OverriddenElementFinder extends GeneralizingAstVisitor<Void> {
private List<OverrideIndicator> indicators = Lists.newArrayList();
@Override
public Void visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement methodElement = node.getElement();
if (methodElement != null) {
Element superElement = findOverriddenElement(methodElement);
if (superElement == null) {
return null;
}
boolean isOverride = hasImplementation(superElement);
// prepare "super" method name
String qualifiedMethodName = MessageFormat.format(
"{0}.{1}",
superElement.getEnclosingElement().getName(),
superElement.getName());
// prepare text
String text;
if (isOverride) {
text = Messages.format(
DartEditorMessages.OverrideIndicatorManager_overrides,
qualifiedMethodName);
} else {
text = Messages.format(
DartEditorMessages.OverrideIndicatorManager_implements,
qualifiedMethodName);
}
// prepare "super" name of implemented
SimpleIdentifier name = node.getName();
Position position = new Position(name.getOffset(), name.getLength());
// add indicator
indicators.add(new OverrideIndicator(superElement, text, isOverride, position));
}
return null;
}
private void addSupertypesToQueue(List<InterfaceType> queue, ClassElement classElement) {
InterfaceType[] mixins = classElement.getMixins();
for (int i = mixins.length - 1; i >= 0; i--) {
queue.add(mixins[i]);
}
InterfaceType superclass = classElement.getSupertype();
if (superclass != null) {
queue.add(superclass);
}
}
private MethodElement findOveriddenMethod(MethodElement methodElement) {
if (methodElement.isStatic()) {
return null;
}
// prepare enclosing ClassElement
if (!(methodElement.getEnclosingElement() instanceof ClassElement)) {
return null;
}
ClassElement classElement = methodElement.getEnclosingElement();
// check super classes
String methodName = methodElement.getDisplayName();
List<InterfaceType> queue = new LinkedList<InterfaceType>();
addSupertypesToQueue(queue, classElement);
while (!queue.isEmpty()) {
InterfaceType type = queue.remove(0);
MethodElement overriddenMethod = type.getMethod(methodName);
if (overriddenMethod != null) {
if (hasCompatibleParams(methodElement, overriddenMethod)) {
return overriddenMethod;
}
}
addSupertypesToQueue(queue, type.getElement());
}
return null;
}
private Element findOverriddenElement(ExecutableElement element) {
ElementKind kind = element.getKind();
if (kind == ElementKind.METHOD) {
return findOveriddenMethod((MethodElement) element);
}
return null;
}
private boolean hasCompatibleParams(MethodElement a, MethodElement b) {
return a.getParameters().length == b.getParameters().length;
}
}
/**
* Overwrite and override indicator annotation.
*/
public static class OverrideIndicator extends Annotation {
private final boolean isOverride;
private final org.dartlang.analysis.server.protocol.Element element;
private final Element element_OLD;
private final Position position;
OverrideIndicator(Element astElement, String text, boolean isOverride, Position position) {
super(ANNOTATION_TYPE, false, text);
this.isOverride = isOverride;
this.element = null;
this.element_OLD = astElement;
this.position = position;
}
OverrideIndicator(org.dartlang.analysis.server.protocol.Element element, String text,
boolean isOverride, Position position) {
super(ANNOTATION_TYPE, false, text);
this.isOverride = isOverride;
this.element = element;
this.element_OLD = null;
this.position = position;
}
/**
* @return <code>true</code> if replacing of the exiting element, or <code>false</code> if new
* implementation of abstract element.
*/
public boolean isOverride() {
return isOverride;
}
/**
* Opens and reveals the defining element.
*/
public void open() {
try {
if (element != null) {
DartUI.openInEditor(element, true);
} else if (element_OLD != null) {
DartUI.openInEditor(element_OLD);
} else {
String title = DartEditorMessages.OverrideIndicatorManager_open_error_title;
String message = DartEditorMessages.OverrideIndicatorManager_open_error_message;
MessageDialog.openError(DartToolsPlugin.getActiveWorkbenchShell(), title, message);
}
} catch (Exception e) {
ExceptionHandler.handle(
e,
DartEditorMessages.OverrideIndicatorManager_open_error_title,
DartEditorMessages.OverrideIndicatorManager_open_error_messageHasLogEntry);
}
}
}
static final String ANNOTATION_TYPE = "com.google.dart.tools.ui.overrideIndicator"; //$NON-NLS-1$
/**
* @return <code>true</code> if given {@link Element} has implementation.
*/
private static boolean hasImplementation(Element element) {
// At this time we are only decorating methods that override a method defined in a superclass or mixin.
return true;
}
private final IAnnotationModel annotationModel;
private final Object annotationModelLockObject;
private List<OverrideIndicator> currentIndicators = Lists.newArrayList();
private CompilationUnitEditor dartEditor;
private String file;
private IDartReconcilingListener reconcileListener = new IDartReconcilingListener() {
@Override
public void reconciled(CompilationUnit unit) {
updateAnnotations(unit, new NullProgressMonitor());
}
};
private AnalysisServerOverridesListener overridesListener = new AnalysisServerOverridesListener() {
@Override
public void computedHighlights(String _file, OverrideMember[] overrides) {
if (Objects.equal(file, _file)) {
updateAnnotations(overrides);
}
}
};
public OverrideIndicatorManager(IAnnotationModel annotationModel) {
Assert.isLegal(DartCoreDebug.ENABLE_ANALYSIS_SERVER);
Assert.isNotNull(annotationModel);
this.annotationModel = annotationModel;
this.annotationModelLockObject = getLockObject(annotationModel);
}
public OverrideIndicatorManager(IAnnotationModel annotationModel, CompilationUnit ast) {
Assert.isNotNull(annotationModel);
this.annotationModel = annotationModel;
this.annotationModelLockObject = getLockObject(annotationModel);
// prepare initial annotations
updateAnnotations(ast, new NullProgressMonitor());
}
public void install(ITextEditor editor) {
Assert.isLegal(editor != null);
uninstall();
if (editor instanceof CompilationUnitEditor) {
dartEditor = (CompilationUnitEditor) editor;
if (DartCoreDebug.ENABLE_ANALYSIS_SERVER) {
file = dartEditor.getInputFilePath();
DartCore.getAnalysisServerData().addOverridesListener(file, overridesListener);
} else {
dartEditor.addReconcileListener(reconcileListener);
}
}
}
public void uninstall() {
if (dartEditor != null) {
if (DartCoreDebug.ENABLE_ANALYSIS_SERVER) {
DartCore.getAnalysisServerData().removeOverridesListener(file, overridesListener);
} else {
dartEditor.removeReconcileListener(reconcileListener);
}
dartEditor = null;
}
}
/**
* Updates the override and implements annotations based on the given AST.
*
* @param ast the compilation unit AST
* @param progressMonitor the progress monitor
*/
protected void updateAnnotations(CompilationUnit ast, IProgressMonitor progressMonitor) {
if (ast == null || progressMonitor.isCanceled()) {
return;
}
// add annotations
OverriddenElementFinder visitor = new OverriddenElementFinder();
ast.accept(visitor);
// may be already cancelled
if (progressMonitor.isCanceled()) {
return;
}
// add annotations to the model
updateAnnotations(visitor.indicators);
}
/**
* Updates the override and implements annotations based on the given {@link OverrideMember}s.
*/
protected void updateAnnotations(OverrideMember[] overrides) {
// add annotations
List<OverrideIndicator> annotations = Lists.newArrayList();
for (OverrideMember override : overrides) {
OverriddenMember superclassMember = override.getSuperclassMember();
List<OverriddenMember> interfaceMembers = override.getInterfaceMembers();
// prepare target
boolean isOverride = true;
org.dartlang.analysis.server.protocol.Element element = null;
String text = null;
if (superclassMember != null) {
element = superclassMember.getElement();
String memberName = MessageFormat.format(
"{0}.{1}",
superclassMember.getClassName(),
superclassMember.getElement().getName());
text = Messages.format(DartEditorMessages.OverrideIndicatorManager_overrides, memberName);
} else if (!interfaceMembers.isEmpty()) {
isOverride = false;
OverriddenMember interfaceMember = interfaceMembers.get(0);
element = interfaceMember.getElement();
String memberName = MessageFormat.format(
"{0}.{1}",
interfaceMember.getClassName(),
interfaceMember.getElement().getName());
text = Messages.format(DartEditorMessages.OverrideIndicatorManager_implements, memberName);
}
// shouldn't happen
if (element == null) {
continue;
}
// add override annotation
Position position = new Position(override.getOffset(), override.getLength());
annotations.add(new OverrideIndicator(element, text, isOverride, position));
}
// add annotations to the model
updateAnnotations(annotations);
}
/**
* Removes all override indicators from this manager's annotation model.
*/
void removeAnnotations() {
if (currentIndicators.isEmpty()) {
return;
}
// remove annotations from the model
synchronized (annotationModelLockObject) {
if (annotationModel instanceof IAnnotationModelExtension) {
((IAnnotationModelExtension) annotationModel).replaceAnnotations(
currentIndicators.toArray(new Annotation[currentIndicators.size()]),
null);
} else {
for (Annotation annotation : currentIndicators) {
annotationModel.removeAnnotation(annotation);
}
}
currentIndicators = Lists.newArrayList();
}
}
/**
* Returns the lock object for the given annotation model.
*
* @param annotationModel the annotation model
* @return the annotation model's lock object
*/
private Object getLockObject(IAnnotationModel annotationModel) {
if (annotationModel instanceof ISynchronizable) {
Object lock = ((ISynchronizable) annotationModel).getLockObject();
if (lock != null) {
return lock;
}
}
return annotationModel;
}
private void updateAnnotations(List<OverrideIndicator> newIndicators) {
synchronized (annotationModelLockObject) {
if (annotationModel instanceof IAnnotationModelExtension) {
Map<OverrideIndicator, Position> annotationMap = Maps.newHashMap();
for (OverrideIndicator indicator : newIndicators) {
annotationMap.put(indicator, indicator.position);
}
((IAnnotationModelExtension) annotationModel).replaceAnnotations(
currentIndicators.toArray(new Annotation[currentIndicators.size()]),
annotationMap);
currentIndicators = newIndicators;
} else {
removeAnnotations();
for (OverrideIndicator indicator : newIndicators) {
annotationModel.addAnnotation(indicator, indicator.position);
}
}
}
}
}