/** * Copyright (c) 2011 Stefan Henss. * 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: * Stefan Henss - initial API and implementation. * Sebastian Proksch - integrated into new eventbus system * Marcel Bruch - changed to own browser-based implementation * Olav Lenz - externalize Strings. */ package org.eclipse.recommenders.internal.apidocs.rcp; import static org.eclipse.recommenders.internal.apidocs.rcp.l10n.LogMessages.ERROR_DURING_JAVADOC_SELECTION; import static org.eclipse.recommenders.internal.rcp.JavaElementSelections.resolveSelectionLocationFromJavaElement; import static org.eclipse.recommenders.utils.Logs.log; import static org.eclipse.recommenders.utils.Reflections.getDeclaredMethodWithAlternativeSignatures; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.net.URL; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2; import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jdt.ui.PreferenceConstants; import org.eclipse.jface.internal.text.html.HTMLPrinter; 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.JavaElementResolver; import org.eclipse.recommenders.rcp.JavaElementSelectionEvent; import org.eclipse.recommenders.rcp.JavaElementSelectionEvent.JavaElementSelectionLocation; import org.eclipse.recommenders.utils.names.VmTypeName; import org.eclipse.recommenders.utils.rcp.Browsers; import org.eclipse.swt.SWT; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.osgi.framework.Bundle; import com.google.common.base.Optional; import com.google.common.eventbus.EventBus; @SuppressWarnings("restriction") public final class JavadocProvider extends ApidocProvider { /** * Use of reflection made necessary due to a change in method signature of * {@link JavadocContentAccess2#getHTMLContent(IMember, boolean)}. There now exists a second alternative signature * of {@link JavadocContentAccess2#getHTMLContent(IJavaElement, boolean)}. * * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=459519">Bug 459519</a> */ private static final Method JAVADOC_CONTENT_ACCESS2_GET_HTML_CONTENT = getDeclaredMethodWithAlternativeSignatures( true, JavadocContentAccess2.class, "getHTMLContent", new Class[] { IMember.class, Boolean.TYPE }, //$NON-NLS-1$ new Class[] { IJavaElement.class, Boolean.TYPE }).orNull(); private static final String FG_STYLE_SHEET = loadStyleSheet(); private final EventBus workspaceBus; private final JavaElementResolver resolver; @Inject public JavadocProvider(final EventBus workspaceBus, final JavaElementResolver resolver) { this.workspaceBus = workspaceBus; this.resolver = resolver; } @JavaSelectionSubscriber public void onCompilationUnitSelection(final ITypeRoot root, final JavaElementSelectionEvent selection, final Composite parent) throws CoreException { final IType type = root.findPrimaryType(); if (type != null) { render(type, parent); } else if (root.getParent() instanceof IPackageFragment) { IPackageFragment packageFragment = (IPackageFragment) root.getParent(); render(packageFragment, parent); } } @JavaSelectionSubscriber public void onTypeSelection(final IType type, final JavaElementSelectionEvent selection, final Composite parent) throws CoreException { render(type, parent); } @JavaSelectionSubscriber public void onMethodSelection(final IMethod method, final JavaElementSelectionEvent selection, final Composite parent) throws CoreException { render(method, parent); } @JavaSelectionSubscriber public void onFieldSelection(final IField field, final JavaElementSelectionEvent selection, final Composite parent) throws CoreException { render(field, parent); } private void render(final IMember element, final Composite parent) throws CoreException { final String html = findJavadoc(element); renderJavadoc(html, parent); } private void render(final IPackageFragment element, final Composite parent) throws CoreException { final String html = findJavadoc(element); renderJavadoc(html, parent); } private void renderJavadoc(final String html, final Composite parent) { runSyncInUiThread(new Runnable() { @Override public void run() { final Browser browser = new Browser(parent, SWT.NONE); browser.setLayoutData(new GridData(GridData.FILL_BOTH)); browser.setText(html); browser.addLocationListener( JavaElementLinks.createLocationListener(new JavaElementLinks.ILinkHandler() { @Override public void handleDeclarationLink(final IJavaElement target) { try { JavaUI.openInEditor(target); } catch (final Exception e) { JavaPlugin.log(e); } } @Override public boolean handleExternalLink(final URL url, final Display display) { try { if (url.getProtocol().equals("file")) { //$NON-NLS-1$ // sometimes we have /, sometimes we have /// String path = url.getPath(); path = StringUtils.removeStart(path, "///"); //$NON-NLS-1$ path = StringUtils.removeStart(path, "/"); //$NON-NLS-1$ final String type = "L" + StringUtils.substring(path, 0, -".html".length()); //$NON-NLS-1$ //$NON-NLS-2$ final VmTypeName typeName = VmTypeName.get(type); final Optional<IType> opt = resolver.toJdtType(typeName); if (opt.isPresent()) { workspaceBus.post(new JavaElementSelectionEvent(opt.get(), JavaElementSelectionLocation.METHOD_DECLARATION)); } } else { Browsers.tryOpenInDefaultBrowser(url); } } catch (final Exception e) { log(ERROR_DURING_JAVADOC_SELECTION, e, url); } return true; } @Override public void handleInlineJavadocLink(final IJavaElement target) { final JavaElementSelectionLocation location = resolveSelectionLocationFromJavaElement(target); workspaceBus.post(new JavaElementSelectionEvent(target, location)); } @Override public void handleJavadocViewLink(final IJavaElement target) { handleInlineJavadocLink(target); } @Override public void handleTextSet() { } })); } }); } private String findJavadoc(final IMember element) throws CoreException { if (JAVADOC_CONTENT_ACCESS2_GET_HTML_CONTENT == null) { return ""; //$NON-NLS-1$ } try { String html = (String) JAVADOC_CONTENT_ACCESS2_GET_HTML_CONTENT.invoke(null, element, true); return extractJavadoc(html); } catch (Exception e) { return ""; //$NON-NLS-1$ } } private String findJavadoc(final IPackageFragment element) throws CoreException { String html = JavadocContentAccess2.getHTMLContent(element); return extractJavadoc(html); } private String extractJavadoc(String html) { if (html == null) { html = Messages.PROVIDER_INTRO_JAVADOC_NOT_FOUND; } final int max = Math.min(100, html.length()); if (html.substring(0, max).indexOf("<html>") != -1) { //$NON-NLS-1$ // there is already a header return html; } final StringBuffer info = new StringBuffer(512 + html.length()); HTMLPrinter.insertPageProlog(info, 0, FG_STYLE_SHEET); info.append(html); HTMLPrinter.addPageEpilog(info); return info.toString(); } private static String loadStyleSheet() { final Bundle bundle = Platform.getBundle(JavaPlugin.getPluginId()); final URL styleSheetURL = bundle.getEntry("/JavadocViewStyleSheet.css"); //$NON-NLS-1$ if (styleSheetURL == null) { return null; } BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(styleSheetURL.openStream())); final StringBuffer buffer = new StringBuffer(1500); String line = reader.readLine(); while (line != null) { buffer.append(line); buffer.append('\n'); line = reader.readLine(); } final FontData fontData = JFaceResources.getFontRegistry() .getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0]; return HTMLPrinter.convertTopLevelFont(buffer.toString(), fontData); } catch (final IOException ex) { JavaPlugin.log(ex); return null; } finally { try { if (reader != null) { reader.close(); } } catch (final IOException e) { } } } }