Index: src/org/openide/text/DocumentLine.java =================================================================== RCS file: /cvs/openide/src/org/openide/text/DocumentLine.java,v retrieving revision 1.54 diff -u -r1.54 DocumentLine.java --- src/org/openide/text/DocumentLine.java 20 May 2004 15:58:15 -0000 1.54 +++ src/org/openide/text/DocumentLine.java 1 Jun 2004 16:21:02 -0000 @@ -781,8 +781,8 @@ * NetBeans conventions. */ public static abstract class Set extends Line.Set { - /** listener on document changes */ - private final LineListener listener; + /** listener on document changes, accessed from LazyLines */ + final LineListener listener; /** all lines in the set or null */ private java.util.List list; @@ -859,14 +859,9 @@ * * @return list of Line objects */ - public java.util.List getLines () { + public synchronized java.util.List getLines () { if (list == null) { - int cnt = listener.getOriginalLineCount (); - java.util.List l = new java.util.LinkedList (); - for (int i = 0; i < cnt; i++) { - l.add (getOriginal (i)); - } - list = l; + list = new LazyLines (this); } return list; } @@ -885,6 +880,16 @@ return safelyRegisterLine(createLine(offset)); } + + public int getOriginal (Line line) { + Line find = findLine (line); + if (find != null) { + return listener.getOld (find.getLineNumber ()); + } else { + return -1; + } + } + /* Creates current line. * * @param line is a number of the line (text line) we want to acquire Index: src/org/openide/text/LazyLines.java =================================================================== RCS file: src/org/openide/text/LazyLines.java diff -N src/org/openide/text/LazyLines.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/openide/text/LazyLines.java 1 Jun 2004 16:21:02 -0000 @@ -0,0 +1,160 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.openide.text; + +/** Lazy List that delegates to another instance of itself. + */ +final class LazyLines extends Object implements java.util.List { + private java.util.List delegate; + private DocumentLine.Set set; + + public LazyLines (DocumentLine.Set set) { + this.set = set; + } + + /** Override this to create the delegate + */ + private java.util.List createDelegate () { + int cnt = set.listener.getOriginalLineCount (); + java.util.List l = new java.util.ArrayList (cnt); + for (int i = 0; i < cnt; i++) { + l.add (set.getOriginal (i)); + } + return l; + } + + + private synchronized java.util.List getDelegate () { + if (delegate == null) { + delegate = createDelegate (); + } + return delegate; + } + + public int indexOf (Object o) { + if (o instanceof DocumentLine) { + Line find = set.findLine ((DocumentLine)o); + if (find != null) { + int indx = set.listener.getOld (find.getLineNumber ()); + if (set.getOriginal (indx).equals (o)) { + // just to verify that the index really exists + return indx; + } + } + } + return -1; + } + + public int lastIndexOf (Object o) { + return indexOf (o); + } + + + // + // Pure delegate methods + // + + public int hashCode () { + return getDelegate ().hashCode (); + } + + public boolean addAll (java.util.Collection c) { + throw new UnsupportedOperationException (); + } + + public boolean removeAll (java.util.Collection c) { + throw new UnsupportedOperationException (); + } + + public java.util.ListIterator listIterator () { + return getDelegate ().listIterator (); + } + + public Object[] toArray () { + return getDelegate ().toArray (); + } + + public Object[] toArray (Object[] a) { + return getDelegate ().toArray (a); + } + + public java.util.ListIterator listIterator (int index) { + return getDelegate ().listIterator (index); + } + + public boolean remove (Object o) { + throw new UnsupportedOperationException (); + } + + public boolean equals (Object obj) { + return getDelegate ().equals (obj); + } + + public boolean contains (Object o) { + return getDelegate ().contains (o); + } + + public void add (int index, Object element) { + throw new UnsupportedOperationException (); + } + + public void clear () { + getDelegate ().clear (); + } + + public Object set (int index, Object element) { + throw new UnsupportedOperationException (); + } + + public int size () { + return getDelegate ().size (); + } + + public Object get (int index) { + return getDelegate ().get (index); + } + + public boolean containsAll (java.util.Collection c) { + return getDelegate ().containsAll (c); + } + + public boolean add (Object o) { + throw new UnsupportedOperationException (); + } + + public boolean isEmpty () { + return getDelegate ().isEmpty (); + } + + public boolean retainAll (java.util.Collection c) { + throw new UnsupportedOperationException (); + } + + public java.util.List subList (int fromIndex, int toIndex) { + return getDelegate ().subList (fromIndex, toIndex); + } + + public Object remove (int index) { + return getDelegate ().remove (index); + } + + public java.util.Iterator iterator () { + return getDelegate ().iterator (); + } + + public boolean addAll (int index, java.util.Collection c) { + throw new UnsupportedOperationException (); + } + +} Index: src/org/openide/text/Line.java =================================================================== RCS file: /cvs/openide/src/org/openide/text/Line.java,v retrieving revision 1.27 diff -u -r1.27 Line.java --- src/org/openide/text/Line.java 25 Mar 2004 16:04:54 -0000 1.27 +++ src/org/openide/text/Line.java 1 Jun 2004 16:21:02 -0000 @@ -325,13 +325,23 @@ * @exception IndexOutOfBoundsException if line is an invalid index for the original set of lines */ public abstract Line getCurrent (int line) throws IndexOutOfBoundsException; - + + /** Finds an original line number for given line in this line set. + * @param line the line to look for + * @return the number that best matches the line number of the line or -1 + * if the line does seem to be produced by this line set + * @since JST-PENDING + */ + public int getOriginal (Line line) { + return computeOriginal (this, line); + } + /** Registers the line to this Line.Set. * @param line Line to register * @return registered Line. Note: the retruned * Line could be different (identityHashCode not equal) * from the one passed in */ - Line registerLine(Line line) { + final Line registerLine(Line line) { synchronized(lines) { Reference r = (Reference)lines.get(line); Line in = (r != null ? (Line)r.get() : null); @@ -347,6 +357,106 @@ return in; } + } + + /** Finds whether a line equal to provided is already registered. + * @param line the line to register + * @return the registered line equal to line or null + */ + final Line findLine (Line line) { + synchronized (lines) { + Reference r = (Reference)lines.get(line); + Line in = (r != null ? (Line)r.get() : null); + return in; + } + } + + /** A method that for a given Line.Set and a line computes the best + * original line number based on the querying the set. This is called + * in default implementation of getOriginal (Line) to provide + * inefficient (but better then most people would write) way to + * compute the number. It is static so it can be tested from + * tests working on DocumentLine objects that override the + * getOriginal (Line) method. + * + * @param set the set to search in + * @param line the line to look for + * @return closest possible line number for given line + */ + static int computeOriginal (Line.Set set, Line line) { + int n = line.getLineNumber (); + Line current = null; + try { + current = set.getOriginal (n); + if (line.equals (current)) { + return n; + } + } catch (IndexOutOfBoundsException ex) { + // ok, few lines have been added and this one is now + // bellow the end of the document + } + + if (current == null) { + return binarySearch (set, n, 0, findMaxLine (set)); + } + + if (n < current.getLineNumber ()) { + return binarySearch (set, n, 0, current.getLineNumber ()); + } else { + return binarySearch (set, n, current.getLineNumber (), findMaxLine (set)); + } + } + + /** Does a search for a given line number in a given Line.Set. + */ + private static int binarySearch (Line.Set set, int number, int from, int to) { + while (from < to) { + int middle = (from + to) / 2; + + Line l = set.getOriginal (middle); + if (l.getLineNumber () < number) { + // try after the middle + from = middle + 1; + } else { + // try before the middle + to = middle - 1; + } + } + + return from; + } + + private static int findMaxLine (Line.Set set) { + int from = 0; + int to = 32000; + + // probably larger than any existing document + for (;;) { + try { + set.getOriginal (to); + // if the line exists, double the max number, but keep + // for reference that it exists + from = to; + to *= 2; + } catch (IndexOutOfBoundsException ex) { + break; + } + } + + while (from < to) { + int middle = (from + to + 1) / 2; + + try { + set.getOriginal (middle); + // line exists + from = middle; + } catch (IndexOutOfBoundsException ex) { + // line does not exists, we have to search lower + to = middle - 1; + } + } + + return from; } } // End of class Line.Set. Index: src/org/openide/text/LineListener.java =================================================================== RCS file: /cvs/openide/src/org/openide/text/LineListener.java,v retrieving revision 1.11 diff -u -r1.11 LineListener.java --- src/org/openide/text/LineListener.java 12 Aug 2003 09:38:11 -0000 1.11 +++ src/org/openide/text/LineListener.java 1 Jun 2004 16:21:02 -0000 @@ -55,7 +55,11 @@ /** Convertor between old and new line sets */ public int getLine (int i) { - return struct.originalToCurrent (i); + return struct.convert (i, true/*originalToCurrent*/); + } + /** Convertor between old and new line sets */ + public int getOld (int i) { + return struct.convert (i, false/*currentToOriginal*/); } public void removeUpdate(javax.swing.event.DocumentEvent p0) { Index: src/org/openide/text/LineStruct.java =================================================================== RCS file: /cvs/openide/src/org/openide/text/LineStruct.java,v retrieving revision 1.12 diff -u -r1.12 LineStruct.java --- src/org/openide/text/LineStruct.java 14 Apr 2004 15:05:55 -0000 1.12 +++ src/org/openide/text/LineStruct.java 1 Jun 2004 16:21:02 -0000 @@ -279,7 +279,7 @@ * @param line the line number in the original * @return line number in the new numbering */ - public int originalToCurrent (int line) { + public int convert (int line, final boolean currentToOriginal) { // class to compute in the request processor thread class Compute extends Object implements Runnable { public int result; @@ -289,7 +289,11 @@ } public void run () { - result = originalToCurrentImpl (result); + if (currentToOriginal) { + result = originalToCurrentImpl (result); + } else { + result = currentToOriginalImpl (result); + } } } @@ -343,6 +347,24 @@ } cur += i.current; line -= i.original; + } + } + + /** Converts the current numbering to original + * @param line the line number now + * @return line number in the original numbering + */ + private int currentToOriginalImpl (int line) { + Iterator it = list.iterator (); + int cur = 0; + for (;;) { + Info i = (Info)it.next (); + if (i.current > line) { + // ok we found the segment that contained this line + return line > i.original ? cur + i.original : cur + line; + } + cur += i.original; + line -= i.current; } } Index: test/unit/src/org/openide/text/LineSetTest.java =================================================================== RCS file: /cvs/openide/test/unit/src/org/openide/text/LineSetTest.java,v retrieving revision 1.1 diff -u -r1.1 LineSetTest.java --- test/unit/src/org/openide/text/LineSetTest.java 20 May 2004 15:58:15 -0000 1.1 +++ test/unit/src/org/openide/text/LineSetTest.java 1 Jun 2004 16:21:02 -0000 @@ -50,6 +50,9 @@ } public static void main(java.lang.String[] args) { + if (args.length == 1) { + junit.textui.TestRunner.run (new LineSetTest (args[0])); + } junit.textui.TestRunner.run(suite()); } @@ -63,6 +66,7 @@ protected void setUp () { ic = new InstanceContent (); support = new CES (this, new AbstractLookup (ic)); + ic.add (this); } public void testLineSetIsEmpty () throws Exception { @@ -90,6 +94,7 @@ Line line = set.getCurrent (1); assertEquals ("Line number is one", 1, line.getLineNumber ()); + assertGetOriginal ("Original number is of course 1", set, line, 1); doc.insertString (0, "New line\n", null); @@ -107,6 +112,7 @@ Line currentLineTwo = support.getLineSet ().getOriginal (2); assertEquals ("This is our original line", line, currentLineTwo); + assertGetOriginal ("Original number of the line was 1", set, line, 1); assertEquals ("Original set still has two lines", 2, set.getLines ().size ()); assertEquals ("Index of current line 1 is 0 in old set", 1, set.getLines ().indexOf (line)); @@ -165,6 +171,211 @@ assertEquals ("They will not part", one, two); assertEquals ("Line number is 0", 0, one.getLineNumber ()); + } + + public void testGetLinesIndexOfDoesNotCreateAllLines () throws Exception { + content = "0\n1\n2\n3\n4\n"; + javax.swing.text.Document doc = support.openDocument (); + + Line.Set set = support.getLineSet (); + Line two = set.getOriginal (2); + + assertEquals ("Line index is two", 2, set.getLines ().indexOf (two)); + assertNumberOfLines (1, set); + + assertEquals ("Really two", 2, new java.util.ArrayList (set.getLines ()).indexOf (two)); + } + + public void testLinesAreNonMutable () throws Exception { + content = "0\n1\n2\n3\n4\n"; + javax.swing.text.Document doc = support.openDocument (); + + assertNonmutable (support.getLineSet ().getLines ()); + } + + public void testGetLinesIndexWorksAfterModifications () throws Exception { + content = "0\n1\n2\n3\n4\n"; + javax.swing.text.Document doc = support.openDocument (); + + Line.Set set = support.getLineSet (); + + int offset = 4; + assertEquals ("2 is on the second line", "2", doc.getText (4, 1)); + doc.insertString (offset, "x\n", null); + assertEquals ("x\n is on the second line", "x\n", doc.getText (4, 2)); + + + Line two = set.getOriginal (2); + assertEquals ("2\n is the line text", "2\n", two.getText ()); + + assertEquals ("Line index is two", 2, set.getLines ().indexOf (two)); + assertNumberOfLines (1, set); + + assertEquals ("Really two", 2, new java.util.ArrayList (set.getLines ()).indexOf (two)); + } + + public void testWhatHappensWhenAskingForLineOutOfBounds () throws Exception { + content = "0"; + javax.swing.text.Document doc = support.openDocument (); + + Line.Set set = support.getLineSet (); + + try { + Line l = set.getCurrent (1); + fail ("Should thrown IndexOutOfBoundsException"); + } catch (IndexOutOfBoundsException ex) { + // ok + } + + try { + Line n = set.getOriginal (1); + fail ("Should thrown IndexOutOfBoundsException"); + } catch (IndexOutOfBoundsException ex) { + // ok + } + try { + Line l = set.getCurrent (-1); + fail ("Should thrown IndexOutOfBoundsException"); + } catch (IndexOutOfBoundsException ex) { + // ok + } + + try { + Line n = set.getOriginal (-1); + fail ("Should thrown IndexOutOfBoundsException"); + } catch (IndexOutOfBoundsException ex) { + // ok + } + + Line l = set.getCurrent (0); + Line n = set.getOriginal (0); + + assertNotNull (l); + assertNotNull (n); + + assertEquals ("Lines are the same", l, n); + assertEquals ("Text is", "0", l.getText ()); + } + + + public void testGetLinesIndexWorksForNewlyAddedLines () throws Exception { + content = "0\n1\n2\n3\n4\n"; + javax.swing.text.Document doc = support.openDocument (); + + Line.Set set = support.getLineSet (); + + int offset = 4; + assertEquals ("2 is on the second line", "2", doc.getText (4, 1)); + doc.insertString (offset, "x\n", null); + assertEquals ("x\n is on the second line", "x\n", doc.getText (4, 2)); + + + Line two = set.getCurrent (2); + assertEquals ("x\n is the line text", "x\n", two.getText ()); + + assertEquals ("Line index is -1 as it is not present", -1, set.getLines ().indexOf (two)); + // two lines created as we need to verify that the current two line is not + // in the original set + // of course that it can be one if somebody implements this query + // in better way + assertNumberOfLines (2, set); + assertEquals ("Query on set works and does not produce new lines", 2, set.getOriginal (two)); + assertNumberOfLines (2, set); + + // now few additinal checks + assertGetOriginal ("However if one asks the line set it works", set, two, 2); + assertEquals ("Really missing from the list", -1, new java.util.ArrayList (set.getLines ()).indexOf (two)); + } + + private void assertNumberOfLines (int cnt, Line.Set set) throws Exception { + class MF implements org.netbeans.junit.MemoryFilter { + private java.util.HashSet counted = new java.util.HashSet (); + public int cnt; + public boolean reject(Object obj) { + if (obj instanceof Line) { + Line l = (Line)obj; + if (counted.add (obj)) { + if (l.getLookup ().lookup (LineSetTest.class) == LineSetTest.this) { + cnt++; + } + } + } + return false; + } + } + + MF mf = new MF (); + + // just travel thru the memory + assertSize ( + "Just one line", + java.util.Collections.singleton (set), + Integer.MAX_VALUE, + mf + ); + + if (mf.cnt > cnt) { + fail ("Only given number of instance of line created (" + cnt + ") but was: " + mf.cnt); + } + } + + private static void assertGetOriginal (String s, Line.Set set, Line line, int expected) { + assertEquals (s + " - Overriden DocumentLine.Set.getOriginal as well", expected, set.getOriginal (line)); + assertEquals (s + " - The default Line.Set.computeOriginal method works", expected, Line.Set.computeOriginal (set, line)); + } + + private static void assertNonmutable (java.util.List l) throws Exception { + try { + l.add (new Object ()); + fail ("add should fail"); + } catch (java.lang.UnsupportedOperationException ex) { + // ok + } + try { + l.add (0, new Object ()); + fail ("add should fail"); + } catch (java.lang.UnsupportedOperationException ex) { + // ok + } + + try { + l.remove (new Object ()); + fail ("remove should fail"); + } catch (java.lang.UnsupportedOperationException ex) { + // ok + } + + try { + l.addAll (java.util.Collections.EMPTY_LIST); + fail ("addAll should fail"); + } catch (java.lang.UnsupportedOperationException ex) { + // ok + } + try { + l.addAll (0, java.util.Collections.EMPTY_LIST); + fail ("addAll should fail"); + } catch (java.lang.UnsupportedOperationException ex) { + // ok + } + try { + l.removeAll (java.util.Collections.EMPTY_LIST); + fail ("removeAll should fail"); + } catch (java.lang.UnsupportedOperationException ex) { + // ok + } + + try { + l.retainAll (java.util.Collections.EMPTY_LIST); + fail ("retainAll should fail"); + } catch (java.lang.UnsupportedOperationException ex) { + // ok + } + try { + l.set (0, null); + fail ("set should fail"); + } catch (java.lang.UnsupportedOperationException ex) { + // ok + } } //