/*
* Copyright 2008, Plutext Pty Ltd.
*
* This file is part of Docx4all.
Docx4all is free software: you can redistribute it and/or modify
it under the terms of version 3 of the GNU General Public License
as published by the Free Software Foundation.
Docx4all is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Docx4all. If not, see <http://www.gnu.org/licenses/>.
*/
package org.plutext.client;
import java.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.plutext.client.diffengine.DiffEngine;
import org.plutext.client.diffengine.DiffResultSpan;
/* This class keeps track of the divergences between
* 2 document states.
*
* Its primary purpose is to make it possible to
* adjust the position for inserts/moves.
*
* One use is for applying server transforms.
*
* In this case, you want to be able to make allowance
* for any local insertions/
* deletions which haven't yet been transmitted to
* the server, or if they have, have/will come back with
* a sequence number which we haven't processed yet.
*
* The other use is calculating positions to be
* included in insert|move transforms to be
* transmitted to the server. (Each transform has
* to be relative to the state the preceding transform
* would produce).
*
* The class is basically an ordered list of sdt id's,
* each of which has associated with it an int:
*
* -1 : this sdt not present (or deleted) on LHS
* 0 : no change
* +1 : this sdt inserted on LHS
*
* (In the case of a move, there will be two entries,
* one with +1, and one with -1)
*
* As updates are applied, we have to keep this object
* up to date. Insert transforms result in
* new entries in the appropriate position, but their
* associated int is *always 0*.
*
* If the thing being inserted is already present
* (ie the transform insert is reflecting a change
* local in origin), then the relevant int just changes
* from 1 to 0.
*
* Delete transforms result in the deletion of the appropriate entry.
*/
public class Divergences
{
private static Logger log = LoggerFactory.getLogger(Divergences.class);
boolean debug = true;
ArrayList<Entry> entries = null; // Model as a list, since a move will be 2 entries
public Divergences(DiffEngine de) //, Skeleton source, Skeleton destination) //, ArrayList DiffLines)
{
ArrayList<DiffResultSpan> DiffLines = de.getDiffLines();
Skeleton source = (Skeleton) de.getSource();
Skeleton destination = (Skeleton) de.getDestination();
entries = new ArrayList<Entry>();
String result = "";
int i;
for (DiffResultSpan drs : DiffLines)
{
switch (drs.getDiffResultSpanStatus())
{
case DELETE_SOURCE:
for (i = 0; i < drs.getLength(); i++)
{
result += "\n" + ((TextLine)source.getByIndex(drs.getSourceIndex() + i)).getLine()
+ " not at this location in dest, so will add ";
entries.add(
new Entry( ((TextLine)source.getByIndex(drs.getSourceIndex() + i)).getLine(),
+1));
}
break;
case NOCHANGE:
for (i = 0; i < drs.getLength(); i++)
{
result += "\n" + ((TextLine)source.getByIndex(drs.getSourceIndex() + i)).getLine()
+ "\t" + ((TextLine)destination.getByIndex(drs.getDestIndex() + i)).getLine() + " (no change)";
entries.add(
new Entry( ((TextLine)destination.getByIndex(drs.getDestIndex() + i)).getLine(),
0) ); // source = dest
}
break;
case ADD_DESTINATION:
for (i = 0; i < drs.getLength(); i++)
{
result += "\n" + "---" + "\t" + ((TextLine)destination.getByIndex(drs.getDestIndex() + i)).getLine()
+ " not at this location in source, so though currently present in dest, will subtract ";
entries.add(
new Entry( ((TextLine)destination.getByIndex(drs.getDestIndex() + i)).getLine(),
-1) );
}
break;
}
}
log.debug( result );
log.debug("Divergences object set up");
}
/* As updates are applied, we have to keep this object
* up to date. Insert transforms result in
* a +1 entry changing to 0 (to signify source and
* dest are now the same).
*/
public void insert(String id)
{
log.debug("Insert " + id + " entry in divergences ");
for (Entry e : entries)
{
if (e.getSdtId().equals(id))
{
if (e.getAdj() == 1)
{
e.setAdj(0);
log.debug("OK");
return;
} else {
/* SPECIAL CASE =-0: this is
* marked as no change. Shouldn't
* happen, since any transforms local
* in origin won't be formally applied.
*
* SPECIAL CASE =-1: this is
* marked as deleted
* In a move case, we wouldn't expect
* to encounter this, since we do the
* delete before the insert (so the -1
* would be gone).
*
* In case you are wondering, a reinstate
* looks just like an
* insert (ie there won't be a -1 entry)
*
*/
log.debug("ERROR: detected unexpected " + e.getAdj() + " entry for " + id);
return;
}
}
}
}
/* As updates are applied, we have to keep this object
* up to date. Insert transforms ordinarily result in
* a +1 entry changing to 0 (to signify source and
* dest are now the same).
*
* However, where this divergence object represents
* the differences between an old server skeleton,
* and the current local actuals, and what we are
* trying to do is maintain its state as new
* transforms (from the server) are applied, we have
* to do some work to ensure the insertion occurs
* in the correct position.
*
*/
public void insert(String id, Long pos)
{
log.debug("Insert " + id + " entry in divergences, at explicit pos " + pos);
// First, get rid of any existing +1 entry
for (Entry e : entries)
{
if (e.getSdtId().equals(id))
{
if (e.getAdj() == 1)
{
entries.remove(e);
log.debug("Removed existing +1 entry");
break;
}
}
}
// Now, add a 0 entry at the correct location
// Correct location being the list index
// of pos live entries
int live = 0;
int index = 0;
for (Entry e : entries)
{
if (live == pos) // will the conversion be done automatically?
{
break;
}
if (e.getAdj() <= 0) // live in dest (ie actual local)
{
live++;
}
index++;
}
//
log.debug(".. which is " + index + "th in divergences");
entries.add(index, new Entry(id, 0)); // 0, since this entry
// is now a "no change" against the server.
}
public void delete(String id)
{
log.debug("Removing " + id + " from divergences");
//log.debug("\n\r Before ");
//foreach (Entry e2 in entries)
//{
// log.debug(e2.SdtId + "\t" + e2.Adj);
//}
boolean result = false;
//List<Entry> deletions = new List<Entry>();
for (Entry e : entries)
{
if (e.getSdtId().equals(id) )
{
log.debug("Found" + id + " with value " + e.getAdj());
// We expect Adj = -1.
// But in the 3 way case (ie applying remote edits)
// it may be a zero entry
if (e.getAdj() <= 0)
{
// Should be the only one
entries.remove(e);
return;
}
}
}
//foreach (Entry e in deletions)
//{
// result = entries.Remove(e);
// log.debug("and removing ..." + result);
//}
log.debug("Couldn't find " + id + " to remove !!!");
//log.debug("\n\r After ");
//foreach (Entry e2 in entries)
//{
// log.debug(e2.SdtId + "\t" + e2.Adj);
//}
}
/* Find the location for this move/insertion. */
public int getTargetLocation(String id)
{
/* Want to count the number of
* preceding live locations in dest.
* These are entries with either
* a zero or -1 value. */
log.debug("Looking for id " + id);
log.debug("in ");
for (Entry e2 : entries)
{
log.debug(e2.getSdtId() + "\t" + e2.getAdj());
}
int count = 0;
for (Entry e : entries)
{
if (e.getSdtId().equals(id))
{
return count;
}
if (e.getAdj() <= 0)
{
count++;
}
}
log.debug("ERROR! Couldn't find insertion point for id " + id);
return -1;
}
/* Given an absolute position, return the adjustment necessary
* to insert in the correct spot in this document.
*
* In this case:
* - the LHS is a previous server state applied
* on the client.
* - the RHS is the current client state
*
* Progressively applying transforms to the
* current client state will move it towards
* the new server state (while preserving local
* client changes).
*
* What we are looking to do is to calculate the
* changes which are necessary to move|insert positions
* received from server, to make allowance for
* local changes.
*/
public Long getOffset(Long pos)
{
// Each LHS deletion in an earlier pos means subtract one
// Each LHS insertion in an earlier pos means add one
// Go through entries until we have counted pos entries
// which are live in the LHS (ie have with *zero* or +1 value)
// Along the way, sum the pluses and minues,
// and return that sum.
log.debug("Looking for offset " + pos);
log.debug("in ");
for (Entry e2 : entries)
{
log.debug(e2.getSdtId() + "\t" + e2.getAdj());
}
int count = 0;
int result = 0;
for (Entry e : entries)
{
if (count == pos)
{
return Long.valueOf(result);
}
if (e.getAdj() >= 0)
{
count++;
}
result -= e.getAdj();
}
// Hmm, ran out of entries.
// eg if all n sdts were deleted, we'd get here
// .. which is ok.
log.debug("Mildly noteworthy: ran out of Divergences entries! Returning " + result);
return Long.valueOf(result);
}
public int currentPosition(String sdtId)
{
int count = 0;
for (Entry e : entries)
{
if (e.getSdtId().equals(sdtId) )
{
return count;
}
count++;
}
return count;
}
public void debugInferred()
{
log.debug("\n\r Currently inferred skeleton" );
int count = 0;
for (Entry e : entries)
{
if (e.getAdj() >= 0)
{
log.debug(count + " : " + e.getSdtId());
count++;
}
}
}
class Entry {
public Entry(String sdtId, int adj)
{
this.sdtId = sdtId;
this.adj = adj;
}
String sdtId;
public String getSdtId() {
return sdtId;
}
// -1, 0, or +1
int adj;
public int getAdj() {
return adj;
}
public void setAdj(int adj) {
this.adj = adj;
}
}
}