| /******************************************************************************* |
| * Copyright (c) 2006, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.team.internal.ui.history; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.compare.CompareUI; |
| import org.eclipse.compare.ITypedElement; |
| import org.eclipse.compare.structuremergeviewer.DiffNode; |
| import org.eclipse.compare.structuremergeviewer.ICompareInput; |
| import org.eclipse.compare.structuremergeviewer.IStructureComparator; |
| import org.eclipse.compare.structuremergeviewer.IStructureCreator; |
| import org.eclipse.compare.structuremergeviewer.IStructureCreator2; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.core.history.IFileRevision; |
| import org.eclipse.team.internal.core.history.LocalFileHistory; |
| import org.eclipse.team.internal.ui.Policy; |
| import org.eclipse.team.internal.ui.TeamUIMessages; |
| import org.eclipse.team.internal.ui.TeamUIPlugin; |
| import org.eclipse.team.internal.ui.actions.CompareRevisionAction; |
| import org.eclipse.team.internal.ui.synchronize.LocalResourceTypedElement; |
| import org.eclipse.team.ui.history.HistoryPage; |
| import org.eclipse.team.ui.history.IHistoryPage; |
| import org.eclipse.team.ui.history.IHistoryPageSite; |
| import org.eclipse.ui.IWorkbenchPage; |
| |
| /** |
| * A history page for a sub-element of a file. |
| * <p> |
| * If the site is modal, the local edition is created up-front and destroyed when the page is destroyed. |
| * Otherwise, the local edition is only created when needed. The {@link #getCompareInput(Object)} and |
| * {@link #prepareInput(ICompareInput, org.eclipse.compare.CompareConfiguration, IProgressMonitor)} |
| * methods are only used when the site is modal so they can use the localEdition. |
| */ |
| public class EditionHistoryPage extends LocalHistoryPage { |
| private IFile file; |
| |
| private Object element; |
| |
| private LocalResourceTypedElement localFileElement; |
| private IStructureCreator structureCreator; |
| private Map<IFileRevision, ITypedElement> editions = new HashMap<IFileRevision, ITypedElement>(); |
| private ITypedElement localEdition; |
| private String name; |
| |
| class CompareEditionAction extends CompareRevisionAction { |
| |
| public CompareEditionAction(HistoryPage page) { |
| super(page); |
| } |
| |
| @Override |
| protected ITypedElement getElementFor(IResource resource) { |
| if (resource.equals(file)) |
| return localFileElement; |
| return super.getElementFor(resource); |
| } |
| |
| @Override |
| protected CompareFileRevisionEditorInput createCompareEditorInput(ITypedElement left, ITypedElement right, IWorkbenchPage page) { |
| ITypedElement leftEdition = getEdition(left); |
| boolean leftIsLocal = false; |
| if (leftEdition == null && left instanceof LocalResourceTypedElement) { |
| leftEdition = createLocalEdition(structureCreator, localFileElement, element); |
| leftIsLocal = true; |
| } |
| ITypedElement rightEdition = getEdition(right); |
| return new CompareEditionsEditorInput(structureCreator, left, right, leftEdition, rightEdition, leftIsLocal, page); |
| } |
| |
| private ITypedElement getEdition(ITypedElement input) { |
| if (input instanceof FileRevisionTypedElement) { |
| FileRevisionTypedElement te = (FileRevisionTypedElement) input; |
| return getEditionFor(te.getRevision()); |
| } |
| return null; |
| } |
| } |
| |
| static class CompareEditionsEditorInput extends CompareFileRevisionEditorInput { |
| |
| private final ITypedElement leftRevision; |
| private final ITypedElement rightRevision; |
| private final boolean leftIsLocal; |
| private IStructureCreator structureCreator; |
| |
| public CompareEditionsEditorInput(IStructureCreator structureCreator, ITypedElement left, |
| ITypedElement right, ITypedElement leftEdition, |
| ITypedElement rightEdition, boolean leftIsLocal, IWorkbenchPage page) { |
| super(leftEdition, rightEdition, page); |
| this.structureCreator = structureCreator; |
| leftRevision = left; |
| rightRevision = right; |
| this.leftIsLocal = leftIsLocal; |
| } |
| |
| @Override |
| public LocalResourceTypedElement getLocalElement() { |
| if (leftRevision instanceof LocalResourceTypedElement) { |
| return (LocalResourceTypedElement) leftRevision; |
| } |
| return super.getLocalElement(); |
| } |
| |
| @Override |
| protected FileRevisionTypedElement getRightRevision() { |
| if (rightRevision instanceof FileRevisionTypedElement) { |
| return (FileRevisionTypedElement) rightRevision; |
| } |
| return null; |
| } |
| |
| @Override |
| protected FileRevisionTypedElement getLeftRevision() { |
| if (leftRevision instanceof FileRevisionTypedElement) { |
| return (FileRevisionTypedElement) leftRevision; |
| } |
| return null; |
| } |
| @Override |
| public <T> T getAdapter(Class<T> adapter) { |
| if (adapter == IFile.class) |
| return null; |
| return super.getAdapter(adapter); |
| } |
| |
| @Override |
| protected void handleDispose() { |
| if (leftIsLocal && structureCreator != null) |
| internalDestroy(structureCreator, getLeft()); |
| structureCreator = null; |
| super.handleDispose(); |
| } |
| } |
| |
| public static ITypedElement getPreviousState(IFile file, Object element) throws TeamException { |
| LocalResourceTypedElement localFileElement= new LocalResourceTypedElement(file); |
| IStructureCreator structureCreator = CompareUI.createStructureCreator(localFileElement); |
| if (structureCreator == null) |
| return null; |
| LocalFileHistory history = new LocalFileHistory(file, false); |
| history.refresh(new NullProgressMonitor()); |
| IFileRevision[] revisions = history.getFileRevisions(); |
| if (revisions.length == 0) |
| return null; |
| sortDescending(revisions); |
| ITypedElement localEdition = null; |
| try { |
| localEdition = createLocalEdition(structureCreator, localFileElement, element); |
| for (int i = 0; i < revisions.length; i++) { |
| IFileRevision revision = revisions[i]; |
| ITypedElement edition = createEdition(structureCreator, element, new FileRevisionTypedElement(revision)); |
| if (edition != null && !contentsEqual(structureCreator, localEdition, edition)) { |
| return edition; |
| } |
| } |
| } finally { |
| if (localEdition != null) |
| destroyLocalEdition(structureCreator, localFileElement, localEdition); |
| } |
| return null; |
| } |
| |
| public EditionHistoryPage(IFile file, Object element) { |
| super(ON | ALWAYS); |
| setInput(file, element); |
| } |
| |
| void setInput(IFile file, Object element) { |
| Assert.isNotNull(file); |
| Assert.isNotNull(element); |
| this.file = file; |
| this.element = element; |
| this.localFileElement= new LocalResourceTypedElement(getFile()); |
| structureCreator = CompareUI.createStructureCreator(localFileElement); |
| } |
| |
| @Override |
| public void setSite(IHistoryPageSite site) { |
| super.setSite(site); |
| // If the site is modal, create the local edition |
| if (site.isModal()) { |
| localEdition = createLocalEdition(structureCreator, localFileElement, element); |
| } |
| } |
| |
| @Override |
| protected IFile getFile() { |
| return file; |
| } |
| |
| @Override |
| protected void update(IFileRevision[] revisions, IProgressMonitor monitor) { |
| monitor.beginTask(null, 100); |
| ITypedElement te = null; |
| try { |
| if (localEdition == null) { |
| te = createLocalEdition(structureCreator, localFileElement, element); |
| } else { |
| te = localEdition; |
| } |
| if (te != null) { |
| String oldValue = getName(); |
| String oldDesc = getDescription(); |
| name = te.getName(); |
| IFileRevision[] filtered = filterRevisions(te, revisions, Policy.subMonitorFor(monitor, 75)); |
| super.update(filtered, Policy.subMonitorFor(monitor, 25)); |
| firePropertyChange(this, IHistoryPage.P_NAME, oldValue, getName()); |
| firePropertyChange(this, IHistoryPage.P_DESCRIPTION, oldDesc, getDescription()); |
| } |
| } finally { |
| if (localEdition == null && te != null) |
| internalDestroy(structureCreator, te); |
| monitor.done(); |
| } |
| } |
| |
| private IFileRevision[] filterRevisions(ITypedElement localEdition, IFileRevision[] revisions, |
| IProgressMonitor monitor) { |
| ITypedElement previousEdition = localEdition; |
| List<IFileRevision> result = new ArrayList<IFileRevision>(); |
| sortDescending(revisions); |
| editions.clear(); |
| for (int i = 0; i < revisions.length; i++) { |
| IFileRevision revision = revisions[i]; |
| ITypedElement edition = createEdition(new FileRevisionTypedElement(revision)); |
| if (edition != null && !contentsEqual(structureCreator, previousEdition, edition)) { |
| editions.put(revision, edition); |
| previousEdition = edition; |
| result.add(revision); |
| } |
| } |
| return result.toArray(new IFileRevision[result.size()]); |
| } |
| |
| private static void sortDescending(IFileRevision[] revisions) { |
| Arrays.sort(revisions, (d1, d2) -> -Long.compare(d1.getTimestamp(), d2.getTimestamp())); |
| } |
| |
| private static boolean contentsEqual(IStructureCreator creator, ITypedElement previousEdition, |
| ITypedElement edition) { |
| if (previousEdition == null || creator == null || edition == null) |
| return false; |
| String contents1 = creator.getContents(previousEdition, false /* TODO: Ignore whitespace */); |
| String contents2 = creator.getContents(edition, false /* TODO: Ignore whitespace */); |
| return (contents1 != null && contents2 != null && contents1.equals(contents2)); |
| } |
| |
| @Override |
| public ICompareInput getCompareInput(Object object) { |
| ITypedElement edition = getEditionFor(object); |
| if (edition != null && localEdition != null) |
| return new DiffNode(localEdition, edition); |
| return null; |
| } |
| |
| public ITypedElement getEditionFor(Object object) { |
| return editions.get(object); |
| } |
| |
| private static ITypedElement createLocalEdition(IStructureCreator creator, ITypedElement input, Object element) { |
| if (creator == null) |
| return null; |
| ITypedElement result = null; |
| if (creator instanceof IStructureCreator2) { |
| IStructureCreator2 sc2 = (IStructureCreator2) creator; |
| try { |
| result = sc2.createElement(element, input, null); |
| } catch (CoreException e) { |
| TeamUIPlugin.log(e); |
| } |
| } |
| if (result == null) { |
| result = createEdition(creator, element, input); |
| } |
| return result; |
| } |
| |
| private ITypedElement createEdition(ITypedElement input) { |
| return createEdition(structureCreator, element, input); |
| } |
| |
| private static ITypedElement createEdition(IStructureCreator creator, Object element, ITypedElement input) { |
| if (creator == null) |
| return null; |
| IStructureComparator edition = creator.locate(element, input); |
| if (edition instanceof ITypedElement) |
| return (ITypedElement) edition; |
| return null; |
| } |
| |
| @Override |
| public boolean isValidInput(Object object) { |
| // This page doesn't support input changes |
| return object.equals(element); |
| } |
| |
| @Override |
| public void dispose() { |
| try { |
| disconnect(); |
| } finally { |
| super.dispose(); |
| } |
| } |
| |
| private void disconnect() { |
| if (localFileElement != null) |
| localFileElement.discardBuffer(); |
| internalDestroy(structureCreator, localEdition); |
| localEdition = null; |
| structureCreator = null; |
| } |
| |
| private static void internalDestroy(IStructureCreator creator, ITypedElement te) { |
| if (te != null && creator instanceof IStructureCreator2) { |
| IStructureCreator2 sc2 = (IStructureCreator2) creator; |
| sc2.destroy(te); |
| } |
| } |
| |
| private static void destroyLocalEdition( |
| IStructureCreator structureCreator, LocalResourceTypedElement localFileElement, ITypedElement localEdition) { |
| if (localFileElement != null) |
| localFileElement.discardBuffer(); |
| if (localEdition != null && structureCreator instanceof IStructureCreator2) { |
| IStructureCreator2 sc2 = (IStructureCreator2) structureCreator; |
| sc2.destroy(localEdition); |
| } |
| } |
| |
| @Override |
| protected String getNoChangesMessage() { |
| if (name != null) |
| return NLS.bind(TeamUIMessages.EditionHistoryPage_0, name); |
| return TeamUIMessages.EditionHistoryPage_1; |
| } |
| |
| @Override |
| protected Image getImage(Object object) { |
| if (object == localEdition) |
| return localEdition.getImage(); |
| Object revision = getRevisionFor(object); |
| if (revision != null) |
| return super.getImage(revision); |
| return super.getImage(object); |
| } |
| |
| @Override |
| protected String getLabel(Object object) { |
| Object revision = getRevisionFor(object); |
| if (revision != null) |
| return super.getLabel(revision); |
| return super.getLabel(object); |
| } |
| |
| private Object getRevisionFor(Object object) { |
| if (object == localEdition) |
| return localFileElement; |
| for (Iterator<IFileRevision> iterator = editions.keySet().iterator(); iterator.hasNext();) { |
| IFileRevision revision = iterator.next(); |
| if (editions.get(revision) == object) { |
| return revision; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public String getName() { |
| if (name != null) |
| return name; |
| return super.getName(); |
| } |
| |
| @Override |
| public String getDescription() { |
| if (name != null) |
| return NLS.bind(TeamUIMessages.EditionHistoryPage_2, name); |
| return super.getDescription(); |
| } |
| |
| @Override |
| protected CompareRevisionAction createCompareAction() { |
| return new CompareEditionAction(this); |
| } |
| |
| } |