blob: dd246081ab63983518c4d3e4aefe4838cc237b4e [file] [log] [blame]
/**
* Copyright (c) 2016 Codetrails GmbH.
* 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
*/
package org.eclipse.epp.internal.logging.aeri.ide.dialogs;
import static com.google.common.base.Objects.firstNonNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.epp.internal.logging.aeri.ide.IProcessorDescriptor;
import org.eclipse.epp.internal.logging.aeri.ide.processors.IEditableReportProcessor;
import org.eclipse.epp.logging.aeri.core.IBundle;
import org.eclipse.epp.logging.aeri.core.IReport;
import org.eclipse.epp.logging.aeri.core.IStackTraceElement;
import org.eclipse.epp.logging.aeri.core.IThrowable;
import org.eclipse.epp.logging.aeri.core.util.ModelSwitch;
import org.eclipse.epp.logging.aeri.core.util.Reports;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
public class ReportPreview {
private static final int RIGHT_PADDING_ATTRIBUTES = 20;
private static final int RIGHT_PADDING_EDIT = 50;
private static final String LINE_SEPARATOR = System.lineSeparator();
private StyledText styledText;
private final Font headlineFont;
private List<SectionsProvider> sectionProviders = ImmutableList.of(new ReportSectionProvider(), new StatusSectionProvider(),
new BundlesSectionProvider(), new AuxiliaryInformationSectionProvider());
private Cursor handCursor = new Cursor(Display.getDefault(), SWT.CURSOR_HAND);
private Cursor arrowCursor = new Cursor(Display.getDefault(), SWT.CURSOR_ARROW);
private Color hyperlinkColor = JFaceColors.getHyperlinkText(Display.getDefault());
private IEditListener editListener;
@FunctionalInterface
public interface IEditListener {
void handleEdit(boolean isReset);
}
public ReportPreview(Composite parent) {
styledText = new StyledText(parent, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER);
styledText.setEditable(false);
styledText.setMargins(2, 2, 2, 2);
styledText.setFont(JFaceResources.getFont(JFaceResources.TEXT_FONT));
styledText.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
styledText.setVisible(false);
styledText.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent event) {
// single click
if (event.count == 1) {
try {
int offset = styledText.getOffsetAtLocation(new Point(event.x, event.y));
StyleRange styleRange = styledText.getStyleRangeAtOffset(offset);
if (styleRange != null && styleRange.data instanceof Runnable) {
((Runnable) styleRange.data).run();
}
} catch (IllegalArgumentException e) {
// no text at the mouse location, ignore...
}
}
}
});
styledText.addMouseMoveListener(new MouseMoveListener() {
@Override
public void mouseMove(MouseEvent event) {
styledText.setCursor(arrowCursor);
try {
int offset = styledText.getOffsetAtLocation(new Point(event.x, event.y));
StyleRange styleRange = styledText.getStyleRangeAtOffset(offset);
if (styleRange != null && styleRange.data instanceof Runnable) {
styledText.setCursor(handCursor);
}
} catch (IllegalArgumentException e) {
// no text at the mouse location, ignore...
}
}
});
FontData[] fd = styledText.getFont().getFontData();
FontRegistry fontRegistry = JFaceResources.getFontRegistry();
// take the first name in case of composed fonts
String name = fd[0].getName();
if (!fontRegistry.hasValueFor(name)) {
fontRegistry.put(name, fd);
}
headlineFont = fontRegistry.getBold(name);
}
public void setEditListener(IEditListener editCallback) {
this.editListener = editCallback;
}
public StyledText getStyledText() {
return styledText;
}
public void preview(IReport report, IStatus status, String serverName, List<IProcessorDescriptor> descriptors, IEclipseContext context,
Shell parent) {
StringBuilder text = new StringBuilder();
text.append("The following will be send to: ").append(serverName).append(LINE_SEPARATOR).append(LINE_SEPARATOR);
List<StyleRange> styleRanges = new ArrayList<>();
for (SectionsProvider sectionsProvider : sectionProviders) {
for (Section section : sectionsProvider.createSections(report, status, serverName, descriptors, context, parent)) {
if (text.length() > 0) {
text.append(LINE_SEPARATOR);
}
// shift relative headline style ranges for absolute text position
if (section.getHeadlineStyleRanges().isEmpty()) {
section.getHeadlineStyleRanges().add(createHeadlineStyleRange(section.getHeadline()));
}
section.getHeadlineStyleRanges().forEach(x -> x.start += text.length());
styleRanges.addAll(section.getHeadlineStyleRanges());
text.append(section.getHeadline()).append(LINE_SEPARATOR).append(LINE_SEPARATOR);
// shift relative style ranges for absolute text position
section.getTextStyleRanges().forEach(x -> x.start += text.length());
styleRanges.addAll(section.getTextStyleRanges());
text.append(section.getText().trim()).append(LINE_SEPARATOR).append(LINE_SEPARATOR);
}
}
styledText.setText(text.toString());
styledText.setStyleRanges(styleRanges.toArray(new StyleRange[0]));
}
private StyleRange createHeadlineStyleRange(String headline) {
StyleRange range = new StyleRange();
range.start = 0;
range.length = headline.length();
range.font = headlineFont;
return range;
}
private void appendAttributes(EObject object, StringBuilder builder) {
for (EAttribute attribute : object.eClass().getEAllAttributes()) {
Object value = firstNonNull(object.eGet(attribute), "");
builder.append(StringUtils.rightPad(attribute.getName(), RIGHT_PADDING_ATTRIBUTES));
builder.append(value);
builder.append(LINE_SEPARATOR);
}
builder.append(LINE_SEPARATOR);
}
private class ReportSectionProvider implements SectionsProvider {
@Override
public List<Section> createSections(IReport report, IStatus status, String serverName, List<IProcessorDescriptor> descriptors,
IEclipseContext context, Shell parent) {
String headline = "REPORT";
StringBuilder text = new StringBuilder();
appendAttributes(report, text);
return Lists.newArrayList(new Section(headline, text.toString()));
}
}
private class StatusSectionProvider implements SectionsProvider {
@Override
public List<Section> createSections(IReport report, IStatus status, String serverName, List<IProcessorDescriptor> descriptors,
IEclipseContext context, Shell parent) {
List<Section> sections = new ArrayList<>();
Reports.visit(report, new ModelSwitch<Void>() {
@Override
public Void caseStatus(org.eclipse.epp.logging.aeri.core.IStatus status) {
String headline = "STATUS";
StringBuilder text = new StringBuilder();
appendAttributes(status, text);
IThrowable exception = status.getException();
if (exception != null) {
text.append("Exception:");
appendStackTrace(exception, text);
}
sections.add(new Section(headline, text.toString()));
return null;
}
private void appendStackTrace(IThrowable throwable, StringBuilder builder) {
builder.append(String.format("%s: %s", throwable.getClassName(), throwable.getMessage())).append(LINE_SEPARATOR);
for (IStackTraceElement element : throwable.getStackTrace()) {
builder.append(String.format("\t at %s.%s(%s:%s)", element.getClassName(), element.getMethodName(),
element.getFileName(), element.getLineNumber())).append(LINE_SEPARATOR);
}
IThrowable cause = throwable.getCause();
if (cause != null) {
builder.append("Caused by: ");
appendStackTrace(cause, builder);
}
}
});
return sections;
}
}
private class BundlesSectionProvider implements SectionsProvider {
@Override
public List<Section> createSections(IReport report, IStatus status, String serverName, List<IProcessorDescriptor> descriptors,
IEclipseContext context, Shell parent) {
String headline = "BUNDLES";
StringBuilder text = new StringBuilder();
Reports.visit(report, new ModelSwitch<Void>() {
@Override
public Void caseBundle(IBundle bundle) {
appendAttributes(bundle, text);
return null;
}
});
if (text.toString().isEmpty()) {
return Collections.emptyList();
}
return Lists.newArrayList(new Section(headline, text.toString()));
}
}
private Set<IProcessorDescriptor> editedDescriptors = new HashSet<>();
private class AuxiliaryInformationSectionProvider implements SectionsProvider {
private static final String LABEL_EDIT = "Edit";
private static final String LABEL_RESET = "Reset";
@Override
public List<Section> createSections(IReport report, IStatus status, String serverName, List<IProcessorDescriptor> descriptors,
IEclipseContext context, Shell parent) {
List<Section> sections = new ArrayList<>();
Map<String, IProcessorDescriptor> directiveToDescriptor = descriptors.stream()
.collect(Collectors.toMap(IProcessorDescriptor::getDirective, Function.identity()));
for (Entry<String, String> entry : report.getAuxiliaryInformation()) {
String directive = entry.getKey();
IProcessorDescriptor directiveDescriptor = directiveToDescriptor.get(directive);
String headline;
if (directiveDescriptor != null) {
headline = StringUtils.abbreviate(directiveDescriptor.getName(), RIGHT_PADDING_EDIT - 1);
} else {
// fallback if a processor adds another key than the directive
headline = directive;
}
String information = entry.getValue().trim();
String text = information + LINE_SEPARATOR + LINE_SEPARATOR;
ArrayList<StyleRange> headlineStyleRanges = new ArrayList<>();
if (directiveDescriptor != null && directiveDescriptor.getProcessor() instanceof IEditableReportProcessor) {
boolean showReset = editedDescriptors.contains(directiveDescriptor);
if (showReset) {
headline = StringUtils.rightPad(headline, RIGHT_PADDING_EDIT);
headlineStyleRanges.add(createHeadlineStyleRange(headline));
String reset = LABEL_RESET;
StyleRange resetStyleRange = new StyleRange();
resetStyleRange.start = headline.length();
resetStyleRange.length = reset.length();
resetStyleRange.underline = true;
resetStyleRange.foreground = hyperlinkColor;
resetStyleRange.data = new Runnable() {
@Override
public void run() {
((IEditableReportProcessor) directiveDescriptor.getProcessor()).reset(status, context);
editedDescriptors.remove(directiveDescriptor);
if (editListener != null) {
editListener.handleEdit(true);
}
}
};
headline += reset;
headline += " ";
headlineStyleRanges.add(resetStyleRange);
} else {
// no reset, increase right padding size to have edit always at the same location
headline = StringUtils.rightPad(headline, RIGHT_PADDING_EDIT + LABEL_RESET.length() + 1);
headlineStyleRanges.add(createHeadlineStyleRange(headline));
}
String edit = LABEL_EDIT;
StyleRange editStyleRange = new StyleRange();
editStyleRange.start = headline.length();
editStyleRange.length = edit.length();
editStyleRange.underline = true;
editStyleRange.foreground = hyperlinkColor;
editStyleRange.data = new Runnable() {
@Override
public void run() {
if (((IEditableReportProcessor) directiveDescriptor.getProcessor()).edit(status, context, parent)) {
editedDescriptors.add(directiveDescriptor);
if (editListener != null) {
editListener.handleEdit(false);
}
}
}
};
headline += edit;
headlineStyleRanges.add(editStyleRange);
sections.add(new Section(headline, headlineStyleRanges, text, new ArrayList<>()));
} else {
// same length for headlines without edit or reset
headline = StringUtils.rightPad(headline, RIGHT_PADDING_EDIT + LABEL_EDIT.length() + LABEL_RESET.length() + 1);
headlineStyleRanges.add(createHeadlineStyleRange(headline));
sections.add(new Section(headline, text));
}
}
return sections;
}
}
private interface SectionsProvider {
List<Section> createSections(IReport report, IStatus status, String serverName, List<IProcessorDescriptor> descriptors,
IEclipseContext context, Shell parent);
}
private static class Section {
private String headline;
private String text;
private List<StyleRange> textStyleRanges;
private List<StyleRange> headlineStyleRanges;
Section(String headline, String text) {
this(headline, new ArrayList<>(), text, new ArrayList<>());
}
Section(String headline, List<StyleRange> headlineStyleRanges, String text, List<StyleRange> textStyleRanges) {
this.headline = headline;
this.text = text;
this.textStyleRanges = textStyleRanges;
this.headlineStyleRanges = headlineStyleRanges;
}
public String getHeadline() {
return headline;
}
public void setHeadline(String headline) {
this.headline = headline;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public List<StyleRange> getTextStyleRanges() {
return textStyleRanges;
}
public void setTextStyleRanges(List<StyleRange> styleRanges) {
this.textStyleRanges = styleRanges;
}
public List<StyleRange> getHeadlineStyleRanges() {
return headlineStyleRanges;
}
public void setHeadlineStyleRanges(List<StyleRange> headlineStyleRanges) {
this.headlineStyleRanges = headlineStyleRanges;
}
}
}