| /******************************************************************************* |
| * Copyright (c) 2008-2011 Chair for Applied Software Engineering, |
| * Technische Universitaet Muenchen. |
| * 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: Aumann |
| ******************************************************************************/ |
| package org.eclipse.emf.emfstore.internal.client.ui.views.historybrowserview; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.emfstore.internal.client.model.ProjectSpace; |
| import org.eclipse.emf.emfstore.internal.server.model.impl.api.ESHistoryInfoImpl; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.HistoryInfo; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.HistoryQuery; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.PrimaryVersionSpec; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.Versions; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.util.HistoryQueryBuilder; |
| import org.eclipse.emf.emfstore.server.exceptions.ESException; |
| import org.eclipse.emf.emfstore.server.model.ESHistoryInfo; |
| import org.eclipse.emf.emfstore.server.model.query.ESHistoryQuery; |
| |
| /** |
| * Class handling pagination. See constructor {@link #PaginationManager(ProjectSpace, EObject, int, int)} |
| * |
| * @author Aumann |
| * |
| */ |
| public class PaginationManager { |
| |
| /** |
| * The version around which history queries are created. At initialization |
| * time this is the base version. It gets change if the user clicks on 'show |
| * next x elements' |
| */ |
| private PrimaryVersionSpec currentCenterVersionShown; |
| |
| private final int aboveCenterCount, belowCenterCount; |
| |
| private List<HistoryInfo> currentlyPresentedInfos = new ArrayList<HistoryInfo>(); |
| |
| private final ProjectSpace projectSpace; |
| |
| private boolean nextPage; |
| |
| private boolean prevPage; |
| |
| private boolean showAllVersions; |
| |
| private final EObject modelElement; |
| |
| private final String projectBranch; |
| |
| /** |
| * Creates a new PaginationManager with given page range around the central |
| * version. The central version is initialized to be the base version. |
| * |
| * Note that the real number of versions shown might be smaller if there are |
| * not enough versions above (e.g. base version == head version) or below |
| * the center version (e.g. only x revisions yet, but below is larger). |
| * |
| * @param projectSpace |
| * The project space to operate on. |
| * @param modelElement An optional modelElement to show the history for. <code>null</code> to show the history for |
| * the project space. |
| * @param aboveCenterCount |
| * The number of versions shown above the central version. |
| * @param belowCenterCount |
| * The number of versions shown below the central version. |
| */ |
| public PaginationManager(ProjectSpace projectSpace, EObject modelElement, int aboveCenterCount, int belowCenterCount) { |
| this.aboveCenterCount = aboveCenterCount; |
| this.belowCenterCount = belowCenterCount; |
| this.projectSpace = projectSpace; |
| this.modelElement = modelElement; |
| projectBranch = projectSpace.getBaseVersion().getBranch(); |
| } |
| |
| /** |
| * @return The history info objects to be displayed on the current page. |
| * @throws ESException |
| * If an exception gets thrown contacting the server. |
| */ |
| public List<HistoryInfo> retrieveHistoryInfos() throws ESException { |
| PrimaryVersionSpec newCenterVersion; |
| int beforeCurrent = -1; |
| if ((prevPage || nextPage) && currentCenterVersionShown != null && !currentlyPresentedInfos.isEmpty()) { |
| for (int i = 0; i < currentlyPresentedInfos.size(); i++) { |
| if (currentlyPresentedInfos.get(i).getPrimarySpec().getIdentifier() == currentCenterVersionShown |
| .getIdentifier()) { |
| beforeCurrent = i; |
| break; |
| } |
| } |
| |
| assert beforeCurrent != -1 : "The currently shown center version should be contained in the currently shown history infos, why has it vanished?"; |
| |
| if (prevPage) { |
| // there might be more versions, so swap page if there are |
| newCenterVersion = currentlyPresentedInfos.get(0).getPrimarySpec(); |
| } else if (nextPage) { |
| newCenterVersion = currentlyPresentedInfos.get(currentlyPresentedInfos.size() - 1).getPrimarySpec(); |
| } else { |
| assert false; |
| newCenterVersion = currentCenterVersionShown; |
| } |
| } else { |
| newCenterVersion = currentCenterVersionShown; |
| } |
| final HistoryQuery<ESHistoryQuery> query = getQuery(newCenterVersion, aboveCenterCount, belowCenterCount); |
| // TODO monitor |
| final List<ESHistoryInfo> infos = projectSpace.toAPI().getHistoryInfos(query.toAPI(), |
| new NullProgressMonitor()); |
| |
| final List<HistoryInfo> historyInfos = new ArrayList<HistoryInfo>(); |
| for (final ESHistoryInfo info : infos) { |
| historyInfos.add(((ESHistoryInfoImpl) info).toInternalAPI()); |
| } |
| |
| if (newCenterVersion != null && !currentCenterVersionShown.equals(newCenterVersion)) { |
| setCorrectCenterVersionAndHistory(historyInfos, newCenterVersion.getIdentifier(), beforeCurrent); |
| } else { |
| currentlyPresentedInfos = cutInfos(historyInfos, |
| findPositionOfId(currentCenterVersionShown.getIdentifier(), historyInfos)); |
| } |
| prevPage = false; |
| nextPage = false; |
| return currentlyPresentedInfos; |
| } |
| |
| /** |
| * Set correct center version and displayed History infos. 1) prev page: |
| * check if there are enough previous versions if not: set centerVersion |
| * further down to retrieve more lower versions 2) next page : similiar to |
| * prev page |
| * |
| * @param historyInfos |
| */ |
| private void setCorrectCenterVersionAndHistory(List<HistoryInfo> newQueryHistoryInfos, int newCenterVersionId, |
| int positionOfOldCenterInCurrentDisplay) { |
| final int idOfCurrentVersionShown = currentlyPresentedInfos.get(positionOfOldCenterInCurrentDisplay) |
| .getPrimarySpec() |
| .getIdentifier(); |
| int olderVersions = 0, newerVersions = 0; |
| int newCenterVersionPos = -1; |
| int oldCenterVersionPos = -1; |
| for (int i = 0; i < newQueryHistoryInfos.size(); i++) { |
| final int idOfI = newQueryHistoryInfos.get(i).getPrimarySpec().getIdentifier(); |
| if (idOfI > newCenterVersionId) { |
| ++newerVersions; |
| } else if (idOfI < newCenterVersionId) { |
| ++olderVersions; |
| } else if (idOfI == newCenterVersionId) { |
| // assert newCenterVersionPos == -1 : "Should not be in there twice."; |
| newCenterVersionPos = i; |
| } else if (idOfI == idOfCurrentVersionShown) { |
| assert oldCenterVersionPos == -1 : "Should not be in there twice."; |
| oldCenterVersionPos = i; |
| } |
| } |
| if (newCenterVersionPos == -1) { |
| newCenterVersionPos = Math.max(0, newerVersions - 1); |
| } |
| PrimaryVersionSpec newCenterVersion = newQueryHistoryInfos.get(newCenterVersionPos).getPrimarySpec(); |
| |
| assert prevPage ^ nextPage; |
| |
| if (prevPage && newerVersions < aboveCenterCount) { |
| final List<HistoryInfo> mergedInfos = mergeHistoryInfoLists(newQueryHistoryInfos, currentlyPresentedInfos); |
| final int oldCenterPos = findPositionOfId(currentCenterVersionShown.getIdentifier(), mergedInfos); |
| |
| // not enough versions: go further down, but never further than the old center as this is supposed to be |
| // prevPage and thus at the very least not nextPage |
| final int newCenterPos = Math |
| .min(Math.min(aboveCenterCount, oldCenterPos), newQueryHistoryInfos.size() - 1); |
| newCenterVersion = mergedInfos.get(newCenterPos).getPrimarySpec(); |
| currentlyPresentedInfos = cutInfos(mergedInfos, newCenterPos); |
| |
| } else if (nextPage && olderVersions < belowCenterCount) { |
| final List<HistoryInfo> mergedInfos = mergeHistoryInfoLists(newQueryHistoryInfos, currentlyPresentedInfos); |
| final int oldCenterPos = findPositionOfId(currentCenterVersionShown.getIdentifier(), mergedInfos); |
| |
| // not enough versions: go further up, but never further than the old center as this is supposed to be |
| // nextPage and thus at the very least not prevPage |
| final int newCenterPos = Math.max(Math.max(mergedInfos.size() - 1 - belowCenterCount, oldCenterPos), 0); |
| newCenterVersion = mergedInfos.get(newCenterPos).getPrimarySpec(); |
| currentlyPresentedInfos = cutInfos(mergedInfos, newCenterPos); |
| |
| } else { |
| newCenterVersion = newQueryHistoryInfos.get(newCenterVersionPos).getPrimarySpec(); |
| currentlyPresentedInfos = cutInfos(newQueryHistoryInfos, newCenterVersionPos); |
| } |
| currentCenterVersionShown = newCenterVersion; |
| |
| } |
| |
| private List<HistoryInfo> cutInfos(List<HistoryInfo> mergedInfos, int newCenterPos) { |
| final int smallestIndexIn = Math.max(0, newCenterPos - aboveCenterCount); |
| final int largestIndexIn = Math.min(mergedInfos.size() - 1, newCenterPos + belowCenterCount); |
| final List<HistoryInfo> cut = new ArrayList<HistoryInfo>(); |
| for (int i = smallestIndexIn; i <= largestIndexIn; i++) { |
| cut.add(mergedInfos.get(i)); |
| } |
| return cut; |
| } |
| |
| private int findPositionOfId(int identifier, List<HistoryInfo> mergedInfos) { |
| return findPositionOfId(identifier, mergedInfos, true); |
| } |
| |
| /** |
| * @param identifier |
| * @param infoList |
| * @param mustBeIn When true, an exception is thrown if the id is not in the list range. |
| * @return The index of the first version with id <= identifier (the first version in the list that is not newer). |
| */ |
| private int findPositionOfId(int identifier, List<HistoryInfo> infoList, boolean mustBeIn) { |
| for (int i = 0; i < infoList.size(); i++) { |
| if (getId(infoList.get(i)) <= identifier) { |
| return i; |
| } |
| } |
| if (mustBeIn) { |
| throw new IllegalArgumentException("Did not find version with id " + identifier + " but should be in."); |
| } |
| return -1; |
| |
| } |
| |
| private List<HistoryInfo> mergeHistoryInfoLists(List<HistoryInfo> newQueryHistoryInfos, |
| List<HistoryInfo> oldPresentedHistoryInfos) { |
| return newMerge(newQueryHistoryInfos, oldPresentedHistoryInfos); |
| |
| } |
| |
| private List<HistoryInfo> newMerge(List<HistoryInfo> newQuery, List<HistoryInfo> olderVersions) { |
| int newPos = 0, oldPos = 0; |
| |
| final List<HistoryInfo> merge = new ArrayList<HistoryInfo>(); |
| while (newPos < newQuery.size() && oldPos < olderVersions.size()) { |
| if (getId(newQuery.get(newPos)) >= getId(olderVersions.get(oldPos))) { |
| merge.add(newQuery.get(newPos)); |
| newPos++; |
| } else { |
| merge.add(olderVersions.get(oldPos)); |
| oldPos++; |
| } |
| |
| } |
| |
| // add rest |
| assert newPos == newQuery.size() || oldPos == olderVersions.size(); |
| for (int i = newPos; i < newQuery.size(); i++) { |
| merge.add(newQuery.get(i)); |
| } |
| |
| for (int i = oldPos; i < olderVersions.size(); i++) { |
| merge.add(olderVersions.get(i)); |
| } |
| |
| // remove duplicates |
| final Iterator<HistoryInfo> iterator = merge.iterator(); |
| int prevId = Integer.MIN_VALUE; |
| while (iterator.hasNext()) { |
| final int currId = getId(iterator.next()); |
| if (currId == prevId) { |
| iterator.remove(); |
| } |
| prevId = currId; |
| } |
| return merge; |
| } |
| |
| private int getId(HistoryInfo info) { |
| return info.getPrimarySpec().getIdentifier(); |
| } |
| |
| /** |
| * |
| * @param centerVersion The query center version. |
| * @return |
| * @throws ESException |
| */ |
| private HistoryQuery getQuery(PrimaryVersionSpec centerVersion, int aboveCenter, int belowCenter) |
| throws ESException { |
| PrimaryVersionSpec version; |
| if (centerVersion != null) { |
| version = centerVersion; |
| } else { |
| version = projectSpace.getBaseVersion(); |
| currentCenterVersionShown = version; |
| } |
| |
| HistoryQuery query; |
| QueryMargins margins; |
| if (centerVersion != null && !centerVersion.getBranch().equals(projectBranch)) { |
| margins = getBranchAdaptedMargins(centerVersion, aboveCenter, belowCenter); |
| } else { |
| margins = new QueryMargins(); |
| margins.aboveCenter = aboveCenter; |
| margins.belowCenter = belowCenter; |
| margins.querySpec = version; |
| } |
| if (modelElement != null && !(modelElement instanceof ProjectSpace) |
| && projectSpace.getProject().contains(modelElement)) { |
| query = HistoryQueryBuilder.modelelementQuery(margins.querySpec, projectSpace.getProject() |
| .getModelElementId(modelElement), margins.aboveCenter, margins.belowCenter, showAllVersions, true); |
| } else { |
| query = HistoryQueryBuilder.rangeQuery(margins.querySpec, margins.aboveCenter, margins.belowCenter, |
| showAllVersions, !showAllVersions, !showAllVersions, true); |
| } |
| |
| return query; |
| } |
| |
| /** |
| * Helper functions for retrieving history info when the current margin info is of the wrong branch. |
| * |
| * @throws ESException |
| */ |
| private QueryMargins getBranchAdaptedMargins(PrimaryVersionSpec centerVersion, int aboveCenter, int belowCenter) |
| throws ESException { |
| final QueryMargins margins = new QueryMargins(); |
| centerVersion.setBranch(projectBranch); |
| margins.aboveCenter = aboveCenter; |
| margins.belowCenter = belowCenter; |
| |
| // currently always the biggest primary version of given branch which is equal or lower |
| // to the given versionSpec |
| // TODO monitor |
| final PrimaryVersionSpec nearestSpec = projectSpace.resolveVersionSpec(centerVersion, |
| new NullProgressMonitor()); |
| if (nearestSpec.getIdentifier() < centerVersion.getIdentifier()) { |
| margins.aboveCenter = aboveCenter + centerVersion.getIdentifier() - nearestSpec.getIdentifier() + 1; |
| } else if (nearestSpec.getIdentifier() > centerVersion.getIdentifier()) { |
| margins.belowCenter = belowCenter + nearestSpec.getIdentifier() - centerVersion.getIdentifier() + 1; |
| } |
| margins.querySpec = nearestSpec; |
| |
| return margins; |
| } |
| |
| /** |
| * Allows to switch between showing all history info items (across all branches) or just those relevant to the |
| * current project branch. |
| * |
| * @param allVersions if true versions across all branches are shown, otherwise only versions for the current branch |
| * including ancestor versions |
| */ |
| public void setShowAllVersions(boolean allVersions) { |
| showAllVersions = allVersions; |
| currentCenterVersionShown = null; |
| currentlyPresentedInfos.clear(); |
| } |
| |
| /** |
| * Swaps to the next page. Call {@link #retrieveHistoryInfos()} to retrieve |
| * the new page. |
| */ |
| public void nextPage() { |
| nextPage = true; |
| prevPage = false; |
| } |
| |
| /** |
| * Swaps to the previous page. Call {@link #retrieveHistoryInfos()} to |
| * retrieve the new page. |
| */ |
| public void previousPage() { |
| prevPage = true; |
| nextPage = false; |
| } |
| |
| /** |
| * Swaps to a page containing the specified version. Call {@link #retrieveHistoryInfos()} to |
| * retrieve the new page. |
| * |
| * @param id The identifier of the version to display. |
| * @throws ESException When an error occurs while retrieving versions from the server. |
| * |
| * @return true if a version range surrounding the id has been found, false otherwise. Note that the range does not |
| * necessarily contain the id, for example if only versions for a certain branch are shown. |
| */ |
| public boolean setVersion(int id) throws ESException { |
| prevPage = false; |
| nextPage = false; |
| if (currentlyPresentedInfos.isEmpty() || currentCenterVersionShown == null) { |
| return false; |
| } |
| |
| final int newestVersion = getId(currentlyPresentedInfos.get(0)); |
| final int oldestVersion = getId(currentlyPresentedInfos.get(currentlyPresentedInfos.size() - 1)); |
| // |
| // List<HistoryInfo> currentHistoryInfosBU = currentlyPresentedInfos; |
| // PrimaryVersionSpec currentCenterBU = currentCenterVersionShown; |
| |
| if (newestVersion >= id && id >= oldestVersion) { |
| return true; // already there |
| } |
| |
| final HistoryQuery<ESHistoryQuery> query = getQuery(Versions.createPRIMARY(projectSpace.getBaseVersion(), id), |
| aboveCenterCount |
| + belowCenterCount, aboveCenterCount + belowCenterCount); |
| // TODO: monitor |
| final List<ESHistoryInfo> infos = projectSpace.toAPI().getHistoryInfos(query.toAPI(), |
| new NullProgressMonitor()); |
| final List<HistoryInfo> historyInfos = new ArrayList<HistoryInfo>(); |
| for (final ESHistoryInfo info : infos) { |
| historyInfos.add(((ESHistoryInfoImpl) info).toInternalAPI()); |
| } |
| int requestedIdPos = findPositionOfId(id, historyInfos, false); |
| final boolean contained = containsId(historyInfos, id); |
| int newCenterPos; |
| if (!contained) { |
| return false; |
| } |
| // The id is at least in the returned range |
| if (requestedIdPos != -1) { |
| // the id is really in there |
| newCenterPos = getPosForIdTakingAboveBelowIntoAccount(historyInfos, requestedIdPos); |
| } else { |
| // id in range but not in |
| requestedIdPos = findPositionOfId(id, historyInfos); |
| newCenterPos = getPosForIdTakingAboveBelowIntoAccount(historyInfos, requestedIdPos); |
| } |
| currentCenterVersionShown = historyInfos.get(newCenterPos).getPrimarySpec(); |
| currentlyPresentedInfos = cutInfos(historyInfos, newCenterPos); |
| |
| return true; |
| } |
| |
| private int getPosForIdTakingAboveBelowIntoAccount(List<HistoryInfo> infoList, int pos) { |
| int newCenterPos; |
| if (pos + belowCenterCount > infoList.size()) { |
| newCenterPos = Math.max(0, infoList.size() - belowCenterCount - 1); |
| } else if (pos - aboveCenterCount < 0) { |
| newCenterPos = Math.min(infoList.size() - 1, aboveCenterCount); |
| } else { |
| newCenterPos = pos; |
| } |
| return newCenterPos; |
| } |
| |
| private boolean containsId(List<HistoryInfo> infos, int id) { |
| final int newestVersion = getId(infos.get(0)); |
| final int oldestVersion = getId(infos.get(infos.size() - 1)); |
| |
| if (newestVersion >= id && id >= oldestVersion) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Helper class containing parameters for history query. |
| */ |
| private class QueryMargins { |
| private int belowCenter; |
| private int aboveCenter; |
| private PrimaryVersionSpec querySpec; |
| } |
| } |