/*******************************************************************************
* Copyright (c) 2016 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.windup.ui.internal.issues;
import static org.jboss.tools.windup.model.domain.WindupMarker.EFFORT;
import static org.jboss.tools.windup.model.domain.WindupMarker.HINT;
import static org.jboss.tools.windup.model.domain.WindupMarker.RULE_ID;
import static org.jboss.tools.windup.model.domain.WindupMarker.SEVERITY;
import static org.jboss.tools.windup.model.domain.WindupMarker.SOURCE_SNIPPET;
import static org.jboss.tools.windup.model.domain.WindupMarker.TITLE;
import static org.jboss.tools.windup.ui.internal.Messages.noIssueDetails;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import org.apache.commons.io.FileUtils;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.internal.browser.BrowserViewer;
import org.jboss.tools.windup.ui.WindupUIPlugin;
import org.jboss.tools.windup.ui.internal.services.MarkerService;
import org.jboss.tools.windup.windup.Issue;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.DocumentType;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.markdown4j.Markdown4jProcessor;
import org.osgi.framework.Bundle;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* View for displaying the details of an Issue.
*/
@SuppressWarnings("restriction")
public class IssueDetailsView {
public static final String ID = "org.jboss.tools.windup.ui.issue.details";
private FormToolkit toolkit;
private DetailsComposite detailsComposite;
private Composite placeholder;
private Composite stack;
@Inject private MarkerService markerService;
private ScrolledForm form;
@PostConstruct
private void create(Composite parent, IEclipseContext context) {
toolkit = new FormToolkit(Display.getDefault());
form = toolkit.createScrolledForm(parent);
GridLayoutFactory.fillDefaults().applyTo(form);
GridDataFactory.fillDefaults().grab(true, true).applyTo(form);
GridLayoutFactory.fillDefaults().applyTo(form.getBody());
stack = toolkit.createComposite(form.getBody());
toolkit.paintBordersFor(stack);
stack.setLayout(new StackLayout());
GridDataFactory.fillDefaults().grab(true, true).applyTo(stack);
IEclipseContext child = context.createChild();
child.set(FormToolkit.class, toolkit);
child.set(Composite.class, stack);
detailsComposite = ContextInjectionFactory.make(DetailsComposite.class, child);
placeholder = toolkit.createComposite(stack);
toolkit.createLabel(placeholder, noIssueDetails);
GridLayoutFactory.fillDefaults().applyTo(placeholder);
setTop(placeholder);
}
@PreDestroy
private void dispose() {
toolkit.dispose();
}
@Inject
public void showIssueDetails(@Optional IMarker marker) {
if (detailsComposite != null && !detailsComposite.isDisposed()) {
Composite top = placeholder;
if (marker != null) {
Issue issue = markerService.find(marker);
if (issue != null) {
detailsComposite.setIssue(marker, issue);
top = detailsComposite;
}
}
setTop(top);
}
}
private void setTop(Control top) {
((StackLayout)stack.getLayout()).topControl = top;
stack.layout(true, true);
form.reflow(true);
}
private static class DetailsComposite extends Composite {
private IMarker marker;
private BrowserViewer browserViewer;
@Inject
public DetailsComposite(Composite parent, FormToolkit toolkit) {
super(parent, SWT.NONE);
GridLayoutFactory.fillDefaults().applyTo(this);
GridDataFactory.fillDefaults().grab(true, true).applyTo(this);
browserViewer = new BrowserViewer(this, BrowserViewer.LOCATION_BAR|BrowserViewer.BUTTON_BAR);
browserViewer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
}
public void setIssue(IMarker marker, Issue issue) {
this.marker = marker;
refresh(issue);
}
private void refresh(Issue issue) {
String html = buildText(issue);
try {
Bundle bundle = WindupUIPlugin.getDefault().getBundle();
File htmlFile = File.createTempFile("tmp", ".html");
Document doc = Jsoup.parse(html);
Elements codeElements = doc.getElementsByTag("code");
codeElements.forEach(element -> {
Set<String> classNames = element.classNames();
Set<String> newNames = Sets.newHashSet();
classNames.forEach(className -> {
// prismjs requires prefix, i'm not sure about another/easier workaround.
newNames.add("language-"+className);
});
element.classNames(newNames);
});
DocumentType type = new DocumentType("html", "", "", "");
doc.insertChildren(0, Lists.newArrayList(type));
Element head = doc.head();
Element css = doc.createElement("link");
URL fileURL = FileLocator.find(bundle, new Path("html/prism.css"), null);
String srcPath = FileLocator.resolve(fileURL).getPath();
css.attr("href", srcPath);
css.attr("rel", "stylesheet");
head.appendChild(css);
Element body = doc.body();
Element script = doc.createElement("script");
fileURL = FileLocator.find(bundle, new Path("html/prism.js"), null);
srcPath = FileLocator.resolve(fileURL).getPath();
script.attr("src", srcPath);
body.appendChild(script);
FileUtils.write(htmlFile, doc.html());
browserViewer.setURL(htmlFile.getAbsolutePath());;
} catch (Exception e) {
WindupUIPlugin.log(e);
}
}
private String buildText(Issue issue) {
StringBuilder builder = new StringBuilder();
builder.append("<h3>Title</h3>");
builder.append(marker.getAttribute(TITLE, noIssueDetails));
builder.append("<h3>Hint</h3>");
Markdown4jProcessor markdownProcessor = new Markdown4jProcessor();
try {
builder.append(markdownProcessor.process(marker.getAttribute(HINT, noIssueDetails)));
} catch (IOException e) {
e.printStackTrace();
}
builder.append("<h3>Severity</h3>");
builder.append(marker.getAttribute(SEVERITY, noIssueDetails));
builder.append("<h3>Effort</h3>");
builder.append(marker.getAttribute(EFFORT, noIssueDetails));
builder.append("<h3>Rule ID</h3>");
builder.append(marker.getAttribute(RULE_ID, noIssueDetails));
builder.append("<h3>More Information</h3>");
if (issue.getLinks().isEmpty()) {
builder.append(noIssueDetails);
}
for (final org.jboss.tools.windup.windup.Link link : issue.getLinks()) {
builder.append("<p>" + link.getDescription() + "<br/>");
builder.append("<ul><li>");
builder.append("<a href=\"" + link.getUrl() + "\"" + ">" + link.getUrl() + "</a>");
builder.append("</ul></li></p>");
}
builder.append("<h3>Source</h3>");
builder.append(marker.getAttribute(SOURCE_SNIPPET, noIssueDetails));
return builder.toString();
}
}
}