Java: Swing: Text components and Document: Document
There is no way to access a single character. Either you need the clumsy Segment setup, or have a String created. This is really awkward.
Usual discussion: how exactly do the methods ensure which modifications have actually taken place? As a) JDK1.4 introduces a DocumentFilter anyway and b) arbitrary removals would damage the document structure in sometimes unexpected ways (at least with DefaultStyledDocument), these methods can be regarded only as suggestions. That means that the Document may convert the text as it likes and ignore any of the given attributes. (But if any changes happen, I would assume it to take place at the given positions and not somewhere else).
insertString(): There may be cases where you would want to specify the insertion position with a bias, for example, when inserting between two elements, you may want to associate the text to the first or the second element (With the standard element structure, this doesn't matter, as elements will be created implicitly based on the attributes anyway, but there is nothing in document that requires that). If there were, position handling would be still more complicated.
Are the attributes taking in addition to those present at the specified position or only them? (the latter with standard implementations)
There are several reasons why the AttributeSet given to insertString() isn't used directly:
It may be mutable; so you could later change attributes in the Document without it knowing, then it wouldn't fire events although it had to.
Usually attributes not present in the set should be resolved by some other AttributeSet (up the paragraph / section structure, if any), but the Document cannot modify the given AttributeSet, as it doesn't know whether it is mutable (and even if it were a MutableAttributeSet, it would be bad style to keep a permanent reference to it and to change it).
The Document may not want to honor all attributes given; especially, it may want to flatten the structure to set the resolving set internally (just copying the attributes isn't enough if there is a resolve parent!).
There are no direct methods to suggest replacing a text range by another. In fact, the event protocol (DocumentEvent) does not make it possible to do this atomically: you always have to handle the remove, then fire an event, than the add (or the other way round) and fire a second event. As both insertString() and remove() are to be regarded as suggestions and may only do part of the action, straight-forward replacing may have confusing results. Concerning multithreading, there is no general way for atomic replaces, even if if insertString() and remove() obtain a (write) lock, the document may change inbetween.
As getStartPosition()'s result Position always returns 0, and getEndPosition()'s always should return Document.getLength(), the existence of these methods is rather senseless, since the implementation can be the same for all Documents.
Except with AbstractDocument: getEndPosition() returns getLength() + 1 which I believe is wrong, since the index of the position is not valid. The two methods, are never used internally.
The Positions created by createPosition() associate forwards (except with argument 0 , which is a design bug, since you cannot get positions which are at the current start of the document and move by any insertions at the beginning). This seems to be intentional because it is needed by the Element implementations (which could have been done differently: it is done differently for every paragraph but the first one).
createPosition() maybe should have taken a bias argument to indicate whether the position should stay before or after text inserted by insertString() at its index.
Implementation note: Positions can not be implemented simply as normal DocumentListeners because then they might update too late. They must be updated before firing DocumentEvents to other listeners.
Anyway, the Document has to know about the created position, so there are problems with garbage collection. The standard implementations use indirection (both Document and Position hold a reference to the actual index, and references to the Position are counted manually, if there are no more, the actual index object is removed); it is also possible to use weak references.
The notification mechanism of document doesn't allow text replacements - only additions and removals. That means that positions contained in a range of text that is "changed" (i.e. first the new text is added, then the old removed, or vice versa), always move to the beginning or end of the range, depending on the order of the mutations.
As noted above, position 0 is a special case. Any position (created by the standard methods) that ever moved to index 0 will always remain there (the end position can never be at index 0 because of the implied newline with AbstractDocument). See (#4204837).
It is virtually impossible (except by circumventing BasicTextUI) to display the element structure of another than the default root element.
This is discussed in a separate text.
See multiple threads article.
What is the difference between BadLocationException and IndexOutOfBoundsException? Why should one be checked and the other not?
Many methods not only taken an index, but a range (alas, unconsistently by 'index' and 'length' instead of 'from' and 'to'). These methods throw BadLocationException if the index is out of bounds, but are supposed to throw some unspecified unchecked exception if the length is negative (BadLocationException explicitly cannot be used in general for that purpose, since index + length still is a valid index if abs(length) <= index; still AbstractDocument does it), even though in both cases the caller will have made a similar bug.
BadLocationException isn't thrown consistently by all methods that take indices (see JTetArea for example: getLineOfOffset() throws, but insert() not). If the 'throws' declarations were consistent, it would much more sense. But many methods aren't supposed to throw BadLocationException, for example View.paint() (where the exception can only happen if some internal data is corrupt anyway, another argument for it not being checked).
There are cases where the exception cannot be possibly thrown, for example when the index is 0 or you have got all information from the indices from the Document or its Elements itself, so you will not want to throw it (or even can't) (for example the HTMLDocument.insertXXX methods!). Using an empty catch block will then hide bugs in the called code; and you explicitly have to write code for that case (each time again) although it clearly cannot happen (unless threading problems occur, see below).
What to do if you call internally need to call a method that throws BadLocationException, but you cannot throw it yourself? Then the exception will have to be converted into another Exception or Error anyway.
Many methods that catch BadLocationException don't anything useful with it, some rethrow an unchecked exception instead.
In JDK1.3.0_01, BadLocationException is declared 137 times, thrown 32 times (and each time it is clearly a precondition validation), caught 101 times, about half of that time it is ignored, about one third another exception is thrown, the rest of time either beep(), or return some default value ('null' or -1).
The only justification (not "reason") I can imagine for BadLocationException existing at all is that because of the strange way of multithreading support, often indices are provided which were valid by the time their were calculated, but aren't any more by the time are used. If IndexOutOfBoundsException were thrown (and not caught), it would terminate the processing of the current event and cause a lot of Objects with an unconsistent state. Using the checked BadLocationException instead forces the caller to catch it, so event processing at a higher level still can continue. All this may happen only because multithreading for Document is broken. It wouldn't be a problem if locking could be done properly; then such invalid indices could never occur.
Result: A BadLocationException always is bug in the caller, and thus shouldn't be checked. To workaround its existance, a) always ensure the preconditions (as you should do anyway), b) for clearer code, use wrapper methods that catch BadLocationException and rethrow IndexOutOfBoundsException. Don't call methods that throw it directly.
(C) 2001-2009 Christian Kaufhold (firstname.lastname@example.org)