【问题标题】:Swing only reads the first character of my AbstractDocument.Content. What am I doing wrong?Swing 只读取我的 AbstractDocument.Content 的第一个字符。我究竟做错了什么?
【发布时间】:2013-11-25 03:50:23
【问题描述】:

我的作品中有一个功能,我应该在其中显示一个巨大的文本文件。所以我想一旦文件超过一定大小,我会尝试在磁盘上映射文件。

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

这是我目前所拥有的:

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 中并没有太多看起来可能出错的地方,所以我很困惑。

这是怎么回事?

【问题讨论】:

    标签: java swing document large-files


    【解决方案1】:

    我并不精通 Document API,但我的理解是 AbstractDocument.Content 预计不会预先配备自己的文本。 AbstractDocument 不知道在这种情况下该怎么做。如果你用 StringContent 试试这个,你会看到同样的问题:

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

    这仅显示前导 's'。显然, AbstractDocument 在文档末尾为插入符号建模了一个额外的有效位置,并且它希望其 Content 的长度 +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;
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2013-08-06
      • 1970-01-01
      • 2016-01-24
      • 2016-07-18
      • 2019-09-08
      • 2019-12-23
      • 2014-06-15
      • 1970-01-01
      • 2015-08-26
      相关资源
      最近更新 更多