/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * 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: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.db.common; import gnu.trove.map.TLongObjectMap; import gnu.trove.map.hash.TLongObjectHashMap; import java.lang.ref.WeakReference; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import org.simantics.db.Operation; import org.simantics.db.UndoContext; import org.simantics.db.common.utils.Logger; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.NoHistoryException; import org.simantics.db.service.ExternalOperation; import org.simantics.db.service.UndoRedoSupport; import org.simantics.db.service.UndoRedoSupport.ChangeListener; import org.simantics.utils.threads.ThreadUtils; public class UndoContextEx implements UndoContext { private static class Weak { private final WeakReference reference; Weak(UndoContext context) { reference = new WeakReference(context); } UndoContext get() { return reference.get(); } } private static class Operations { private final Deque operationsQue = new ArrayDeque(); private final TLongObjectMap operationsMap = new TLongObjectHashMap(); void addLast(Operation op) { long id = op.getId(); Operation old = operationsMap.put(id, op); if (old != null) { operationsQue.remove(old); op.combine(old); } operationsQue.addLast(op); fireChangeEvent(); } Operation getLast() { if (operationsQue.size() < 1) return null; Operation op = operationsQue.getLast(); if (DEBUG) System.out.println("DEBUG: Get last id=" + op.getId() + "."); return op; } Collection getAll() { return operationsQue; } Operation getCombined(long id) { return operationsMap.get(id); } Operation removeLast() { Operation op = operationsQue.pollLast(); if (null != op) operationsMap.remove(op.getId()); fireChangeEvent(); return op; } Operation remove(long id) { Operation op = operationsMap.remove(id); if (null != op) operationsQue.remove(op); fireChangeEvent(); return op; } void clear() { operationsQue.clear(); operationsMap.clear(); fireChangeEvent(); } private final CopyOnWriteArrayList changeListeners = new CopyOnWriteArrayList(); private void fireChangeEvent() { final Iterator it = changeListeners.iterator(); ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() { @Override public void run() { while (it.hasNext()) { ChangeListener l = it.next(); try { l.onChanged(); } catch (Throwable t) { Logger.defaultLogError(t); } } } }); } void addChangeListener(ChangeListener cl) { changeListeners.add(cl); } void removeChangeListener(ChangeListener cl) { changeListeners.remove(cl); } } private static HashMap contexts = new HashMap(); protected static final boolean DEBUG = false; protected final boolean DISABLED = false; private final Operations operations = new Operations(); private final Deque redos = new ArrayDeque(); private final ArrayList pendingExternals = new ArrayList(); private final String name; private boolean undoRedoOn = false; private final String id; private boolean commitOkDisabled = false; public UndoContextEx() { this.id = getUniqueId(this.toString()); this.name = id; init(); } public UndoContextEx(String id) { this.id = getUniqueId(id); this.name = id; init(); } public UndoContextEx(String id, String name) { this.id = getUniqueId(id); this.name = name; init(); } private String getUniqueId(String id) { String uniqueId = id; int i=0; while (contexts.containsKey(uniqueId)) { uniqueId += "_" + ++i; } return uniqueId; } private void init() { synchronized (this) { Weak old = contexts.get(id); assert(null == old); contexts.put(id, new Weak(this)); } } public static synchronized UndoContext getUndoContext(String id) { Weak w = contexts.get(id); UndoContext c = null == w ? null : w.get(); return c; } public static synchronized Collection getUndoContexts() { Collection ucs = new ArrayList(); List weaks = new ArrayList(); List stale = new ArrayList(); for (Map.Entry e : contexts.entrySet()) { UndoContext c = e.getValue().get(); if (null != c) { ucs.add(c); weaks.add(new Weak(c)); } else stale.add(e.getKey()); } for (String id : stale) contexts.remove(id); return ucs; } @Override public String toString() { return name + "@" + Integer.toHexString(hashCode()); } @Override public void commitOk(Operation op) throws DatabaseException { if (DISABLED) return; if (commitOkDisabled) { if (DEBUG) System.out.println("DEBUG: Commit ok disabled."); return; } if (DEBUG) System.out.println("DEBUG: Commit ok id=" + op.getId() + " cs=" + op.getCSId() + " context=" + this + "."); if (undoRedoOn) { undoRedoOn = false; redos.clear(); } // Check if operation is new. if (op.getId() < 1 || op.getId() == op.getCSId()) { Operation last = getLast(); if (null != last && last.getCSId() == op.getCSId()) { Logger.defaultLogError("Duplicate operation for new operation id=" + op.getId() + "."); if (DEBUG) System.out.println("DEBUG: New operation already in undo list id=" + op.getId() + "."); return; } if (DEBUG) System.out.println("DEBUG: New operation added to undo list id=" + op.getId() + "."); } else { // Operation is old i.e. part of combined operation. Operation last = operations.getCombined(op.getId()); if (null == last) { Logger.defaultLogError("Missing operation for combined operation id=" + op.getId() + " cs=" + op.getCSId() + "."); if (DEBUG) System.out.println("DEBUG: Missing old combined operation id=" + op.getId() + "."); } } operations.addLast(op); pendingExternals.clear(); } public void externalCommit(Operation op) { operations.addLast(op); pendingExternals.clear(); } public void cancelCommit() { pendingExternals.clear(); } @Override public Operation getLast() throws DatabaseException { return operations.getLast(); } protected Operation getLastRedo() throws DatabaseException { if (redos.size() < 1) return null; Operation op = redos.getLast(); if (DEBUG) System.out.println("DEBUG: Get last redo id=" + op.getId() + "."); return op; } @Override public Collection getAll() throws DatabaseException { return operations.getAll(); } @Override public Collection getRedoList() throws DatabaseException { return redos; } @Override public List undo(UndoRedoSupport support, int count) throws DatabaseException { List ops = new ArrayList(count); for (int i=0; i redo(UndoRedoSupport support, int count) throws DatabaseException { List ops = new ArrayList(count); for (int i=0; i getPendingExternals() { if(pendingExternals.isEmpty()) return Collections.emptyList(); if(!pendingExternals.isEmpty()) System.err.println("pending externals: " + pendingExternals); return new ArrayList(pendingExternals); } }