Java tutorial
/******************************************************************************* * Copyright (c) 2015, 2016 Florian Zoubek. * 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: * Florian Zoubek - initial API and implementation *******************************************************************************/ package at.bitandart.zoubek.mervin.patchset.history; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.e4.ui.model.application.ui.basic.MPart; import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem; import org.eclipse.e4.ui.workbench.modeling.EModelService; import org.eclipse.e4.ui.workbench.modeling.ElementMatcher; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.Match; import org.eclipse.emf.ecore.util.EContentAdapter; import org.eclipse.emf.edit.provider.ComposedAdapterFactory; import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider; import org.eclipse.gmf.runtime.notation.View; import org.eclipse.jface.util.Policy; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StyledCellLabelProvider; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Tree; import at.bitandart.zoubek.mervin.IReviewHighlightService; import at.bitandart.zoubek.mervin.IReviewHighlightServiceListener; import at.bitandart.zoubek.mervin.model.modelreview.ModelReview; import at.bitandart.zoubek.mervin.model.modelreview.ModelReviewPackage; import at.bitandart.zoubek.mervin.model.modelreview.PatchSet; import at.bitandart.zoubek.mervin.review.HighlightHoveredTreeItemMouseTracker; import at.bitandart.zoubek.mervin.review.HighlightMode; import at.bitandart.zoubek.mervin.review.HighlightSelectionListener; import at.bitandart.zoubek.mervin.review.IReviewHighlightProvidingPart; import at.bitandart.zoubek.mervin.review.ModelReviewEditorTrackingView; import at.bitandart.zoubek.mervin.swt.ProgressPanel; import at.bitandart.zoubek.mervin.util.vis.ThreeWayObjectTreeViewerComparator; /** * Shows the similarity of differences to differences of other patch sets. * * <p> * This class adapts to the following classes: * <ul> * <li>{@link ModelReview} - the current model review instance</li> * </ul> * </p> * * @author Florian Zoubek * */ public class PatchSetHistoryView extends ModelReviewEditorTrackingView implements IReviewHighlightProvidingPart, IAdaptable { public static final String PART_DESCRIPTOR_ID = "at.bitandart.zoubek.mervin.partdescriptor.patchset.history"; public static final String PART_TOOLBAR_ID = "at.bitandart.zoubek.mervin.toolbar.patchset.history"; public static final String VIEW_MENU_ID = "at.bitandart.zoubek.mervin.menu.view.patchset.history"; public static final String VIEW_MENU_ITEM_HIGHLIGHT_SWITCH_MODE = "at.bitandart.zoubek.mervin.menu.view.patchset.history.highlight.switchmode"; public static final String VIEW_MENU_ITEM_MERGE_EQUAL_DIFFS = "at.bitandart.zoubek.mervin.menu.view.patchset.history.mergeequaldiffs"; public static final String VIEW_MENU_ITEM_RADIO_VISIBLE_DIFFS_ALL_PATCHSETS = "at.bitandart.zoubek.mervin.menu.view.patchset.history.visiblediffs.allpatchsets"; public static final String VIEW_MENU_ITEM_RADIO_VISIBLE_DIFFS_NEW_PATCHSET = "at.bitandart.zoubek.mervin.menu.view.patchset.history.visiblediffs.newpatchset"; public static final String VIEW_MENU_ITEM_RADIO_VISIBLE_DIFFS_OLD_PATCHSET = "at.bitandart.zoubek.mervin.menu.view.patchset.history.visiblediffs.oldpatchset"; public enum VisibleDiffMode { ALL_DIFFS, NEW_PATCHSET_DIFFS, OLD_PATCHSET_DIFFS } /** * indicates if all SWT controls and viewers have been correctly set up */ private boolean viewInitialized = false; private EContentAdapter patchSetHistoryViewUpdater; /** * the current {@link HighlightMode}, never null; */ private HighlightMode highlightMode = HighlightMode.SELECTION; @Inject private ISimilarityHistoryService similarityHistoryService; @Inject private IReviewHighlightService highlightService; private IPatchSetHistoryEntryOrganizer entryOrganizer; private VisibleDiffMode visibleDiffs = VisibleDiffMode.ALL_DIFFS; private boolean mergeEqualDiffs = true; // JFace Viewers /** * tree viewer that shows the patch set history of the model review */ private TreeViewer historyTreeViewer; // SWT Controls /** * the main panel of this view, that contains all controls for this view */ private Composite mainPanel; private TreeViewerColumn labelColumn; @Inject private Shell shell; @Inject private Display display; // Colors private Color progressBackgroundColor; private Color progressForegroundColor; private HighlightStyler highlightStyler; private ProgressPanel progressPanel; private PatchSetHistoryTreeUpdater currentUpdateThread; @Inject public PatchSetHistoryView() { patchSetHistoryViewUpdater = new UpdatePatchSetHistoryViewAdapter(); } @Inject public void setEntryOrganizer(IPatchSetHistoryEntryOrganizer entryOrganizer) { this.entryOrganizer = entryOrganizer; } @PostConstruct public void postConstruct(Composite parent, EModelService modelService, MPart part) { syncMenuAndToolbarItemState(modelService, part); initializeColors(); highlightStyler = new HighlightStyler(display); mainPanel = new Composite(parent, SWT.NONE); mainPanel.setLayout(new GridLayout()); // progress information panel progressPanel = new ProgressPanel(mainPanel, SWT.BORDER); progressPanel.setBackground(progressBackgroundColor); progressPanel.setForeground(progressForegroundColor); progressPanel.setVisible(false); progressPanel.setLayout(new GridLayout()); GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); gridData.exclude = true; progressPanel.setLayoutData(gridData); // initialize tree viewer historyTreeViewer = new TreeViewer(mainPanel, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL); historyTreeViewer.setComparator(new ViewerComparator()); historyTreeViewer.setContentProvider(new PatchSetHistoryContentProvider()); historyTreeViewer.addSelectionChangedListener(new HighlightSelectionListener(this)); Tree histroryTree = historyTreeViewer.getTree(); histroryTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); histroryTree.setLinesVisible(false); histroryTree.setHeaderVisible(true); histroryTree.addMouseTrackListener(new HighlightHoveredTreeItemMouseTracker(this)); // set up all columns of the tree labelColumn = new TreeViewerColumn(historyTreeViewer, SWT.NONE); labelColumn.getColumn().setResizable(true); labelColumn.getColumn().setMoveable(true); labelColumn.getColumn().setText("Diff"); labelColumn.getColumn().setWidth(400); DiffNameColumnLabelProvider labelColumnLabelProvider = new DiffNameColumnLabelProvider(); labelColumn.setLabelProvider(labelColumnLabelProvider); labelColumn.getColumn().addSelectionListener( new ThreeWayObjectTreeViewerComparator(historyTreeViewer, labelColumn, labelColumnLabelProvider)); viewInitialized = true; // refresh the viewer highlights if highlighting is requested highlightService.addHighlightServiceListener(new IReviewHighlightServiceListener() { @Override public void elementRemoved(ModelReview review, Object element) { historyTreeViewer.refresh(); } @Override public void elementAdded(ModelReview review, Object element) { historyTreeViewer.refresh(); } }); } /** * synchronizes the menu and toolbar item state of radio and check items * with this view. * * @param modelService * the service used to find the menu items * @param part * the part containing the menu items */ private void syncMenuAndToolbarItemState(EModelService modelService, MPart part) { /* for now, just enforce the default state */ /* highlight mode -> disabled by default */ ElementMatcher matcher = new ElementMatcher(VIEW_MENU_ITEM_HIGHLIGHT_SWITCH_MODE, MHandledMenuItem.class, (List<String>) null); /* * IN_PART is not part of ANYWHERE, so use this variant of findElements * and pass IN_PART as search flag */ List<MHandledMenuItem> items = modelService.findElements(part, MHandledMenuItem.class, EModelService.IN_PART, matcher); for (MHandledMenuItem item : items) { item.setSelected(false); } /* visible diffs -> all patch sets is default */ matcher = new ElementMatcher(VIEW_MENU_ITEM_RADIO_VISIBLE_DIFFS_ALL_PATCHSETS, MHandledMenuItem.class, (List<String>) null); items = modelService.findElements(part, MHandledMenuItem.class, EModelService.IN_PART, matcher); for (MHandledMenuItem item : items) { item.setSelected(true); } matcher = new ElementMatcher(VIEW_MENU_ITEM_RADIO_VISIBLE_DIFFS_OLD_PATCHSET, MHandledMenuItem.class, (List<String>) null); items = modelService.findElements(part, MHandledMenuItem.class, EModelService.IN_PART, matcher); for (MHandledMenuItem item : items) { item.setSelected(false); } matcher = new ElementMatcher(VIEW_MENU_ITEM_RADIO_VISIBLE_DIFFS_NEW_PATCHSET, MHandledMenuItem.class, (List<String>) null); items = modelService.findElements(part, MHandledMenuItem.class, EModelService.IN_PART, matcher); for (MHandledMenuItem item : items) { item.setSelected(false); } /* merge equal diffs -> enabled by default */ matcher = new ElementMatcher(VIEW_MENU_ITEM_MERGE_EQUAL_DIFFS, MHandledMenuItem.class, (List<String>) null); items = modelService.findElements(part, MHandledMenuItem.class, EModelService.IN_PART, matcher); for (MHandledMenuItem item : items) { item.setSelected(true); } } /** * initializes the colors for the controls of this view. */ private void initializeColors() { progressBackgroundColor = new Color(display, new RGB(255, 230, 128)); progressForegroundColor = new Color(display, new RGB(212, 170, 0)); } @Override protected void updateValues() { // we cannot update the controls if they are not initialized yet if (viewInitialized) { final ModelReview currentModelReview = getCurrentModelReview(); if (currentModelReview != null) { if (!currentModelReview.eAdapters().contains(patchSetHistoryViewUpdater)) { currentModelReview.eAdapters().add(patchSetHistoryViewUpdater); } if (currentUpdateThread != null && currentUpdateThread.isAlive()) { /* * update thread is already running - disable the progress * panel update, create a new monitor for the progess panel * and cancel the previous thread using the old progress * monitor */ currentUpdateThread.setUpdateProgressPanel(false); IProgressMonitor oldMonitor = progressPanel.getProgressMonitor(); progressPanel.createNewProgressMonitor(); oldMonitor.setCanceled(true); } PatchSet activePatchSet = null; if (visibleDiffs == VisibleDiffMode.NEW_PATCHSET_DIFFS) { activePatchSet = currentModelReview.getRightPatchSet(); } else if (visibleDiffs == VisibleDiffMode.OLD_PATCHSET_DIFFS) { activePatchSet = currentModelReview.getLeftPatchSet(); } currentUpdateThread = new PatchSetHistoryTreeUpdater(currentModelReview, activePatchSet, mergeEqualDiffs, similarityHistoryService, entryOrganizer, historyTreeViewer, labelColumn, progressPanel, mainPanel); currentUpdateThread.start(); } else { // no current review, so reset the input of the history tree historyTreeViewer.setInput(Collections.EMPTY_LIST); } mainPanel.redraw(); mainPanel.update(); } } @PreDestroy private void dispose() { highlightStyler.dispose(); progressBackgroundColor.dispose(); progressForegroundColor.dispose(); } @Override public ModelReview getHighlightedModelReview() { return getCurrentModelReview(); } @Override public HighlightMode getHighlightMode() { return highlightMode; } @Override public void setHighlightMode(HighlightMode highlightMode) { if (highlightMode != null) { this.highlightMode = highlightMode; highlightService.clearHighlights(getCurrentModelReview()); } } @Override public IReviewHighlightService getReviewHighlightService() { return highlightService; } private class UpdatePatchSetHistoryViewAdapter extends EContentAdapter { @Override public void notifyChanged(Notification notification) { super.notifyChanged(notification); Object feature = notification.getFeature(); if (visibleDiffs != VisibleDiffMode.ALL_DIFFS && (feature == ModelReviewPackage.Literals.MODEL_REVIEW__LEFT_PATCH_SET || feature == ModelReviewPackage.Literals.MODEL_REVIEW__RIGHT_PATCH_SET)) display.syncExec(new Runnable() { @Override public void run() { updateValues(); } }); } } /** * @param visibleDiffMode */ public void setVisibleDiffs(VisibleDiffMode visibleDiffMode) { this.visibleDiffs = visibleDiffMode; updateValues(); } /** * * @param mergeEqualDiffs * true if equal diffs should be merged into a single entry, * false otherwise */ public void setMergeEqualDiffs(boolean mergeEqualDiffs) { this.mergeEqualDiffs = mergeEqualDiffs; updateValues(); } @Override public <T> T getAdapter(Class<T> adapterType) { if (adapterType == ModelReview.class) { return adapterType.cast(getCurrentModelReview()); } return null; } /** * A {@link ITreeContentProvider} that provides the objects for the patch * set history. This content provider only supports one viewer per instance, * so do not share a single instance of it across multiple viewers. * * @author Florian Zoubek * */ public class PatchSetHistoryContentProvider implements ITreeContentProvider { private List<IPatchSetHistoryEntry<?, ?>> cachedContainers = new LinkedList<IPatchSetHistoryEntry<?, ?>>(); @Override public void dispose() { } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { cachedContainers.clear(); // cache all containers for a parent identification in // #getParent(Object) Object[] elements = getElements(newInput); for (Object object : elements) { if (object instanceof IPatchSetHistoryEntry<?, ?>) { cachedContainers.add((IPatchSetHistoryEntry<?, ?>) object); } } } @Override public Object[] getElements(Object inputElement) { if (inputElement instanceof Collection<?>) { return ((Collection<?>) inputElement).toArray(); } return new Object[0]; } @Override public Object[] getChildren(Object parentElement) { if (parentElement instanceof IPatchSetHistoryEntry<?, ?>) { return ((IPatchSetHistoryEntry<?, ?>) parentElement).getSubEntries().toArray(); } return new Object[0]; } @Override public Object getParent(Object element) { for (IPatchSetHistoryEntry<?, ?> container : cachedContainers) { if (container.getSubEntries().contains(element)) { return container; } } return null; } @Override public boolean hasChildren(Object element) { if (element instanceof IPatchSetHistoryEntry<?, ?>) { return !((IPatchSetHistoryEntry<?, ?>) element).getSubEntries().isEmpty(); } return false; } } /** * A {@link ColumnLabelProvider} implementation that provides labels for * {@link NamedHistoryEntryContainer}s, as well as * {@link IPatchSetHistoryEntry} entry objects which are instances of EMF * compare model elements. * * @author Florian Zoubek * */ private class DiffNameColumnLabelProvider extends StyledCellLabelProvider implements Comparator<Object> { private AdapterFactoryLabelProvider adapterFactoryLabelProvider; public DiffNameColumnLabelProvider() { adapterFactoryLabelProvider = new AdapterFactoryLabelProvider( new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE)); } @Override public void update(ViewerCell cell) { Object element = cell.getElement(); List<Object> highlightedElements = highlightService.getHighlightedElements(getCurrentModelReview()); StyledString text = new StyledString(); if (isHighlighted(element, highlightedElements)) { text.append(getText(element), highlightStyler); } else { text.append(getText(element)); } if (element instanceof NamedHistoryEntryContainer) { NamedHistoryEntryContainer container = (NamedHistoryEntryContainer) element; text.append(" "); text.append(MessageFormat.format("({0})", countTotalNumberOfEntries(container)), StyledString.COUNTER_STYLER); } cell.setText(text.getString()); cell.setStyleRanges(text.getStyleRanges()); cell.setImage(getImage(element)); super.update(cell); } /** * counts the total number of child entries of the given entry. * * @param entry * the entry to count the child entries for. * @return the number of child entries. */ private int countTotalNumberOfEntries(IPatchSetHistoryEntry<?, ?> entry) { List<IPatchSetHistoryEntry<?, ?>> subEntries = entry.getSubEntries(); int size = subEntries.size(); for (IPatchSetHistoryEntry<?, ?> subEntry : subEntries) { size += countTotalNumberOfEntries(subEntry); } return size; } /** * checks if the given element should be highlighted or not. * * @param element * the element to check. * @param highlightedElements * the set of elements to highlight. * @return true if the given element should be highlighted, false * otherwise. */ private boolean isHighlighted(Object element, List<Object> highlightedElements) { if (highlightedElements.contains(element)) { return true; } if (element instanceof View) { if (isHighlighted(((View) element).getElement(), highlightedElements)) { return true; } } if (element instanceof IPatchSetHistoryEntry) { if (isHighlighted(((IPatchSetHistoryEntry<?, ?>) element).getEntryObject(), highlightedElements)) { return true; } // TODO include values } if (element instanceof Diff) { Match match = ((Diff) element).getMatch(); if (isHighlighted(match.getLeft(), highlightedElements) || isHighlighted(match.getRight(), highlightedElements)) { return true; } } if (element instanceof NamedHistoryEntryContainer) { for (Object entry : ((NamedHistoryEntryContainer) element).getSubEntries()) { if (isHighlighted(entry, highlightedElements)) { return true; } } } return false; } public String getText(Object element) { if (element instanceof NamedHistoryEntryContainer) { NamedHistoryEntryContainer container = (NamedHistoryEntryContainer) element; return container.getName(); } else if (element instanceof IPatchSetHistoryEntry) { Object entryObject = ((IPatchSetHistoryEntry<?, ?>) element).getEntryObject(); // delegate to the default EMF compare label provider return adapterFactoryLabelProvider.getText(entryObject); } return element.toString(); } public Image getImage(Object element) { if (element instanceof IPatchSetHistoryEntry) { Object entryObject = ((IPatchSetHistoryEntry<?, ?>) element).getEntryObject(); // delegate to the default EMF compare label provider return adapterFactoryLabelProvider.getImage(entryObject); } return null; } @Override public int compare(Object object1, Object object2) { String text1 = getText(object1); String text2 = getText(object2); return Policy.getComparator().compare(text1, text2); } } }