| /** |
| * 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; |
| } |
| } |
| |
| } |