/**
* Copyright (c) 2010, 2014 Darmstadt University of Technology.
* 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:
* Marcel Bruch - initial API and implementation.
* Olav Lenz - externalize Strings.
*/
package org.eclipse.recommenders.internal.apidocs.rcp;
import static com.google.common.base.Optional.*;
import static org.eclipse.jdt.ui.JavaElementLabels.*;
import static org.eclipse.recommenders.internal.apidocs.rcp.ApidocsViewUtils.*;
import static org.eclipse.recommenders.internal.apidocs.rcp.l10n.LogMessages.ERROR_FAILED_TO_DETERMINE_STATIC_MEMBERS;
import static org.eclipse.recommenders.rcp.JavaElementSelectionEvent.JavaElementSelectionLocation.METHOD_DECLARATION;
import static org.eclipse.recommenders.utils.Logs.log;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.recommenders.apidocs.rcp.ApidocProvider;
import org.eclipse.recommenders.apidocs.rcp.JavaSelectionSubscriber;
import org.eclipse.recommenders.internal.apidocs.rcp.l10n.Messages;
import org.eclipse.recommenders.rcp.JavaElementSelectionEvent;
import org.eclipse.recommenders.rcp.utils.JdtUtils;
import org.eclipse.recommenders.utils.IOUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.TreeMultimap;
import com.google.common.eventbus.EventBus;
@SuppressWarnings("restriction")
public class StaticHooksProvider extends ApidocProvider {
private final class HooksRendererRunnable implements Runnable {
private final TreeMultimap<IType, IMethod> index;
private final Composite parent;
private StyledText styledText;
private HooksRendererRunnable(final TreeMultimap<IType, IMethod> index, final Composite parent) {
this.index = index;
this.parent = parent;
}
@Override
public void run() {
final Composite container = createComposite(parent, 1);
if (index.isEmpty()) {
createLabel(container, Messages.PROVIDER_INTRO_NO_STATIC_HOOKS_FOUND, true);
}
final List<StyleRange> typeRanges = Lists.newLinkedList();
final StringBuilder sb = new StringBuilder();
for (final IType type : index.keySet()) {
final String typeLabel = type.getFullyQualifiedName();
final int typeLabelBegin = sb.length();
sb.append(typeLabel);
final int typeLabelEnd = sb.length();
final StyleRange styleRange = new StyleRange();
styleRange.rise = -12;
styleRange.start = typeLabelBegin;
styleRange.length = typeLabelEnd - typeLabelBegin;
styleRange.fontStyle = SWT.BOLD;
styleRange.data = type;
styleRange.font = JFaceResources.getHeaderFont();
typeRanges.add(styleRange);
sb.append(IOUtils.LINE_SEPARATOR);
for (final IMethod method : index.get(type)) {
sb.append(" "); //$NON-NLS-1$
final int methodLabelBegin = sb.length();
final String methodLabel = getElementLabel(method, M_APP_RETURNTYPE | M_PARAMETER_TYPES);
sb.append(methodLabel);
final int methodLabelEnd = sb.length();
final StyleRange methodStyleRange = new StyleRange();
methodStyleRange.start = methodLabelBegin;
methodStyleRange.length = methodLabelEnd - methodLabelBegin;
methodStyleRange.data = method;
methodStyleRange.underline = true;
methodStyleRange.font = JFaceResources.getDialogFont();
methodStyleRange.foreground = Display.getDefault().getSystemColor(SWT.COLOR_BLUE);
typeRanges.add(methodStyleRange);
sb.append(IOUtils.LINE_SEPARATOR);
}
}
styledText = new StyledText(container, SWT.NONE);
styledText.setRedraw(false);
styledText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
setInfoBackgroundColor(styledText);
setInfoForegroundColor(styledText);
styledText.setEditable(false);
styledText.setText(sb.toString());
styledText.setStyleRanges(typeRanges.toArray(new StyleRange[0]));
final Cursor c1 = Display.getDefault().getSystemCursor(SWT.CURSOR_ARROW);
final Cursor c2 = Display.getDefault().getSystemCursor(SWT.CURSOR_HAND);
styledText.addListener(SWT.MouseDown, new Listener() {
@Override
public void handleEvent(final Event event) {
// It is up to the application to determine when and how a link should be activated.
// In this snippet links are activated on mouse down when the control key is held down
final Optional<IMethod> opt = getSelectedMethod(event.x, event.y);
if (opt.isPresent()) {
final JavaElementSelectionEvent sEvent = new JavaElementSelectionEvent(opt.get(),
METHOD_DECLARATION);
workspaceBus.post(sEvent);
}
}
});
styledText.addMouseMoveListener(new MouseMoveListener() {
@Override
public void mouseMove(final MouseEvent e) {
final Optional<IMethod> opt = getSelectedMethod(e.x, e.y);
if (opt.isPresent()) {
styledText.setCursor(c2);
} else {
styledText.setCursor(c1);
}
}
});
styledText.setRedraw(true);
}
private Optional<StyleRange> getSelectedStyleRange(final int x, final int y) {
try {
final int offset = styledText.getOffsetAtLocation(new Point(x, y));
final StyleRange style = styledText.getStyleRangeAtOffset(offset);
return Optional.fromNullable(style);
} catch (final IllegalArgumentException e) {
return absent();
}
}
private Optional<IMethod> getSelectedMethod(final int x, final int y) {
final Optional<StyleRange> range = getSelectedStyleRange(x, y);
if (!range.isPresent()) {
return absent();
}
final Object data = range.get().data;
if (data instanceof IMethod) {
return of((IMethod) data);
} else {
return absent();
}
}
}
private static class MethodNameComparator implements Comparator<IMethod> {
@Override
public int compare(final IMethod o1, final IMethod o2) {
final String s1 = JavaElementLabels.getElementLabel(o1, JavaElementLabels.ALL_FULLY_QUALIFIED);
final String s2 = JavaElementLabels.getElementLabel(o2, JavaElementLabels.ALL_FULLY_QUALIFIED);
return s1.compareTo(s2);
}
}
private static class TypeNameComparator implements Comparator<IType> {
@Override
public int compare(final IType arg0, final IType arg1) {
final String s0 = arg0.getFullyQualifiedName();
final String s1 = arg1.getFullyQualifiedName();
return s0.compareTo(s1);
}
}
private final EventBus workspaceBus;
@Inject
public StaticHooksProvider(final EventBus workspaceBus) {
this.workspaceBus = workspaceBus;
}
@JavaSelectionSubscriber
public void onPackageRootSelection(final IPackageFragmentRoot root, final JavaElementSelectionEvent event,
final Composite parent) throws ExecutionException {
final TreeMultimap<IType, IMethod> index = TreeMultimap.create(new TypeNameComparator(),
new MethodNameComparator());
try {
for (final IJavaElement e : root.getChildren()) {
if (e.getElementType() == IJavaElement.PACKAGE_FRAGMENT) {
final IPackageFragment pkg = (IPackageFragment) e;
findStaticHooks(pkg, index);
pkg.close();
}
}
} catch (final Exception x) {
log(ERROR_FAILED_TO_DETERMINE_STATIC_MEMBERS, x, root.getElementName());
}
runSyncInUiThread(new HooksRendererRunnable(index, parent));
}
@JavaSelectionSubscriber
public void onPackageSelection(final IPackageFragment pkg, final JavaElementSelectionEvent event,
final Composite parent) throws ExecutionException {
final TreeMultimap<IType, IMethod> index = TreeMultimap.create(new TypeNameComparator(),
new MethodNameComparator());
try {
findStaticHooks(pkg, index);
} catch (final Exception e) {
log(ERROR_FAILED_TO_DETERMINE_STATIC_MEMBERS, e, pkg.getElementName());
}
runSyncInUiThread(new HooksRendererRunnable(index, parent));
}
@JavaSelectionSubscriber
public void onVariableSelection(ILocalVariable var, JavaElementSelectionEvent event, Composite parent)
throws ExecutionException {
IType type = ApidocsViewUtils.findType(var).orNull();
if (type != null) {
onPackageSelection(type.getPackageFragment(), event, parent);
}
}
@JavaSelectionSubscriber
public void onVariableSelection(IField var, JavaElementSelectionEvent event, Composite parent)
throws ExecutionException, JavaModelException {
IType type = ApidocsViewUtils.findType(var).orNull();
if (type != null) {
onPackageSelection(type.getPackageFragment(), event, parent);
}
}
@JavaSelectionSubscriber
public void onJavaElementSelection(final IJavaElement e, final JavaElementSelectionEvent event,
final Composite parent) throws ExecutionException {
IPackageFragment pkg = (IPackageFragment) e.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
if (pkg != null) {
onPackageSelection(pkg, event, parent);
}
}
private void findStaticHooks(final IPackageFragment pkg, final TreeMultimap<IType, IMethod> index)
throws JavaModelException {
for (final ITypeRoot f : pkg.getClassFiles()) {
findStaticHooks(index, f);
}
for (final ITypeRoot f : pkg.getCompilationUnits()) {
findStaticHooks(index, f);
}
}
private void findStaticHooks(final TreeMultimap<IType, IMethod> index, final ITypeRoot root)
throws JavaModelException {
final IType type = root.findPrimaryType();
if (type == null) {
return;
}
if (!type.isClass()) {
return;
}
for (final IMethod m : type.getMethods()) {
if (JdtFlags.isStatic(m) && JdtFlags.isPublic(m) && !JdtUtils.isInitializer(m)) {
index.put(type, m);
}
}
}
}