java - Swing 只读取 AbstractDocument.Content 的第一个字符。我究竟做错了什么?

标签 java swing document large-files

我正在开发一个功能,我应该显示一个巨大的文本文件。所以我想一旦文件超过一定大小,我就会尝试将文件映射到磁盘上。

文本至少是不可变的,这应该比编写支持相同尺寸的完整编辑器更容易。

这是我到目前为止所拥有的:

public class CharBufferContent implements AbstractDocument.Content {
    private final CharBuffer charBuffer;
    private final int length;

    public CharBufferContent(CharBuffer charBuffer) {
        this.charBuffer = charBuffer;
        length = charBuffer.length();
    }

    public Position createPosition(int offset) throws BadLocationException {
        return new ImmutablePosition(offset);
    }

    public int length() {
        return length;
    }

    public UndoableEdit insertString(int where, String string)
            throws BadLocationException {
        throw new UnsupportedOperationException("Document is immutable");
    }

    public UndoableEdit remove(int where, int nItems) throws BadLocationException {
        throw new UnsupportedOperationException("Document is immutable");
    }

    public String getString(int where, int length) throws BadLocationException {
        if (where < 0 || where + length > this.length) {
            throw new BadLocationException("Invalid range", this.length);
        }

        char[] out = new char[length];
        charBuffer.position(where);
        charBuffer.get(out);
        return new String(out);
    }

    public void getChars(int where, int length, Segment segment)
            throws BadLocationException {
        if (where < 0 || where + length > this.length) {
            throw new BadLocationException("Invalid range", this.length);
        }

        // This will be inefficient, but I'm just trying to get it working first.
        segment.array = new char[length];
        charBuffer.position(where);
        charBuffer.get(segment.array, 0, length);
        segment.offset = 0;
        segment.count = length;
    }

    private static class ImmutablePosition implements Position {
        private final int offset;

        private ImmutablePosition(int offset) {
            this.offset = offset;
        }

        @Override
        public int getOffset() {
            return offset;
        }
    }
}

我编写了一个小测试程序,它只使用内存缓冲区来测试它:

