This implements full bidi support for AbstractDocument. This means that
there is now an additional element structure that reflects the direction
of bidi runs in the document. 'Full' in the sense that the Intel
testcases for AbstractDocument and subclasses all pass now. And since
the testsuite is pretty extensive with the bidi support I think it's
quite good.
There's also some (somewhat hidden) stubs implemented that were pointed
out by Intel's testsuite.
2006-08-10 Roman Kennke <[EMAIL PROTECTED]>
* javax/swing/text/AbstractDocument.java
(BidiRootName): New constant field, denotes the element name
for bidi root elements.
(AsyncLoadPriority): New constant field, denotes the property
to store the asynchronousLoadPriority.
(I18N): New constant field, denotes the property for
I18N support.
(bidiRoot): Made field type BidiRootElement.
(AbstractDocument): Build initial element structure for
bidi.
(getAsynchronousLoadPriority): Implemented. Returns the
value stored in the document properties.
(setAsynchronousLoadPriority): Implemented. Sets the
value stored in the document properties.
(getEndPosition): Implemented to use a Position from the
content.
(getStartPosition): Implemented to use a Position from the
content.
(insertStringImpl): Update the I18N setting if necessary.
(insertUpdate): Update the bidi structure if necessary.
(postRemoveUpdate): Update the bidi structure if necessary.
(putProperty): Update the I18N setting and bidi structure
if necessary.
(updateBidi): New helper method for updating the bidi
structure.
(getBidis): New helper method. Fetches the Bidi analysers
for the paragraphs of the range to check.
(dump): Also dump the bidi structure.
(AbstractElement.dump): Indent the '>' correctly.
(AbstractElement.children): Check numChildren rather then
children.length.
(BidiRootElement): New inner class.
(BidiElement): New inner class.
/Roman
Index: javax/swing/text/AbstractDocument.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/AbstractDocument.java,v
retrieving revision 1.61
diff -u -1 -2 -r1.61 AbstractDocument.java
--- javax/swing/text/AbstractDocument.java 9 Aug 2006 15:15:39 -0000 1.61
+++ javax/swing/text/AbstractDocument.java 10 Aug 2006 21:34:27 -0000
@@ -29,26 +29,29 @@
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package javax.swing.text;
+import java.awt.font.TextAttribute;
import java.io.PrintStream;
import java.io.Serializable;
+import java.text.Bidi;
+import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Hashtable;
import java.util.Vector;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.EventListenerList;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.DocumentFilter;
@@ -96,24 +99,39 @@
/**
* Standard name for section <code>Element</code>s. These are usually
* [EMAIL PROTECTED] DefaultStyledDocument.SectionElement}s.
*/
public static final String SectionElementName = "section";
/**
* Attribute key for storing the element name.
*/
public static final String ElementNameAttribute = "$ename";
/**
+ * Standard name for the bidi root element.
+ */
+ private static final String BidiRootName = "bidi root";
+
+ /**
+ * Key for storing the asynchronous load priority.
+ */
+ private static final String AsyncLoadPriority = "load priority";
+
+ /**
+ * Key for storing the I18N state.
+ */
+ private static final String I18N = "i18n";
+
+ /**
* The actual content model of this <code>Document</code>.
*/
Content content;
/**
* The AttributeContext for this <code>Document</code>.
*/
AttributeContext context;
/**
* The currently installed <code>DocumentFilter</code>.
*/
@@ -149,25 +167,25 @@
*/
private Object documentCV = new Object();
/** An instance of a DocumentFilter.FilterBypass which allows calling
* the insert, remove and replace method without checking for an installed
* document filter.
*/
private DocumentFilter.FilterBypass bypass;
/**
* The bidi root element.
*/
- private Element bidiRoot;
+ private BidiRootElement bidiRoot;
/**
* Creates a new <code>AbstractDocument</code> with the specified
* [EMAIL PROTECTED] Content} model.
*
* @param doc the <code>Content</code> model to be used in this
* <code>Document<code>
*
* @see GapContent
* @see StringContent
*/
protected AbstractDocument(Content doc)
@@ -182,30 +200,43 @@
* @param doc the <code>Content</code> model to be used in this
* <code>Document<code>
* @param ctx the <code>AttributeContext</code> to use
*
* @see GapContent
* @see StringContent
*/
protected AbstractDocument(Content doc, AttributeContext ctx)
{
content = doc;
context = ctx;
+ // FIXME: Fully implement bidi.
+ bidiRoot = new BidiRootElement();
+
// FIXME: This is determined using a Mauve test. Make the document
// actually use this.
- putProperty("i18n", Boolean.FALSE);
+ putProperty(I18N, Boolean.FALSE);
- // FIXME: Fully implement bidi.
- bidiRoot = new BranchElement(null, null);
+ // Add one child to the bidi root.
+ writeLock();
+ try
+ {
+ Element[] children = new Element[1];
+ children[0] = new BidiElement(bidiRoot, 0, 1, 0);
+ bidiRoot.replace(0, 0, children);
+ }
+ finally
+ {
+ writeUnlock();
+ }
}
/** Returns the DocumentFilter.FilterBypass instance for this
* document and create it if it does not exist yet.
*
* @return This document's DocumentFilter.FilterBypass instance.
*/
private DocumentFilter.FilterBypass getBypass()
{
if (bypass == null)
bypass = new Bypass();
@@ -343,25 +374,29 @@
for (int index = 0; index < listeners.length; ++index)
listeners[index].undoableEditHappened(event);
}
/**
* Returns the asynchronous loading priority. Returns <code>-1</code> if this
* document should not be loaded asynchronously.
*
* @return the asynchronous loading priority
*/
public int getAsynchronousLoadPriority()
{
- return 0;
+ Object val = getProperty(AsyncLoadPriority);
+ int prio = -1;
+ if (val != null)
+ prio = ((Integer) val).intValue();
+ return prio;
}
/**
* Returns the [EMAIL PROTECTED] AttributeContext} used in this <code>Document</code>.
*
* @return the [EMAIL PROTECTED] AttributeContext} used in this <code>Document</code>
*/
protected final AttributeContext getAttributeContext()
{
return context;
}
@@ -416,32 +451,35 @@
return properties;
}
/**
* Returns a [EMAIL PROTECTED] Position} which will always mark the end of the
* <code>Document</code>.
*
* @return a [EMAIL PROTECTED] Position} which will always mark the end of the
* <code>Document</code>
*/
public final Position getEndPosition()
{
- // FIXME: Properly implement this by calling Content.createPosition().
- return new Position()
- {
- public int getOffset()
- {
- return getLength();
- }
- };
+ Position p;
+ try
+ {
+ p = createPosition(content.length());
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't really happen.
+ p = null;
+ }
+ return p;
}
/**
* Returns the length of this <code>Document</code>'s content.
*
* @return the length of this <code>Document</code>'s content
*/
public int getLength()
{
// We return Content.getLength() -1 here because there is always an
// implicit \n at the end of the Content which does count in Content
// but not in Document.
@@ -495,32 +533,35 @@
return elements;
}
/**
* Returns a [EMAIL PROTECTED] Position} which will always mark the beginning of the
* <code>Document</code>.
*
* @return a [EMAIL PROTECTED] Position} which will always mark the beginning of the
* <code>Document</code>
*/
public final Position getStartPosition()
{
- // FIXME: Properly implement this using Content.createPosition().
- return new Position()
- {
- public int getOffset()
- {
- return 0;
- }
- };
+ Position p;
+ try
+ {
+ p = createPosition(0);
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't really happen.
+ p = null;
+ }
+ return p;
}
/**
* Returns a piece of this <code>Document</code>'s content.
*
* @param offset the start offset of the content
* @param length the length of the content
*
* @return the piece of content specified by <code>offset</code> and
* <code>length</code>
*
* @throws BadLocationException if <code>offset</code> or <code>offset +
@@ -594,84 +635,411 @@
{
// Just return when no text to insert was given.
if (text == null || text.length() == 0)
return;
DefaultDocumentEvent event =
new DefaultDocumentEvent(offset, text.length(),
DocumentEvent.EventType.INSERT);
UndoableEdit undo = content.insertString(offset, text);
if (undo != null)
event.addEdit(undo);
+ // Check if we need bidi layout.
+ if (getProperty(I18N).equals(Boolean.FALSE))
+ {
+ Object dir = getProperty(TextAttribute.RUN_DIRECTION);
+ if (TextAttribute.RUN_DIRECTION_RTL.equals(dir))
+ putProperty(I18N, Boolean.TRUE);
+ else
+ {
+ char[] chars = text.toCharArray();
+ if (Bidi.requiresBidi(chars, 0, chars.length))
+ putProperty(I18N, Boolean.TRUE);
+ }
+ }
+
insertUpdate(event, attributes);
fireInsertUpdate(event);
if (undo != null)
fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
}
/**
* Called to indicate that text has been inserted into this
* <code>Document</code>. The default implementation does nothing.
* This method is executed within a write lock.
*
* @param chng the <code>DefaultDocumentEvent</code> describing the change
* @param attr the attributes of the changed content
*/
protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
{
- // Do nothing here. Subclasses may want to override this.
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ updateBidi(chng);
}
/**
* Called after some content has been removed from this
* <code>Document</code>. The default implementation does nothing.
* This method is executed within a write lock.
*
* @param chng the <code>DefaultDocumentEvent</code> describing the change
*/
protected void postRemoveUpdate(DefaultDocumentEvent chng)
{
- // Do nothing here. Subclasses may want to override this.
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ updateBidi(chng);
}
/**
* Stores a property in this <code>Document</code>'s property list.
*
* @param key the key of the property to be stored
* @param value the value of the property to be stored
*/
public final void putProperty(Object key, Object value)
{
// FIXME: make me thread-safe
if (properties == null)
properties = new Hashtable();
- properties.put(key, value);
+ if (value == null)
+ properties.remove(key);
+ else
+ properties.put(key, value);
+
+ // Update bidi structure if the RUN_DIRECTION is set.
+ if (TextAttribute.RUN_DIRECTION.equals(key))
+ {
+ if (TextAttribute.RUN_DIRECTION_RTL.equals(value)
+ && Boolean.FALSE.equals(getProperty(I18N)))
+ putProperty(I18N, Boolean.TRUE);
+
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ {
+ writeLock();
+ try
+ {
+ DefaultDocumentEvent ev =
+ new DefaultDocumentEvent(0, getLength(),
+ DocumentEvent.EventType.INSERT);
+ updateBidi(ev);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the bidi element structure.
+ *
+ * @param ev the document event for the change
+ */
+ private void updateBidi(DefaultDocumentEvent ev)
+ {
+ // Determine start and end offset of the paragraphs to be scanned.
+ int start = 0;
+ int end = 0;
+ DocumentEvent.EventType type = ev.getType();
+ if (type == DocumentEvent.EventType.INSERT
+ || type == DocumentEvent.EventType.CHANGE)
+ {
+ int offs = ev.getOffset();
+ int endOffs = offs + ev.getLength();
+ start = getParagraphElement(offs).getStartOffset();
+ end = getParagraphElement(endOffs).getEndOffset();
+ }
+ else if (type == DocumentEvent.EventType.REMOVE)
+ {
+ Element par = getParagraphElement(ev.getOffset());
+ start = par.getStartOffset();
+ end = par.getEndOffset();
+ }
+ else
+ assert false : "Unknown event type";
+
+ // Determine the bidi levels for the affected range.
+ Bidi[] bidis = getBidis(start, end);
+
+ int removeFrom = 0;
+ int removeTo = 0;
+
+ int offs = 0;
+ int lastRunStart = 0;
+ int lastRunEnd = 0;
+ int lastRunLevel = 0;
+ ArrayList newEls = new ArrayList();
+ for (int i = 0; i < bidis.length; i++)
+ {
+ Bidi bidi = bidis[i];
+ int numRuns = bidi.getRunCount();
+ for (int r = 0; r < numRuns; r++)
+ {
+ if (r == 0 && i == 0)
+ {
+ if (start > 0)
+ {
+ // Try to merge with the previous element if it has the
+ // same bidi level as the first run.
+ int prevElIndex = bidiRoot.getElementIndex(start - 1);
+ removeFrom = prevElIndex;
+ Element prevEl = bidiRoot.getElement(prevElIndex);
+ AttributeSet atts = prevEl.getAttributes();
+ int prevElLevel = StyleConstants.getBidiLevel(atts);
+ if (prevElLevel == bidi.getRunLevel(r))
+ {
+ // Merge previous element with current run.
+ lastRunStart = prevEl.getStartOffset() - start;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ }
+ else if (prevEl.getEndOffset() > start)
+ {
+ // Split previous element and replace by 2 new elements.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ newEls.add(new BidiElement(bidiRoot,
+ prevEl.getStartOffset(),
+ start, prevElLevel));
+ }
+ else
+ {
+ // Simply start new run at start location.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ removeFrom++;
+ }
+ }
+ else
+ {
+ // Simply start new run at start location.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ removeFrom = 0;
+ }
+ }
+ if (i == bidis.length - 1 && r == numRuns - 1)
+ {
+ if (end <= getLength())
+ {
+ // Try to merge last element with next element.
+ int nextIndex = bidiRoot.getElementIndex(end);
+ Element nextEl = bidiRoot.getElement(nextIndex);
+ AttributeSet atts = nextEl.getAttributes();
+ int nextLevel = StyleConstants.getBidiLevel(atts);
+ int level = bidi.getRunLevel(r);
+ if (lastRunLevel == level && level == nextLevel)
+ {
+ // Merge runs together.
+ if (lastRunStart + start == nextEl.getStartOffset())
+ removeTo = nextIndex - 1;
+ else
+ {
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ nextEl.getEndOffset(), level));
+ removeTo = nextIndex;
+ }
+ }
+ else if (lastRunLevel == level)
+ {
+ // Merge current and last run.
+ int endOffs = offs + bidi.getRunLimit(r);
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + endOffs, level));
+ if (start + endOffs == nextEl.getStartOffset())
+ removeTo = nextIndex - 1;
+ else
+ {
+ newEls.add(new BidiElement(bidiRoot, start + endOffs,
+ nextEl.getEndOffset(),
+ nextLevel));
+ removeTo = nextIndex;
+ }
+ }
+ else if (level == nextLevel)
+ {
+ // Merge current and next run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
+ nextEl.getEndOffset(), level));
+ removeTo = nextIndex;
+ }
+ else
+ {
+ // Split next element.
+ int endOffs = offs + bidi.getRunLimit(r);
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
+ start + endOffs, level));
+ newEls.add(new BidiElement(bidiRoot, start + endOffs,
+ nextEl.getEndOffset(),
+ nextLevel));
+ removeTo = nextIndex;
+ }
+ }
+ else
+ {
+ removeTo = bidiRoot.getElementIndex(end);
+ int level = bidi.getRunLevel(r);
+ int runEnd = offs + bidi.getRunLimit(r);
+
+ if (level == lastRunLevel)
+ {
+ // Merge with previous.
+ lastRunEnd = offs + runEnd;
+ newEls.add(new BidiElement(bidiRoot,
+ start + lastRunStart,
+ start + runEnd, level));
+ }
+ else
+ {
+ // Create element for last run and current run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot,
+ start + lastRunEnd,
+ start + runEnd,
+ level));
+ }
+ }
+ }
+ else
+ {
+ int level = bidi.getRunLevel(r);
+ int runEnd = bidi.getRunLimit(r);
+
+ if (level == lastRunLevel)
+ {
+ // Merge with previous.
+ lastRunEnd = offs + runEnd;
+ }
+ else
+ {
+ // Create element for last run and update values for
+ // current run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ lastRunStart = lastRunEnd;
+ lastRunEnd = offs + runEnd;
+ lastRunLevel = level;
+ }
+ }
+ }
+ offs += bidi.getLength();
+ }
+
+ // Determine the bidi elements which are to be removed.
+ int numRemoved = 0;
+ if (bidiRoot.getElementCount() > 0)
+ numRemoved = removeTo - removeFrom + 1;
+ Element[] removed = new Element[numRemoved];
+ for (int i = 0; i < numRemoved; i++)
+ removed[i] = bidiRoot.getElement(removeFrom + i);
+
+ Element[] added = new Element[newEls.size()];
+ added = (Element[]) newEls.toArray(added);
+
+ // Update the event.
+ ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added);
+ ev.addEdit(edit);
+
+ // Update the structure.
+ bidiRoot.replace(removeFrom, numRemoved, added);
+ }
+
+ /**
+ * Determines the Bidi objects for the paragraphs in the specified range.
+ *
+ * @param start the start of the range
+ * @param end the end of the range
+ *
+ * @return the Bidi analysers for the paragraphs in the range
+ */
+ private Bidi[] getBidis(int start, int end)
+ {
+ // Determine the default run direction from the document property.
+ Boolean defaultDir = null;
+ Object o = getProperty(TextAttribute.RUN_DIRECTION);
+ if (o instanceof Boolean)
+ defaultDir = (Boolean) o;
+
+ // Scan paragraphs and add their level arrays to the overall levels array.
+ ArrayList bidis = new ArrayList();
+ Segment s = new Segment();
+ for (int i = start; i < end;)
+ {
+ Element par = getParagraphElement(i);
+ int pStart = par.getStartOffset();
+ int pEnd = par.getEndOffset();
+
+ // Determine the default run direction of the paragraph.
+ Boolean dir = defaultDir;
+ o = par.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
+ if (o instanceof Boolean)
+ dir = (Boolean) o;
+
+ // Bidi over the paragraph.
+ try
+ {
+ getText(pStart, pEnd - pStart, s);
+ }
+ catch (BadLocationException ex)
+ {
+ assert false : "Must not happen";
+ }
+ int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
+ if (dir != null)
+ {
+ if (TextAttribute.RUN_DIRECTION_LTR.equals(dir))
+ flag = Bidi.DIRECTION_LEFT_TO_RIGHT;
+ else
+ flag = Bidi.DIRECTION_RIGHT_TO_LEFT;
+ }
+ Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag);
+ bidis.add(bidi);
+ i = pEnd;
+ }
+ Bidi[] ret = new Bidi[bidis.size()];
+ ret = (Bidi[]) bidis.toArray(ret);
+ return ret;
}
/**
* Blocks until a read lock can be obtained. Must block if there is
* currently a writer modifying the <code>Document</code>.
*/
public final void readLock()
{
if (currentWriter != null && currentWriter.equals(Thread.currentThread()))
return;
synchronized (documentCV)
{
while (currentWriter != null || numWritersWaiting > 0)
{
+
try
{
documentCV.wait();
}
catch (InterruptedException ie)
{
throw new Error("interrupted trying to get a readLock");
}
}
numReaders++;
}
}
@@ -947,25 +1315,26 @@
}
}
/**
* Sets the asynchronous loading priority for this <code>Document</code>.
* A value of <code>-1</code> indicates that this <code>Document</code>
* should be loaded synchronously.
*
* @param p the asynchronous loading priority to set
*/
public void setAsynchronousLoadPriority(int p)
{
- // TODO: Implement this properly.
+ Integer val = p >= 0 ? new Integer(p) : null;
+ putProperty(AsyncLoadPriority, val);
}
/**
* Sets the properties of this <code>Document</code>.
*
* @param p the document properties to set
*/
public void setDocumentProperties(Dictionary p)
{
// FIXME: make me thread-safe
properties = p;
}
@@ -1038,24 +1407,25 @@
{
this.documentFilter = filter;
}
/**
* Dumps diagnostic information to the specified <code>PrintStream</code>.
*
* @param out the stream to write the diagnostic information to
*/
public void dump(PrintStream out)
{
((AbstractElement) getDefaultRootElement()).dump(out, 0);
+ ((AbstractElement) getBidiRootElement()).dump(out, 0);
}
/**
* Defines a set of methods for managing text attributes for one or more
* <code>Document</code>s.
*
* Replicating [EMAIL PROTECTED] AttributeSet}s throughout a <code>Document</code> can
* be very expensive. Implementations of this interface are intended to
* provide intelligent management of <code>AttributeSet</code>s, eliminating
* costly duplication.
*
* @see StyleContext
@@ -1643,24 +2013,29 @@
while (attNames.hasMoreElements())
{
for (int i = 0; i < indent + 2; ++i)
b.append(' ');
Object attName = attNames.nextElement();
b.append(attName);
b.append('=');
Object attribute = getAttribute(attName);
b.append(attribute);
b.append('\n');
}
}
+ if (getAttributeCount() > 0)
+ {
+ for (int i = 0; i < indent; ++i)
+ b.append(' ');
+ }
b.append(">\n");
// Dump element content for leaf elements.
if (isLeaf())
{
for (int i = 0; i < indent + 2; ++i)
b.append(' ');
int start = getStartOffset();
int end = getEndOffset();
b.append('[');
b.append(start);
b.append(',');
@@ -1731,25 +2106,25 @@
children = new Element[1];
numChildren = 0;
lastIndex = -1;
}
/**
* Returns the children of this <code>BranchElement</code>.
*
* @return the children of this <code>BranchElement</code>
*/
public Enumeration children()
{
- if (children.length == 0)
+ if (numChildren == 0)
return null;
Vector tmp = new Vector();
for (int index = 0; index < numChildren; ++index)
tmp.add(children[index]);
return tmp.elements();
}
/**
* Returns <code>true</code> since <code>BranchElements</code> allow
@@ -2376,25 +2751,81 @@
/**
* Returns a string representation of this <code>Element</code>.
*
* @return a string representation of this <code>Element</code>
*/
public String toString()
{
return ("LeafElement(" + getName() + ") "
+ getStartOffset() + "," + getEndOffset() + "\n");
}
}
-
+
+ /**
+ * The root element for bidirectional text.
+ */
+ private class BidiRootElement
+ extends BranchElement
+ {
+ /**
+ * Creates a new bidi root element.
+ */
+ BidiRootElement()
+ {
+ super(null, null);
+ }
+
+ /**
+ * Returns the name of the element.
+ *
+ * @return the name of the element
+ */
+ public String getName()
+ {
+ return BidiRootName;
+ }
+ }
+
+ /**
+ * A leaf element for the bidi structure.
+ */
+ private class BidiElement
+ extends LeafElement
+ {
+ /**
+ * Creates a new BidiElement.
+ *
+ * @param parent the parent element
+ * @param start the start offset
+ * @param end the end offset
+ * @param level the bidi level
+ */
+ BidiElement(Element parent, int start, int end, int level)
+ {
+ super(parent, new SimpleAttributeSet(), start, end);
+ addAttribute(StyleConstants.BidiLevel, new Integer(level));
+ }
+
+ /**
+ * Returns the name of the element.
+ *
+ * @return the name of the element
+ */
+ public String getName()
+ {
+ return BidiElementName;
+ }
+ }
+
/** A class whose methods delegate to the insert, remove and replace methods
* of this document which do not check for an installed DocumentFilter.
*/
class Bypass extends DocumentFilter.FilterBypass
{
public Document getDocument()
{
return AbstractDocument.this;
}
public void insertString(int offset, String string, AttributeSet attr)