/* * Copyright (c) 2012, 2015, 2016 Eike Stepper (Berlin, Germany) and others. * 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: * Eike Stepper - initial API and implementation */ package org.eclipse.emf.cdo.internal.common.commit; import org.eclipse.emf.cdo.common.branch.CDOBranch; import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.commit.CDOChangeKind; import org.eclipse.emf.cdo.common.commit.CDOChangeSetData; import org.eclipse.emf.cdo.common.commit.CDOCommitHistory; import org.eclipse.emf.cdo.common.commit.CDOCommitInfo; import org.eclipse.emf.cdo.common.commit.CDOCommitInfoHandler; import org.eclipse.emf.cdo.common.commit.CDOCommitInfoManager; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.model.CDOPackageUnit; import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion; import org.eclipse.emf.cdo.common.revision.CDORevisionKey; import org.eclipse.emf.cdo.internal.common.bundle.OM; import org.eclipse.net4j.util.collection.GrowingRandomAccessList; import org.eclipse.net4j.util.container.Container; import org.eclipse.net4j.util.event.FinishedEvent; import org.eclipse.net4j.util.event.IListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.Map; /** * @author Eike Stepper * @since 4.2 */ public class CDOCommitHistoryImpl extends Container<CDOCommitInfo> implements CDOCommitHistory { private static final CDOCommitInfo[] NO_ELEMENTS = {}; private final TriggerLoadElement triggerLoadElement = new TriggerLoadElementImpl(); private final CDOCommitInfoManager manager; private final CDOBranch branch; private final GrowingRandomAccessList<CDOCommitInfo> commitInfos = new GrowingRandomAccessList<CDOCommitInfo>(CDOCommitInfo.class, DEFAULT_LOAD_COUNT); private final Object loadLock = new Object(); private int loadCount = DEFAULT_LOAD_COUNT; private CDOCommitInfo[] elements; private boolean appendingTriggerLoadElement; private boolean full; private boolean loading; public CDOCommitHistoryImpl(CDOCommitInfoManager manager, CDOBranch branch) { super(true); this.manager = manager; this.branch = branch; } public final CDOCommitInfoManager getManager() { return manager; } public final CDOBranch getBranch() { return branch; } public final int getLoadCount() { synchronized (loadLock) { return loadCount; } } public final void setLoadCount(int loadCount) { synchronized (loadLock) { this.loadCount = loadCount; } } public final boolean isAppendingTriggerLoadElement() { synchronized (loadLock) { return appendingTriggerLoadElement; } } public final void setAppendingTriggerLoadElement(boolean appendingTriggerLoadElement) { int event = 0; synchronized (loadLock) { if (this.appendingTriggerLoadElement != appendingTriggerLoadElement) { this.appendingTriggerLoadElement = appendingTriggerLoadElement; elements = null; if (!full) { if (appendingTriggerLoadElement) { event = 1; } else { event = 2; } } } } switch (event) { case 1: fireElementAddedEvent(triggerLoadElement); break; case 2: fireElementRemovedEvent(triggerLoadElement); break; } } public final CDOCommitInfo getFirstElement() { checkActive(); synchronized (loadLock) { if (loading) { if (elements == null) { return null; } return elements[0]; } if (commitInfos.isEmpty()) { return null; } return commitInfos.getFirst(); } } public final CDOCommitInfo getLastElement() { checkActive(); synchronized (loadLock) { if (loading) { if (elements == null) { return null; } return elements[elements.length - 1]; } if (commitInfos.isEmpty()) { return null; } return commitInfos.getLast(); } } public final CDOCommitInfo getElement(int index) { checkActive(); synchronized (loadLock) { if (loading) { if (elements == null) { return null; } return elements[index]; } return commitInfos.get(index); } } public final CDOCommitInfo[] getElements() { checkActive(); synchronized (loadLock) { if (loading) { if (elements == null) { return NO_ELEMENTS; } return elements; } if (elements == null) { int size = commitInfos.size(); if (!full && appendingTriggerLoadElement) { elements = commitInfos.toArray(new CDOCommitInfo[size + 1]); elements[size] = triggerLoadElement; } else { elements = commitInfos.toArray(new CDOCommitInfo[size]); } } return elements; } } public final int size() { checkActive(); synchronized (loadLock) { if (loading) { if (elements == null) { return 0; } return elements.length; } int size = commitInfos.size(); if (!full && appendingTriggerLoadElement) { ++size; } return size; } } @Override public final boolean isEmpty() { checkActive(); synchronized (loadLock) { if (loading) { if (elements == null) { return true; } return elements.length == 0; } if (!full && appendingTriggerLoadElement) { return false; } return commitInfos.isEmpty(); } } public final boolean isFull() { synchronized (loadLock) { return full; } } public final boolean isLoading() { synchronized (loadLock) { return loading; } } public final void waitWhileLoading(long timeout) { long end = System.currentTimeMillis() + timeout; synchronized (loadLock) { while (loading) { try { long remaining = end - System.currentTimeMillis(); if (remaining <= 0) { return; } loadLock.wait(remaining); } catch (InterruptedException ex) { return; } } } } public final void handleCommitInfo(CDOCommitInfo commitInfo) { synchronized (loadLock) { loading = true; } List<CDOCommitInfo> addedCommitInfos = Collections.emptyList(); try { if (addCommitInfo(commitInfo)) { addedCommitInfos = Collections.singletonList(commitInfo); } } finally { finishLoading(addedCommitInfos, null); } } public final boolean triggerLoad() { return triggerLoad(null); } public final boolean triggerLoad(final CDOCommitInfoHandler handler) { synchronized (loadLock) { if (full || loading) { return false; } loading = true; new Thread("CDOCommitHistoryLoader") { @Override public void run() { final List<CDOCommitInfo> addedCommitInfos = new ArrayList<CDOCommitInfo>(); try { loadCommitInfos(loadCount, addedCommitInfos); } catch (Throwable ex) { OM.LOG.error(ex); } finally { finishLoading(addedCommitInfos, handler); } } }.start(); } return true; } protected void loadCommitInfos(int loadCount, final List<CDOCommitInfo> addedCommitInfos) { final CDOCommitInfo[] lastCommitInfo = { getLastCommitInfo() }; long timeStamp = CDOBranchPoint.UNSPECIFIED_DATE; int delivered = 0; if (lastCommitInfo[0] == null) { timeStamp = manager.getLastCommitOfBranch(branch, false); } else { timeStamp = lastCommitInfo[0].getPreviousTimeStamp(); } // ============================================== // Fill history with already loaded commit infos. // ============================================== if (timeStamp != CDOBranchPoint.UNSPECIFIED_DATE) { CDOCommitInfo commitInfo; while ((commitInfo = manager.getCommitInfo(timeStamp, false)) != null) { if (addCommitInfo(commitInfo)) { addedCommitInfos.add(commitInfo); lastCommitInfo[0] = commitInfo; ++delivered; } if (commitInfo.isInitialCommit()) { setFull(); break; } timeStamp = commitInfo.getPreviousTimeStamp(); } } // ================================================================ // Load commit infos from server if load count is not reached, yet. // ================================================================ if (delivered < loadCount && !isFull()) { final int[] loaded = { 0 }; // Used to detect premature end of loading. manager.getCommitInfos(branch, timeStamp, null, null, -loadCount, new CDOCommitInfoHandler() { public void handleCommitInfo(CDOCommitInfo commitInfo) { ++loaded[0]; if (addCommitInfo(commitInfo)) { addedCommitInfos.add(commitInfo); lastCommitInfo[0] = commitInfo; } } }); if (loaded[0] < loadCount) { setFull(); } else { // ================================================== // Complete history with already loaded commit infos. // ================================================== if (lastCommitInfo[0] != null) { long previousTimeStamp = lastCommitInfo[0].getPreviousTimeStamp(); while ((lastCommitInfo[0] = manager.getCommitInfo(previousTimeStamp, false)) != null) { if (addCommitInfo(lastCommitInfo[0])) { addedCommitInfos.add(lastCommitInfo[0]); } if (lastCommitInfo[0].isInitialCommit()) { setFull(); break; } previousTimeStamp = lastCommitInfo[0].getPreviousTimeStamp(); } } } } if (appendingTriggerLoadElement && !isFull()) { addedCommitInfos.add(triggerLoadElement); } } protected boolean filterCommitInfo(CDOCommitInfo commitInfo) { return branch != null && branch != commitInfo.getBranch(); } protected final boolean addCommitInfo(CDOCommitInfo commitInfo) { checkLoading(); if (commitInfo == null || filterCommitInfo(commitInfo)) { return false; } long timeStamp = commitInfo.getTimeStamp(); if (commitInfos.isEmpty() || timeStamp > commitInfos.getFirst().getTimeStamp()) { commitInfos.addFirst(commitInfo); } else if (timeStamp < commitInfos.getLast().getTimeStamp()) { commitInfos.addLast(commitInfo); } else { for (ListIterator<CDOCommitInfo> it = commitInfos.listIterator(); it.hasNext();) { CDOCommitInfo current = it.next(); long currentTimeStamp = current.getTimeStamp(); if (timeStamp == currentTimeStamp) { // Ignore duplicate commit infos. return false; } if (timeStamp < currentTimeStamp) { it.add(commitInfo); break; } } } return true; } protected final void deliverFinish(List<CDOCommitInfo> addedCommitInfos, CDOCommitInfoHandler handler) { if (handler instanceof IListener) { IListener listener = (IListener)handler; listener.notifyEvent(new FinishedEvent(addedCommitInfos)); } } protected final void setFull() { full = true; } @Override protected void doActivate() throws Exception { super.doActivate(); manager.addCommitInfoHandler(this); } @Override protected void doAfterActivate() throws Exception { super.doAfterActivate(); triggerLoad(); } @Override protected void doDeactivate() throws Exception { manager.removeCommitInfoHandler(this); super.doDeactivate(); } private void checkLoading() { if (!loading) { throw new IllegalStateException("Not loading"); } } private void finishLoading(final List<CDOCommitInfo> addedCommitInfos, final CDOCommitInfoHandler handler) { synchronized (loadLock) { elements = null; loading = false; loadLock.notifyAll(); } if (!addedCommitInfos.isEmpty()) { fireElementsAddedEvent(addedCommitInfos.toArray(new CDOCommitInfo[addedCommitInfos.size()])); if (handler != null) { for (CDOCommitInfo commitInfo : addedCommitInfos) { handler.handleCommitInfo(commitInfo); } deliverFinish(addedCommitInfos, handler); } } } private CDOCommitInfo getLastCommitInfo() { checkLoading(); if (commitInfos.isEmpty()) { return null; } return commitInfos.getLast(); } /** * @author Eike Stepper */ private final class TriggerLoadElementImpl implements TriggerLoadElement { public CDOCommitHistory getHistory() { return CDOCommitHistoryImpl.this; } public CDOCommitInfoManager getCommitInfoManager() { return manager; } public CDOBranch getBranch() { return null; } public long getTimeStamp() { return CDOBranchPoint.UNSPECIFIED_DATE; } public long getPreviousTimeStamp() { return CDOBranchPoint.UNSPECIFIED_DATE; } public CDOCommitInfo getPreviousCommitInfo() { return null; } public String getUserID() { return null; } public String getComment() { return "Load more history elements"; } public CDOBranchPoint getMergeSource() { return null; } public CDOCommitInfo getMergedCommitInfo() { return null; } public boolean isEmpty() { return true; } public boolean isInitialCommit() { return false; } public boolean isCommitDataLoaded() { return true; } public List<CDOPackageUnit> getNewPackageUnits() { return null; } public List<CDOIDAndVersion> getNewObjects() { return null; } public List<CDORevisionKey> getChangedObjects() { return null; } public List<CDOIDAndVersion> getDetachedObjects() { return null; } public Map<CDOID, CDOChangeKind> getChangeKinds() { return null; } public CDOChangeKind getChangeKind(CDOID id) { return null; } public void merge(CDOChangeSetData changeSetData) { } public CDOChangeSetData copy() { return null; } } /** * @author Eike Stepper */ public static final class Empty extends Container<CDOCommitInfo> implements CDOCommitHistory { public CDOCommitInfo[] getElements() { return NO_ELEMENTS; } public void handleCommitInfo(CDOCommitInfo commitInfo) { } public CDOCommitInfoManager getManager() { return null; } public CDOBranch getBranch() { return null; } public boolean isAppendingTriggerLoadElement() { return false; } public void setAppendingTriggerLoadElement(boolean appendingTriggerLoadElement) { } public CDOCommitInfo getFirstElement() { return null; } public CDOCommitInfo getLastElement() { return null; } public CDOCommitInfo getElement(int index) { return null; } public int size() { return 0; } public boolean isLoading() { return false; } public void waitWhileLoading(long timeout) { } public int getLoadCount() { return 0; } public void setLoadCount(int loadCount) { } public boolean triggerLoad() { return false; } public boolean triggerLoad(CDOCommitInfoHandler handler) { return false; } public boolean isFull() { return true; } } }