public class Test implements Runnable {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Test());
    }

    public void run() {
        CharBuffer charBuffer = CharBuffer.wrap("This is a fairly simple test, " +
            "so nothing should go wrong, right?\n");
        AbstractDocument.Content content = new CharBufferContent(charBuffer);
        final Document document = new PlainDocument(content);

        JTextArea text = new JTextArea(document);
        text.setEditable(false);

        JScrollPane textScroll = new JScrollPane(text);
        textScroll.setPreferredSize(new Dimension(600, 500));

        JFrame frame = new JFrame("Test");
        frame.setLayout(new BorderLayout());
        frame.add(textScroll, BorderLayout.CENTER);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

当我运行它时,窗口显示“T”。在调试器中,我可以看到 Swing 仅调用 length()getChars()。每次调用 getChars() 时都会有 where == 0length == 1。因此,它只显示一个字符是有道理的,但是 Swing 调用我的代码并且只要求第一个字符似乎很奇怪,即使我可以看到 length()返回文本的完整长度。

当我使用 StringContent 作为实现运行相同的测试时,将使用文档的完整长度调用其中的 getChars()

这个 API 中没有太多看起来可能出错的地方,所以我很困惑。

这是怎么回事?

最佳答案

我不太熟悉文档 API,但我的理解是 AbstractDocument.Content 预计不会预先配备自己的文本。 AbstractDocument 不知道在这种情况下该怎么办。如果您尝试使用 StringContent,您会看到同样的问题:

content = new StringContent();
content.insertString(0, "some text");
final AbstractDocument document = new PlainDocument(content);

这只显示前导“s”。显然,AbstractDocument 在文档末尾为插入符建模了一个额外的有效位置,并且它期望其内容的长度 +1 其实际长度来支持这一点,我相信这是第一个字符确实如此的事实的起源出现。

如果您在创建文档后调用StringContent.insertString,它似乎可以显示所有文本,但我认为这可能只是运气。如果以这种方式插入的字符串包含换行符,它将始终忽略它们,因为它们没有按照要求建模为单独的文档元素。

我认为实现这项工作的唯一方法是直接实现 Document,而不是 AbstractDocument.Content。这是一个更复杂的界面,但这确实有效:

class CharBufferDocument implements Document {
    private final CharBuffer charBuffer;
    private final int length;
    private final int[] lineOffsets;

    public CharBufferDocument(CharBuffer charBuffer) {
        this.charBuffer = charBuffer;

        charBuffer.position(0);
        length = charBuffer.length();

        int[] lineOffsets = new int[] { 0 };
        int lineCount = 1;

        for (int i = 0; i < length; i++) {
            char c = charBuffer.get(i);
            if (c == '\n') {
                if (lineCount == lineOffsets.length) {
                    if (lineCount == Integer.MAX_VALUE) throw new OutOfMemoryError();
                    int newLength = (int)Math.min(lineCount * 2L, Integer.MAX_VALUE);
                    lineOffsets = Arrays.copyOf(lineOffsets, newLength);
                }
                lineOffsets[lineCount++] = i + 1;
            }
        }

        lineOffsets = Arrays.copyOf(lineOffsets, lineCount);
        this.lineOffsets = lineOffsets;
    }

    @Override
    public int getLength() {
        return length;
    }

    @Override
    public void addDocumentListener(DocumentListener listener) {}

    @Override
    public void removeDocumentListener(DocumentListener listener) {}

    @Override
    public void addUndoableEditListener(UndoableEditListener listener) {}

    @Override
    public void removeUndoableEditListener(UndoableEditListener listener) {}

    @Override
    public void putProperty(Object key, Object value) {}

    @Override
    public Object getProperty(Object key) {
        return null;
    }

    @Override
    public void remove(int offs, int len) throws BadLocationException {
        throw new UnsupportedOperationException("Document is immutable");
    }

    @Override
    public void insertString(int offset, String str, AttributeSet a)
            throws BadLocationException {
        throw new UnsupportedOperationException("Document is immutable");
    }

    @Override
    public String getText(int offset, int length) throws BadLocationException {
        char[] out = new char[length];
        charBuffer.position(offset);
        charBuffer.get(out);
        return new String(out);
    }

    @Override
    public void getText(int offset, int length, Segment segment) throws BadLocationException {
        segment.array = new char[length];
        charBuffer.position(offset);
        charBuffer.get(segment.array);
        segment.offset = 0;
        segment.count = length;
    }

    @Override
    public Position getStartPosition() {
        return createPosition(0);
    }

    @Override
    public Position getEndPosition() {
        return createPosition(getLength());
    }

    @Override
    public Position createPosition(int offset) {
        return new ImmutablePosition(offset);
    }

    private final Element rootElement = new Element() {
        @Override
        public Document getDocument() {
            return CharBufferDocument.this;
        }

        @Override
        public Element getParentElement() {
            return null;
        }

        @Override
        public String getName() {
            return "root";
        }

        @Override
        public AttributeSet getAttributes() {
            return null;
        }

        @Override
        public int getStartOffset() {
            return 0;
        }

        @Override
        public int getEndOffset() {
            return getLength();
        }

        @Override
        public int getElementIndex(int offset) {
            // binary search for the line that contains offset
            int low = 0;
            int high = lineOffsets.length - 1;

            while (low <= high) {
                int mid = (low + high) >>> 1;
                int midLineStart = lineOffsets[mid];
                int midLineEnd = (mid + 1 < lineOffsets.length) ? (lineOffsets[mid + 1] - 1) : getLength();

                if (offset < midLineStart) {
                    high = mid - 1;
                } else if (offset > midLineEnd) {
                    low = mid + 1;
                } else {
                    return mid;
                }
            }
            return 0;
        }

        @Override
        public int getElementCount() {
            return lineOffsets.length;
        }

        @Override
        public Element getElement(int index) {
            return createLineElement(
                lineOffsets[index],
                (index + 1 < lineOffsets.length) ? (lineOffsets[index + 1] - 1) : getLength()
            );
        }

        @Override
        public boolean isLeaf() {
            return false;
        }
    };

    private Element createLineElement(final int start, final int end) {
        return new Element() {
            @Override
            public Document getDocument() {
                return CharBufferDocument.this;
            }

            @Override
            public Element getParentElement() {
                return CharBufferDocument.this.getDefaultRootElement();
            }

            @Override
            public String getName() {
                return "line"; // XXX: Does the name matter?
            }

            @Override
            public AttributeSet getAttributes() {
                return null;
            }

            @Override
            public int getStartOffset() {
                return start;
            }

            @Override
            public int getEndOffset() {
                return end;
            }

            @Override
            public int getElementIndex(int offset) {
                return -1;
            }

            @Override
            public int getElementCount() {
                return 0;
            }

            @Override
            public Element getElement(int index) {
                return null;
            }

            @Override
            public boolean isLeaf() {
                return true;
            }
        };
    }

    @Override
    public Element getDefaultRootElement() {
        return rootElement;
    }

    @Override
    public Element[] getRootElements() {
        return new Element[] { getDefaultRootElement() };
    }

    @Override
    public void render(Runnable r) {
        r.run();
    }

    private static class ImmutablePosition implements Position {
        private final int offset;

        private ImmutablePosition(int offset) {
            this.offset = offset;
        }

        @Override
        public int getOffset() {
            return offset;
        }
    }
}

关于java - Swing 只读取 AbstractDocument.Content 的第一个字符。我究竟做错了什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20174827/

相关文章:

java - JTable自动更新相关

java - AudioClip 音量问题

c# - richtextbox 到字符串

java - 运行时结构可以在开放 MPI 中使用吗?

java - 避免 JNI C 与 Java 之间的内存泄漏

java - 将一维数组保存到另一个二维数组中

java - JTextField 文档监听器/文档事件

java - 无法摆脱 Split 包,多个 jars 提供相同的包

java - 有可用于 Java Swing 的好的免费日期和时间选择器吗?

javascript - document.location.href 的问题