/*
JMeld is a visual diff and merge tool.
Copyright (C) 2007 Kees Kuip
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA
*/
package org.jmeld.ui.text;
import org.jmeld.JMeldException;
import org.jmeld.util.StopWatch;
import org.jmeld.util.StringUtil;
import org.jmeld.vc.BlameIF;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public abstract class AbstractBufferDocument implements BufferDocumentIF, DocumentListener {
private String name;
private String shortName;
private Line[] lineArray;
private int[] lineOffsetArray;
private PlainDocument document;
private MyGapContent content;
private List<BufferDocumentChangeListenerIF> listeners;
// Variables to detect if this document has been changed (and needs to be saved!)
private boolean changed;
private int originalLength;
private int digest;
public AbstractBufferDocument() {
listeners = new ArrayList<BufferDocumentChangeListenerIF>();
}
public void addChangeListener(BufferDocumentChangeListenerIF listener) {
listeners.add(listener);
}
public void removeChangeListener(BufferDocumentChangeListenerIF listener) {
listeners.remove(listener);
}
abstract int getBufferSize();
abstract public Reader getReader()
throws JMeldException;
abstract Writer getWriter()
throws JMeldException;
protected void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
protected void setShortName(String shortName) {
this.shortName = shortName;
}
public String getShortName() {
return shortName;
}
public PlainDocument getDocument() {
return document;
}
public boolean isChanged() {
return changed;
}
public Line[] getLines() {
initLines();
return lineArray;
}
public String getLineText(int lineNumber) {
Line[] la;
la = getLines();
if (la == null) {
return null;
}
if (lineNumber >= la.length || lineNumber < 0) {
return "<NO LINE>";
}
return la[lineNumber].toString();
}
public int getNumberOfLines() {
return getLines().length;
}
public int getOffsetForLine(int lineNumber) {
Line[] la;
if (lineNumber < 0) {
return -1;
}
if (lineNumber == 0) {
return 0;
}
la = getLines();
if (la == null) {
return -1;
}
if (lineNumber > la.length) {
lineNumber = la.length;
}
return la[lineNumber - 1].getOffset();
}
public int getLineForOffset(int offset) {
int searchIndex;
Line[] la;
if (offset < 0) {
return 0;
}
la = getLines();
if (la == null) {
return 0;
}
if (offset >= lineOffsetArray[lineOffsetArray.length - 1]) {
return lineOffsetArray.length - 1;
}
searchIndex = Arrays.binarySearch(lineOffsetArray, offset);
if (searchIndex >= 0) {
return searchIndex + 1;
}
return (-searchIndex) - 1;
}
public void read()
throws JMeldException {
try {
Reader reader;
StopWatch stopWatch;
if (document != null) {
document.removeDocumentListener(this);
}
stopWatch = new StopWatch();
stopWatch.start();
System.out.println("before read : " + this);
content = new MyGapContent(getBufferSize() + 500);
document = new PlainDocument(content);
reader = getReader();
new DefaultEditorKit().read(reader, document, 0);
reader.close();
System.out.println("create document took " + stopWatch.getElapsedTime());
document.addDocumentListener(this);
reset();
initLines();
initDigest();
} catch (JMeldException ex) {
throw ex;
} catch (Exception ex) {
throw new JMeldException("Problem reading document (name=" + getName()
+ ") in buffer", ex);
}
}
private void initLines() {
Element paragraph;
Element e;
int size;
Line line;
if (lineArray != null) {
return;
}
paragraph = document.getDefaultRootElement();
size = paragraph.getElementCount();
//lineArray = new Line[size - 1];
lineArray = new Line[size];
lineOffsetArray = new int[lineArray.length];
for (int i = 0; i < lineArray.length; i++) {
e = paragraph.getElement(i);
line = new Line(e);
lineArray[i] = line;
lineOffsetArray[i] = line.getOffset();
}
}
public void reset() {
lineArray = null;
lineOffsetArray = null;
}
public void write()
throws JMeldException {
Writer out;
System.out.println("write : " + getName());
try {
out = getWriter();
new DefaultEditorKit().write(out, document, 0, document.getLength());
out.flush();
out.close();
initDigest();
} catch (JMeldException ex) {
throw ex;
} catch (Exception ex) {
throw new JMeldException("Problem writing document (name=" + getName()
+ ") from buffer", ex);
}
}
class MyGapContent
extends GapContent {
public MyGapContent(int length) {
super(length);
}
char[] getCharArray() {
return (char[]) getArray();
}
public char getChar(int offset) {
int g0;
int g1;
g0 = getGapStart();
g1 = getGapEnd();
if (offset >= g0) {
// Take into account the gap!
// This offset is above the gap.
offset = g1 + offset - g0;
}
return getCharArray()[offset];
}
public boolean equals(MyGapContent c2, int start1, int end1, int start2) {
char[] array1;
char[] array2;
int g1_0;
int g1_1;
int g2_0;
int g2_1;
int size;
int o1;
int o2;
array1 = getCharArray();
array2 = c2.getCharArray();
g1_0 = getGapStart();
g1_1 = getGapEnd();
g2_0 = c2.getGapStart();
g2_1 = c2.getGapEnd();
if (start1 >= g1_0) {
o1 = start1 + g1_1 - g1_0;
} else {
o1 = start1;
}
if (start2 >= g2_0) {
o2 = start2 + g2_1 - g2_0;
} else {
o2 = start2;
}
size = end1 - start1;
for (int i = 0; i < size; i++, o1++, o2++) {
if (o1 == g1_0) {
o1 += g1_1 - g1_0;
}
if (o2 == g2_0) {
o2 += g2_1 - g2_0;
}
if (array1[o1] != array2[o2]) {
return false;
}
}
return true;
}
public int hashCode(int start, int end) {
char[] array;
int g0;
int g1;
int size;
int h;
int o;
h = 0;
array = getCharArray();
g0 = getGapStart();
g1 = getGapEnd();
// Mind the gap!
if (start >= g0) {
o = start + g1 - g0;
} else {
o = start;
}
size = end - start;
for (int i = 0; i < size; i++, o++) {
// Mind the gap!
if (o == g0) {
o += g1 - g0;
}
h = 31 * h + array[o];
}
if (h == 0) {
h = 1;
}
return h;
}
public int getDigest() {
return hashCode(0, document.getLength());
}
}
public class Line
implements Comparable {
Element element;
Line(Element element) {
this.element = element;
}
MyGapContent getContent() {
return content;
}
public int getOffset() {
return element.getEndOffset();
}
public void print() {
System.out.printf("[%08d]: %s\n", getOffset(), StringUtil
.replaceNewLines(toString()));
}
@Override
public boolean equals(Object o) {
Element element2;
Line line2;
int start1;
int start2;
int end1;
int end2;
if (!(o instanceof Line)) {
return false;
}
line2 = ((Line) o);
element2 = line2.element;
start1 = element.getStartOffset();
end1 = element.getEndOffset();
start2 = element2.getStartOffset();
end2 = element2.getEndOffset();
// If the length is different the element is not equal!
if ((end1 - start1) != (end2 - start2)) {
return false;
}
return content.equals(line2.getContent(), start1, end1, start2);
}
@Override
public int hashCode() {
return content.hashCode(element.getStartOffset(), element.getEndOffset());
}
@Override
public String toString() {
try {
return content.getString(element.getStartOffset(),
element.getEndOffset() - element.getStartOffset());
} catch (Exception ex) {
ex.printStackTrace();
return "";
}
}
public int compareTo(Object line) {
return toString().compareTo(((Line) line).toString());
}
}
public void print() {
Line[] la;
la = getLines();
if (la != null) {
for (int lineNumber = 0; lineNumber < la.length; lineNumber++) {
System.out.printf("[%05d]", lineNumber);
la[lineNumber].print();
}
}
}
public void changedUpdate(DocumentEvent de) {
documentChanged(de);
}
public void insertUpdate(DocumentEvent de) {
documentChanged(de);
}
public void removeUpdate(DocumentEvent de) {
documentChanged(de);
}
private void initDigest() {
originalLength = document != null ? document.getLength() : 0;
digest = createDigest();
changed = false;
fireDocumentChanged(new JMDocumentEvent(this));
}
public int createDigest() {
return content.getDigest();
}
private void documentChanged(DocumentEvent de) {
boolean newChanged;
int newDigest;
int startLine;
int numberOfLinesChanged;
JMDocumentEvent jmde;
String text;
jmde = new JMDocumentEvent(this, de);
numberOfLinesChanged = 0;
if (lineArray != null) {
// Make large documents perform well!
if (de.getType() == DocumentEvent.EventType.INSERT) {
try {
text = document.getText(de.getOffset(), de.getLength());
} catch (BadLocationException ex) {
}
}
numberOfLinesChanged = getLines().length;
reset();
numberOfLinesChanged = getLines().length - numberOfLinesChanged;
startLine = getLineForOffset(de.getOffset() + 1);
if (startLine < 0) {
System.out.println("haha");
}
jmde.setStartLine(startLine);
jmde.setNumberOfLines(numberOfLinesChanged);
}
newChanged = false;
if (document.getLength() != originalLength) {
newChanged = true;
} else {
// Calculate the digest in order to see of a buffer has been
// changed (and should be saved)
newDigest = createDigest();
if (newDigest != digest) {
newChanged = true;
}
}
if (newChanged != changed) {
changed = newChanged;
}
fireDocumentChanged(jmde);
}
private void fireDocumentChanged(JMDocumentEvent de) {
for (BufferDocumentChangeListenerIF listener : listeners) {
listener.documentChanged(de);
}
}
public BlameIF getVersionControlBlame() {
return null;
}
public boolean isReadonly() {
return false;
}
public String toString() {
return "Document[name=" + name + "]";
}
}