diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxDocument.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxDocument.java
index a4f86b275..80b07ceff 100755
--- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxDocument.java
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxDocument.java
@@ -22,6 +22,7 @@
import org.fife.ui.rsyntaxtextarea.modes.AbstractMarkupTokenMaker;
import org.fife.ui.rtextarea.RDocument;
+import org.fife.ui.rtextarea.RGapContent;
import org.fife.util.DynamicIntArray;
@@ -110,6 +111,11 @@ public RSyntaxDocument(String syntaxStyle) {
* @param syntaxStyle The syntax highlighting scheme to use.
*/
public RSyntaxDocument(TokenMakerFactory tmf, String syntaxStyle) {
+ this(tmf, syntaxStyle, new RGapContent());
+ }
+
+ public RSyntaxDocument(TokenMakerFactory tmf, String syntaxStyle, Content content) {
+ super(content);
putProperty(tabSizeAttribute, 5);
lastTokensOnLines = new DynamicIntArray(400);
lastTokensOnLines.add(Token.NULL); // Initial (empty) line.
@@ -531,6 +537,9 @@ private void setSharedSegment(int line) {
}
+ public TokenMakerFactory getTokenMakerFactory() {
+ return tokenMakerFactory;
+ }
/**
* Sets the syntax style being used for syntax highlighting in this
@@ -680,4 +689,7 @@ private void updateSyntaxHighlightingInformation() {
}
+ public boolean isEditable() {
+ return true;
+ }
}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextArea.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextArea.java
index 37ef18642..c20d6d3c0 100755
--- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextArea.java
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextArea.java
@@ -681,7 +681,7 @@ public void copyAsStyledText() {
* @return The document.
*/
@Override
- protected Document createDefaultModel() {
+ protected RSyntaxDocument createDefaultModel() {
return new RSyntaxDocument(SYNTAX_STYLE_NONE);
}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TextEditorPane.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TextEditorPane.java
index 1b7bd97f3..7f0a9b9d4 100755
--- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TextEditorPane.java
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TextEditorPane.java
@@ -9,21 +9,22 @@
*/
package org.fife.ui.rsyntaxtextarea;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.nio.charset.UnsupportedCharsetException;
+import org.fife.io.UnicodeReader;
+import org.fife.io.UnicodeWriter;
+import org.fife.ui.rtextarea.RTextAreaEditorKit;
+import org.fife.ui.rtextarea.readonly.ReadOnlyContent;
+import org.fife.ui.rtextarea.readonly.ReadOnlyDocument;
+import org.fife.ui.rtextarea.readonly.ReadOnlyFileStructure;
+import org.fife.ui.rtextarea.readonly.ReadOnlyFileStructureParser;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
+import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
-
-import org.fife.io.UnicodeReader;
-import org.fife.io.UnicodeWriter;
-import org.fife.ui.rtextarea.RTextAreaEditorKit;
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
+import java.nio.file.Path;
/**
* An extension of {@link org.fife.ui.rsyntaxtextarea.RSyntaxTextArea}
@@ -456,18 +457,17 @@ public void load(FileLocation loc) throws IOException {
* @see #save()
* @see #saveAs(FileLocation)
*/
- public void load(FileLocation loc, Charset defaultEnc) throws IOException {
- load(loc, defaultEnc == null ? null : defaultEnc.name());
+ public void load(FileLocation loc, String defaultEnc) throws IOException {
+ load(loc, defaultEnc == null ? Charset.defaultCharset() : Charset.forName(defaultEnc));
}
-
/**
* Loads the specified file in this editor. This method fires a property
* change event of type {@link #FULL_PATH_PROPERTY}.
*
* @param loc The location of the file to load. This cannot be
* null.
- * @param defaultEnc The encoding to use when loading/saving the file.
+ * @param defaultCharset The encoding to use when loading/saving the file.
* This encoding will only be used if the file is not Unicode.
* If this value is null, the system default encoding
* is used.
@@ -477,41 +477,73 @@ public void load(FileLocation loc, Charset defaultEnc) throws IOException {
* @see #save()
* @see #saveAs(FileLocation)
*/
- public void load(FileLocation loc, String defaultEnc) throws IOException {
+ public void load(FileLocation loc, Charset defaultCharset) throws IOException {
+ Charset fileLocationCharset = getFileLocationCharset(loc, defaultCharset);
+ RSyntaxDocument doc = createEditableDocument(loc, fileLocationCharset);
+ loadDocument(loc, doc, defaultCharset);
+ }
- // For new local files, just go with it.
- if (loc.isLocal() && !loc.isLocalAndExists()) {
- this.charSet = defaultEnc!=null ? defaultEnc : getDefaultEncoding();
- this.loc = loc;
- setText(null);
- discardAllEdits();
- setDirty(false);
- return;
- }
+ public void loadLocalFileInReadOnlyDocument(File file, Charset defaultCharset) throws IOException {
+ FileLocation fileLocation = FileLocation.create(file);
+ Charset fileLocationCharset = getFileLocationCharset(fileLocation, defaultCharset);
+ RSyntaxDocument doc = createLazyLoadDocument(fileLocation, fileLocationCharset);
+ loadDocument(fileLocation, doc, defaultCharset);
+ }
- // Old local files and remote files, load 'em up. UnicodeReader will
- // check for BOMs and handle them correctly in all cases, then pass
- // rest of stream down to InputStreamReader.
- UnicodeReader ur = new UnicodeReader(loc.getInputStream(), defaultEnc);
+ private Charset getFileLocationCharset(FileLocation fileLocation, Charset defaultCharset) throws IOException {
+ try( InputStream is = fileLocation.getInputStream() ){
+ UnicodeReader ur = new UnicodeReader(is, defaultCharset);
+ defaultCharset = Charset.forName(ur.getEncoding());
+ }
+ return defaultCharset;
+ }
- // Remove listener so dirty flag doesn't get set when loading a file.
- Document doc = getDocument();
- doc.removeDocumentListener(this);
- try (BufferedReader r = new BufferedReader(ur)) {
- read(r, null);
- } finally {
- doc.addDocumentListener(this);
+ private RSyntaxDocument createEditableDocument(FileLocation loc, Charset charset) throws IOException {
+ RSyntaxDocument doc = createDefaultModel();
+ // For new local files, just go with it.
+ if( !loc.isLocal() || loc.isLocalAndExists() ){
+ try( InputStreamReader r = new InputStreamReader(loc.getInputStream(), charset) ){
+ RTextAreaEditorKit kit = (RTextAreaEditorKit) getUI().getEditorKit(this);
+ try{
+ // NOTE: Resets the "line separator" property.
+ kit.read(r, doc, 0);
+ } catch( BadLocationException e ){
+ throw new IOException(e.getMessage());
+ }
+ }
}
+ return doc;
+ }
- // No IOException thrown, so we can finally change the location.
- charSet = ur.getEncoding();
- String old = getFileFullPath();
- this.loc = loc;
+ private RSyntaxDocument createLazyLoadDocument(FileLocation fileLocation, Charset charSet) throws IOException {
+ if( !fileLocation.isLocalAndExists() ){
+ throw new FileNotFoundException("ReadOnlyDocument supports only files from local file system");
+ }
+ Path filePath = Path.of(fileLocation.getFileFullPath());
+ ReadOnlyFileStructureParser fileStructureParser = new ReadOnlyFileStructureParser(filePath, charSet);
+ ReadOnlyFileStructure readOnlyFileStructure = fileStructureParser.readStructure();
+ ReadOnlyContent content = new ReadOnlyContent(filePath, charSet, readOnlyFileStructure);
+ return new ReadOnlyDocument(getTokenMarkerFactory(), getSyntaxEditingStyle(), content);
+ }
+
+ public void loadDocument(FileLocation fileLocation, RSyntaxDocument doc, Charset charSet) {
+ this.charSet = charSet.name();
+ String oldFileFullPath = getFileFullPath();
+ setDocument(doc);
+ this.loc = fileLocation;
setDirty(false);
setCaretPosition(0);
discardAllEdits();
- firePropertyChange(FULL_PATH_PROPERTY, old, getFileFullPath());
+ firePropertyChange(FULL_PATH_PROPERTY, oldFileFullPath, getFileFullPath());
+ setReadOnly(isReadOnly() && doc.isEditable());
+ }
+ public TokenMakerFactory getTokenMarkerFactory() {
+ Document document = getDocument();
+ if( document instanceof RSyntaxDocument ){
+ return ((RSyntaxDocument) document).getTokenMakerFactory();
+ }
+ return null;
}
@@ -701,7 +733,7 @@ public void setLineSeparator(String separator) {
/**
* Sets the line separator sequence to use when this file is saved (e.g.
* "\n", "\r\n" or "\r").
- *
+ *
* Besides parameter checking, this method is preferred over
* getDocument().putProperty() because can set the editor's
* dirty flag when the line separator is changed.
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/RContent.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/RContent.java
new file mode 100644
index 000000000..d49012035
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/RContent.java
@@ -0,0 +1,7 @@
+package org.fife.ui.rtextarea;
+
+import javax.swing.text.BadLocationException;
+
+public interface RContent {
+ char charAt(int offset) throws BadLocationException;
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/RDocument.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/RDocument.java
index 4328935e3..bf69dd3f8 100755
--- a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/RDocument.java
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/RDocument.java
@@ -9,7 +9,6 @@
package org.fife.ui.rtextarea;
import javax.swing.text.BadLocationException;
-import javax.swing.text.GapContent;
import javax.swing.text.PlainDocument;
@@ -21,14 +20,16 @@
*/
public class RDocument extends PlainDocument {
-
/**
* Constructor.
*/
public RDocument() {
- super(new RGapContent());
+ this(new RGapContent());
}
+ public RDocument(Content content) {
+ super(content);
+ }
/**
* Returns the character in the document at the specified offset.
@@ -38,28 +39,6 @@ public RDocument() {
* @throws BadLocationException If the offset is invalid.
*/
public char charAt(int offset) throws BadLocationException {
- return ((RGapContent)getContent()).charAt(offset);
+ return ((RContent) getContent()).charAt(offset);
}
-
-
- /**
- * Document content that provides fast access to individual characters.
- */
- private static class RGapContent extends GapContent {
-
- public char charAt(int offset) throws BadLocationException {
- if (offset<0 || offset>=length()) {
- throw new BadLocationException("Invalid offset", offset);
- }
- int g0 = getGapStart();
- char[] array = (char[]) getArray();
- if (offset= length() ){
+ throw new BadLocationException("Invalid offset", offset);
+ }
+ int g0 = getGapStart();
+ char[] array = (char[]) getArray();
+ if( offset < g0 ){ // below gap
+ return array[offset];
+ }
+ return array[getGapEnd() + offset - g0]; // above gap
+ }
+
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/FileCharsCounter.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/FileCharsCounter.java
new file mode 100644
index 000000000..69c621e2a
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/FileCharsCounter.java
@@ -0,0 +1,14 @@
+package org.fife.ui.rtextarea.readonly;
+
+public class FileCharsCounter {
+
+ int count = 2;
+
+ public void onBufferRead(int readChars) {
+ count += readChars;
+ }
+
+ public int getCount() {
+ return count;
+ }
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/FileOffsetLineDiscover.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/FileOffsetLineDiscover.java
new file mode 100644
index 000000000..8dcd1e946
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/FileOffsetLineDiscover.java
@@ -0,0 +1,56 @@
+package org.fife.ui.rtextarea.readonly;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class FileOffsetLineDiscover {
+
+ int lastOffset = 0;
+
+ private final List lfOffsets = new ArrayList<>();
+ private final Set crOffsets = new HashSet<>();
+
+ public List getOffsets() {
+
+ List lineOffsets = new ArrayList<>();
+ lineOffsets.add(0);
+
+ for( Integer newLineIndex : lfOffsets ){
+
+ //reconize \n\r LFCR
+ if( crOffsets.contains(newLineIndex + 1) ){
+ int lfcrIndex = newLineIndex + 1;
+ lineOffsets.add(lfcrIndex);
+ crOffsets.remove(lfcrIndex);
+ }
+
+ //recognize \r\n CRLF
+ else if( crOffsets.contains(newLineIndex - 1) ){
+ int crlfOffset = newLineIndex - 1;
+ lineOffsets.add(newLineIndex);
+ crOffsets.remove(crlfOffset);
+ }
+
+ //add LF
+ else lineOffsets.add(newLineIndex);
+ }
+
+ lineOffsets.addAll(crOffsets);
+ lineOffsets.sort(Integer::compare);
+ return lineOffsets;
+ }
+
+ public void onBufferRead(char[] charBuffer, int readChars) {
+ for( int i = 0; i < readChars; i++ ){
+ lastOffset++;
+ char character = charBuffer[i];
+ if( character == '\n' ){
+ lfOffsets.add(lastOffset);
+ } else if( character == '\r' ){
+ crOffsets.add(lastOffset);
+ }
+ }
+ }
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/NullReadOnlyContent.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/NullReadOnlyContent.java
new file mode 100644
index 000000000..879f8e671
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/NullReadOnlyContent.java
@@ -0,0 +1,65 @@
+package org.fife.ui.rtextarea.readonly;
+
+import org.fife.ui.rtextarea.readonly.ReadOnlyContentInterface;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Position;
+import javax.swing.text.Segment;
+import javax.swing.undo.UndoableEdit;
+
+public class NullReadOnlyContent implements ReadOnlyContentInterface {
+ @Override
+ public char charAt(int offset) {
+ return 0;
+ }
+
+ @Override
+ public int getStartOffset(int row) {
+ return 0;
+ }
+
+ @Override
+ public int getEndOffset(int index) {
+ return 0;
+ }
+
+ @Override
+ public int getElementCount() {
+ return 0;
+ }
+
+ @Override
+ public int getElementIndex(int offset) {
+ return 0;
+ }
+
+ @Override
+ public Position createPosition(int offset) {
+ return null;
+ }
+
+ @Override
+ public int length() {
+ return 0;
+ }
+
+ @Override
+ public UndoableEdit insertString(int where, String str) throws BadLocationException {
+ return null;
+ }
+
+ @Override
+ public UndoableEdit remove(int where, int nitems) throws BadLocationException {
+ return null;
+ }
+
+ @Override
+ public String getString(int where, int len) throws BadLocationException {
+ return "";
+ }
+
+ @Override
+ public void getChars(int where, int len, Segment txt) throws BadLocationException {
+
+ }
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyContent.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyContent.java
new file mode 100644
index 000000000..55ae0b207
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyContent.java
@@ -0,0 +1,157 @@
+package org.fife.ui.rtextarea.readonly;
+
+import org.fife.io.UnicodeReader;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Position;
+import javax.swing.text.Segment;
+import javax.swing.undo.UndoableEdit;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.util.List;
+
+public class ReadOnlyContent implements ReadOnlyContentInterface {
+
+ private static final int MAX_BUFFER_SIZE = 1024 * 1024; //1 MB
+ private static final int NOT_INITIALIZED_BUFFER_OFFSET = -1;
+
+ private final Path filePath;
+ private final Charset charset;
+ private final List offsets;
+ private final int bufferSize;
+ private final int fileSize;
+ private final int tailOffset;
+ private final char[] buffer;
+ private final int charsCount;
+
+ private int startBufferOffset = NOT_INITIALIZED_BUFFER_OFFSET;
+
+ public ReadOnlyContent(Path filePath, Charset charset, ReadOnlyFileStructure fileStructure) {
+ this.filePath = filePath;
+ this.charset = charset;
+ fileSize = (int) fileStructure.getFileSize();
+ this.tailOffset = fileStructure.getTailOffset();
+ bufferSize = Math.min(fileSize, MAX_BUFFER_SIZE);
+ buffer = new char[bufferSize];
+ offsets = fileStructure.getOffsets();
+ charsCount = fileStructure.getCharsCount();
+ }
+
+
+ @Override
+ public Position createPosition(int offset) {
+ return () -> offset;
+ }
+
+ @Override
+ public int length() {
+ return charsCount;
+ }
+
+ @Override
+ public UndoableEdit insertString(int where, String str) throws BadLocationException {
+ throw new BadLocationException("Unable to insert String. This content is not editable", where);
+ }
+
+ @Override
+ public UndoableEdit remove(int where, int nitems) throws BadLocationException {
+ throw new BadLocationException("Unable to remove String. This content is not editable", where);
+ }
+
+ @Override
+ public String getString(int charsOffset, int amountCharsToRead) throws BadLocationException {
+ int endReadOffset = charsOffset + amountCharsToRead;
+
+ if( endReadOffset > length() ){
+ throw new BadLocationException("Unable to get String. The requested end offset exceed the document length: ", charsOffset + amountCharsToRead);
+ }
+
+ int endBuffedOffset = buffer.length + startBufferOffset;
+
+ if( startBufferOffset == NOT_INITIALIZED_BUFFER_OFFSET || charsOffset < startBufferOffset || endReadOffset > endBuffedOffset ){
+ reloadBuffer(charsOffset);
+ }
+ int relativeIndex = Math.min(charsOffset - startBufferOffset, bufferSize - amountCharsToRead);
+ try{
+ return new String(buffer, relativeIndex, amountCharsToRead);
+ } catch( Exception e ){
+ throw new ReadOnlyBadLocationException("Unable to read String", charsOffset, e);
+ }
+ }
+
+
+ private void reloadBuffer(int startReadOffset) throws BadLocationException {
+ if( fileSize < startReadOffset + bufferSize ){
+ startReadOffset = Math.max(0, fileSize - bufferSize);
+ }
+ this.startBufferOffset = startReadOffset;
+
+ read(startReadOffset, bufferSize);
+ }
+
+ private void read(int startReadOffset, int charsToRead) throws BadLocationException {
+
+ try( UnicodeReader bufferedReader = new UnicodeReader(filePath.toFile(), charset); ){
+ bufferedReader.skip(tailOffset);
+ bufferedReader.skip(startReadOffset);
+ bufferedReader.read(buffer, 0, charsToRead);
+ } catch( Exception e ){
+ throw new ReadOnlyBadLocationException("Unable to read file", startReadOffset, e);
+ }
+ }
+
+ @Override
+ public void getChars(int where, int len, Segment chars) throws BadLocationException {
+ String content = getString(where, len);
+
+ chars.array = content.toCharArray();
+ chars.offset = 0;
+ chars.count = len;
+ }
+
+ @Override
+ public char charAt(int offset) throws BadLocationException {
+ try{
+ return getString(offset, 1).charAt(0);
+ } catch( BadLocationException e ){
+ throw new ReadOnlyBadLocationException("Invalid offset", offset, e);
+ }
+ }
+
+ @Override
+ public int getStartOffset(int row) {
+ return offsets.get(row);
+ }
+
+ @Override
+ public int getEndOffset(int index) {
+ if( index < offsets.size() - 1 )
+ return offsets.get(index + 1);
+ else return offsets.get(offsets.size() - 1) + 1;
+ }
+
+ @Override
+ public int getElementCount() {
+ return offsets.size();
+ }
+
+ @Override
+ public int getElementIndex(int offset) {
+ if( offsets.isEmpty() ) return 0;
+
+ for( int i = 0; i < offsets.size() - 1; i++ ){
+ if( offset < offsets.get(i + 1) ){
+ return i;
+ }
+ }
+ return offsets.size() - 1;
+ }
+
+ private static class ReadOnlyBadLocationException extends BadLocationException {
+
+ public ReadOnlyBadLocationException(String s, int offs, Exception cause) {
+ super(s, offs);
+ setStackTrace(cause.getStackTrace());
+ }
+ }
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyContentInterface.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyContentInterface.java
new file mode 100644
index 000000000..cf4476e9e
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyContentInterface.java
@@ -0,0 +1,18 @@
+package org.fife.ui.rtextarea.readonly;
+
+import org.fife.ui.rtextarea.RContent;
+
+import javax.swing.text.AbstractDocument;
+import javax.swing.text.BadLocationException;
+
+public interface ReadOnlyContentInterface extends AbstractDocument.Content, RContent {
+ char charAt(int offset) throws BadLocationException;
+
+ int getStartOffset(int row);
+
+ int getEndOffset(int index);
+
+ int getElementCount();
+
+ int getElementIndex(int offset);
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyDocument.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyDocument.java
new file mode 100644
index 000000000..cbd591a02
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyDocument.java
@@ -0,0 +1,33 @@
+package org.fife.ui.rtextarea.readonly;
+
+import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
+import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
+import org.fife.ui.rsyntaxtextarea.TokenTypes;
+
+import javax.swing.text.Element;
+
+//This document is useful to read a huge files, like Log Files
+public class ReadOnlyDocument extends RSyntaxDocument {
+
+ private ReadOnlyRootElement readOnlyRootElement;
+
+ public ReadOnlyDocument(TokenMakerFactory tmf, String syntaxStyle, ReadOnlyContentInterface content) {
+ super(tmf, syntaxStyle, content);
+ readOnlyRootElement.setContent(content);
+ lastTokensOnLines.clear();
+ lastTokensOnLines.insertRange(0, content.getElementCount(), TokenTypes.NULL);
+ }
+
+ @Override
+ public Element getDefaultRootElement() {
+ if( readOnlyRootElement == null ){
+ readOnlyRootElement = new ReadOnlyRootElement(this);
+ }
+ return readOnlyRootElement;
+ }
+
+ @Override
+ public boolean isEditable() {
+ return false;
+ }
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyFileStructure.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyFileStructure.java
new file mode 100644
index 000000000..006cf93a8
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyFileStructure.java
@@ -0,0 +1,34 @@
+package org.fife.ui.rtextarea.readonly;
+
+import java.util.List;
+
+public class ReadOnlyFileStructure {
+
+ private final int charsCount;
+ private final List offsets;
+ private final long fileSize;
+ private final int tailOffset;
+
+ public ReadOnlyFileStructure(int charsCount, List offsets, long fileSize, int tailOffset) {
+ this.charsCount = charsCount;
+ this.offsets = offsets;
+ this.fileSize = fileSize;
+ this.tailOffset = tailOffset;
+ }
+
+ public int getCharsCount() {
+ return charsCount;
+ }
+
+ public List getOffsets() {
+ return offsets;
+ }
+
+ public long getFileSize() {
+ return fileSize;
+ }
+
+ public int getTailOffset() {
+ return tailOffset;
+ }
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyFileStructureParser.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyFileStructureParser.java
new file mode 100644
index 000000000..28a9990ae
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyFileStructureParser.java
@@ -0,0 +1,45 @@
+package org.fife.ui.rtextarea.readonly;
+
+import org.fife.io.UnicodeReader;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class ReadOnlyFileStructureParser {
+
+ private static final int BUFFER_SIZE = 1024 * 1024;
+ private final Path filePath;
+ private final Charset charset;
+ private final int tailOffset;
+
+ public ReadOnlyFileStructureParser(Path filePath, Charset charset) {
+ this(filePath, charset, 0);
+ }
+
+ //tailOffset can be used to exclude the header of a file
+ public ReadOnlyFileStructureParser(Path filePath, Charset charset, int tailOffset) {
+ this.filePath = filePath;
+ this.charset = charset;
+ this.tailOffset = tailOffset;
+ }
+
+ public ReadOnlyFileStructure readStructure() throws IOException {
+
+ try( UnicodeReader bufferedReader = new UnicodeReader(filePath.toFile(), charset) ){
+ bufferedReader.skip(tailOffset);
+ char[] charBuffer = new char[BUFFER_SIZE];
+ FileCharsCounter fileCharsCounter = new FileCharsCounter();
+ FileOffsetLineDiscover fileOffsetLineDiscover = new FileOffsetLineDiscover();
+
+ int readChars = 0;
+ while( readChars >= 0 ){
+ readChars = bufferedReader.read(charBuffer);
+ fileCharsCounter.onBufferRead(readChars);
+ fileOffsetLineDiscover.onBufferRead(charBuffer, readChars);
+ }
+ return new ReadOnlyFileStructure(fileCharsCounter.getCount(), fileOffsetLineDiscover.getOffsets(), Files.size(filePath), tailOffset);
+ }
+ }
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyLeafElement.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyLeafElement.java
new file mode 100644
index 000000000..1170d36f0
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyLeafElement.java
@@ -0,0 +1,73 @@
+package org.fife.ui.rtextarea.readonly;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.SimpleAttributeSet;
+
+public class ReadOnlyLeafElement implements Element {
+
+ private final AttributeSet attributeSet = new SimpleAttributeSet();
+
+ private final Document document;
+ private final Element parentElement;
+ private final ReadOnlyContentInterface content;
+ private final int index;
+
+ public ReadOnlyLeafElement(Document document, Element parentElement, ReadOnlyContentInterface content, int index) {
+ this.document = document;
+ this.parentElement = parentElement;
+ this.content = content;
+ this.index = index;
+ }
+
+ @Override
+ public Document getDocument() {
+ return document;
+ }
+
+ @Override
+ public Element getParentElement() {
+ return parentElement;
+ }
+
+ @Override
+ public String getName() {
+ return "content";
+ }
+
+ @Override
+ public AttributeSet getAttributes() {
+ return attributeSet;
+ }
+
+ @Override
+ public int getStartOffset() {
+ return content.getStartOffset(index);
+ }
+
+ @Override
+ public int getEndOffset() {
+ return content.getEndOffset(index);
+ }
+
+ @Override
+ public int getElementIndex(int offset) {
+ return 0;
+ }
+
+ @Override
+ public int getElementCount() {
+ return 0;
+ }
+
+ @Override
+ public Element getElement(int index) {
+ return null;
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return true;
+ }
+}
diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyRootElement.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyRootElement.java
new file mode 100644
index 000000000..41c72fdb8
--- /dev/null
+++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rtextarea/readonly/ReadOnlyRootElement.java
@@ -0,0 +1,73 @@
+package org.fife.ui.rtextarea.readonly;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.SimpleAttributeSet;
+
+public class ReadOnlyRootElement implements Element {
+
+ private final AttributeSet attributeSet = new SimpleAttributeSet();
+
+ private final Document document;
+
+ private ReadOnlyContentInterface content = new NullReadOnlyContent();
+
+ public ReadOnlyRootElement(ReadOnlyDocument document) {
+ this.document = document;
+ }
+
+ @Override
+ public Document getDocument() {
+ return document;
+ }
+
+ @Override
+ public Element getParentElement() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "paragraph";
+ }
+
+ @Override
+ public AttributeSet getAttributes() {
+ return attributeSet;
+ }
+
+ @Override
+ public int getStartOffset() {
+ return 0;
+ }
+
+ @Override
+ public int getEndOffset() {
+ return content.length();
+ }
+
+ @Override
+ public int getElementIndex(int offset) {
+ return content.getElementIndex(offset);
+ }
+
+ @Override
+ public int getElementCount() {
+ return content.getElementCount();
+ }
+
+ @Override
+ public Element getElement(int index) {
+ return new ReadOnlyLeafElement(document, this, content, index);
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return false;
+ }
+
+ public void setContent(ReadOnlyContentInterface content) {
+ this.content = content;
+ }
+}
diff --git a/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/FileBuilderTestUtil.java b/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/FileBuilderTestUtil.java
new file mode 100644
index 000000000..effdd986f
--- /dev/null
+++ b/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/FileBuilderTestUtil.java
@@ -0,0 +1,41 @@
+package org.fife.ui.rtextarea.readonly;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class FileBuilderTestUtil {
+
+ public static void writeTestFileWithAsciiChars(Path filePath, Charset charset, String lineSeparator, int rows) throws IOException {
+ writeTestFile(filePath, charset, lineSeparator, "abcdefghi", rows, true);
+ }
+
+ public static void writeTestFileWithDoubleByteChar(Path filePath, Charset charset, String lineSeparator, int rows) throws IOException {
+ writeTestFile(filePath, charset, lineSeparator, "Aݔabcdefg", rows, true);
+ }
+
+ public static void writeTestFile(Path filePath, Charset charset, String lineSeparator, String content, int rows, boolean includeBom) throws IOException {
+
+ if( includeBom ){
+ String text = lineSeparator;
+ try( BufferedWriter writer = Files.newBufferedWriter(filePath, charset) ){
+ for( int i = 0; i < rows; i++ ){
+ text = content + text;
+ writer.write(text);
+ }
+ }
+ } else{
+ try( OutputStream out = new FileOutputStream(filePath.toFile()) ){
+ String text = lineSeparator;
+ for( int i = 0; i < rows; i++ ){
+ text = content + text;
+ out.write(text.getBytes(charset));
+ }
+ }
+ }
+ }
+}
diff --git a/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/FileOffsetLineDiscoverTest.java b/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/FileOffsetLineDiscoverTest.java
new file mode 100644
index 000000000..8d18dd147
--- /dev/null
+++ b/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/FileOffsetLineDiscoverTest.java
@@ -0,0 +1,70 @@
+package org.fife.ui.rtextarea.readonly;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class FileOffsetLineDiscoverTest {
+
+ @Test
+ void testOffsetsWithLFCRSeparator(@TempDir Path tempDir) throws IOException {
+ Path filePath = tempDir.resolve("template.txt");
+ testOffsets("\n\r", filePath);
+ }
+
+ @Test
+ void testOffsetsWithCRLFSeparator(@TempDir Path tempDir) throws IOException {
+ Path filePath = tempDir.resolve("template.txt");
+ testOffsets("\r\n", filePath);
+ }
+
+ @Test
+ void testOffsetsWithCRSeparator(@TempDir Path tempDir) throws IOException {
+ Path filePath = tempDir.resolve("template.txt");
+ testOffsets("\r", filePath);
+ }
+
+ @Test
+ void testOffsetsWithLFSeparator(@TempDir Path tempDir) throws IOException {
+ Path filePath = tempDir.resolve("template.txt");
+ testOffsets("\n", filePath);
+ }
+
+ private void testOffsets(String separator, Path filePath) throws IOException {
+ Charset charset = StandardCharsets.UTF_8;
+ int rows = 2048;
+ FileBuilderTestUtil.writeTestFileWithAsciiChars(filePath, charset, separator, rows);
+
+ FileOffsetLineDiscover fileOffsetLineDiscover = new FileOffsetLineDiscover();
+ try( BufferedReader bufferedReader = Files.newBufferedReader(filePath, charset); ){
+ char[] charBuffer = new char[1024];
+ int readChars = 0;
+ while( readChars >= 0 ){
+ readChars = bufferedReader.read(charBuffer);
+ fileOffsetLineDiscover.onBufferRead(charBuffer, readChars);
+ }
+ }
+ List offsets = fileOffsetLineDiscover.getOffsets();
+
+
+ List expectedOffsets = new ArrayList<>();
+ int separatorSize = separator.length();
+ int last = 0;
+ for( int i = 0; i <= rows; i++ ){
+ last = (last + separatorSize) + i * 9;
+ expectedOffsets.add(last - separatorSize);
+ }
+
+ assertEquals(expectedOffsets, offsets);
+ }
+}
diff --git a/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/ReadOnlyContentTest.java b/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/ReadOnlyContentTest.java
new file mode 100644
index 000000000..503b3efae
--- /dev/null
+++ b/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/ReadOnlyContentTest.java
@@ -0,0 +1,100 @@
+package org.fife.ui.rtextarea.readonly;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.GapContent;
+import javax.swing.text.Segment;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ReadOnlyContentTest {
+
+ private static GapContent expectedContent;
+ private static ReadOnlyContent readOnlyContent;
+
+ @BeforeAll
+ static void beforeAll(@TempDir Path tempDir) throws Exception {
+ Path filePath = tempDir.resolve("template.txt");
+ Charset charset = StandardCharsets.UTF_16;
+ FileBuilderTestUtil.writeTestFileWithAsciiChars(filePath, charset, "\n", 128);
+ expectedContent = new GapContent();
+ String readString = Files.readString(filePath, charset);
+ expectedContent.insertString(0, readString);
+ ReadOnlyFileStructure fileStructure = new ReadOnlyFileStructureParser(filePath, charset).readStructure();
+ readOnlyContent = new ReadOnlyContent(filePath, charset, fileStructure);
+ }
+
+ @Test
+ void testGetLength() {
+ assertEquals(expectedContent.length(), readOnlyContent.length());
+ }
+
+ @Test
+ void testGetStringWithSingleChar() throws BadLocationException {
+ int last = expectedContent.length() - 1;
+ for( int i = 0; i < last; i++ ){
+ testGetString(i, 1);
+ }
+ }
+
+ @Test
+ void testGetStringWithTwoChars() throws BadLocationException {
+ int last = expectedContent.length() - 1;
+ for( int i = 0; i < last - 1; i++ ){
+ testGetString(i, 2);
+ }
+ }
+
+ @Test
+ void testGetStringThrowsExceptionWhenOffsetExceedLength() {
+ int length = expectedContent.length();
+ testGetStringThrowsException(length, 1);
+ testGetStringThrowsException(length - 1, 2);
+ }
+
+ @Test
+ void testGetSegmentWithSingleChar() throws BadLocationException {
+ int last = expectedContent.length() - 1;
+ for( int i = 0; i < last; i++ ){
+ testGetSegment(i, 1);
+ }
+ }
+
+ @Test
+ void testGetSegmentWithTwoChars() throws BadLocationException {
+ int last = expectedContent.length() - 1;
+ for( int i = 0; i < last - 1; i++ ){
+ testGetSegment(i, 2);
+ }
+ }
+
+ private void testGetString(int where, int len) throws BadLocationException {
+ assertEquals(expectedContent.getString(where, len), readOnlyContent.getString(where, len), () -> "Error on offset where: " + where + " len: " + len);
+ }
+
+ private void testGetStringThrowsException(int where, int len) {
+ assertThrows(BadLocationException.class, () -> expectedContent.getString(where, len));
+ assertThrows(BadLocationException.class, () -> readOnlyContent.getString(where, len));
+ }
+
+
+ private void testGetSegment(int where, int len) throws BadLocationException {
+ Segment testSegment = new Segment();
+ Segment readOnlySegment = new Segment();
+
+ testSegment.setPartialReturn(true);
+ readOnlySegment.setPartialReturn(true);
+ expectedContent.getChars(where, len, testSegment);
+ readOnlyContent.getChars(where, len, readOnlySegment);
+
+ assertEquals(testSegment.toString(), readOnlySegment.toString(), () -> "Error on offset where:" + where);
+ }
+}
diff --git a/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/ReadOnlyContentWithEmptyFileTest.java b/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/ReadOnlyContentWithEmptyFileTest.java
new file mode 100644
index 000000000..86b615c84
--- /dev/null
+++ b/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/ReadOnlyContentWithEmptyFileTest.java
@@ -0,0 +1,85 @@
+package org.fife.ui.rtextarea.readonly;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.GapContent;
+import javax.swing.text.Segment;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ReadOnlyContentWithEmptyFileTest {
+
+ private static GapContent expectedContent;
+ private static ReadOnlyContent readOnlyContent;
+
+ @BeforeAll
+ static void beforeAll(@TempDir Path tempDir) throws Exception {
+ Path filePath = tempDir.resolve("template.txt");
+ Files.createFile(filePath);
+ Charset charset = StandardCharsets.UTF_16;
+ expectedContent = new GapContent();
+ ReadOnlyFileStructure fileStructure = new ReadOnlyFileStructureParser(filePath, charset).readStructure();
+ readOnlyContent = new ReadOnlyContent(filePath, charset, fileStructure);
+ }
+
+ @Test
+ void testGetLength() {
+ assertEquals(expectedContent.length(), readOnlyContent.length());
+ }
+
+ @Test
+ void testGetStringWithSingleChar() throws BadLocationException {
+ int last = expectedContent.length() - 1;
+ for( int i = 0; i < last; i++ ){
+ testGetString(i, 1);
+ }
+ }
+
+ @Test
+ void testGetStringWithTwoChars() throws BadLocationException {
+ int last = expectedContent.length() - 1;
+ for( int i = 0; i < last - 1; i++ ){
+ testGetString(i, 2);
+ }
+ }
+
+ @Test
+ void testGetSegmentWithSingleChar() throws BadLocationException {
+ int last = expectedContent.length() - 1;
+ for( int i = 0; i < last; i++ ){
+ testGetSegment(i, 1);
+ }
+ }
+
+ @Test
+ void testGetSegmentWithTwoChars() throws BadLocationException {
+ int last = expectedContent.length() - 1;
+ for( int i = 0; i < last - 1; i++ ){
+ testGetSegment(i, 2);
+ }
+ }
+
+ private void testGetString(int where, int len) throws BadLocationException {
+ assertEquals(expectedContent.getString(where, len), readOnlyContent.getString(where, len), () -> "Error on offset where: " + where + " len: " + len);
+ }
+
+
+ private void testGetSegment(int where, int len) throws BadLocationException {
+ Segment testSegment = new Segment();
+ Segment readOnlySegment = new Segment();
+
+ testSegment.setPartialReturn(true);
+ readOnlySegment.setPartialReturn(true);
+ expectedContent.getChars(where, len, testSegment);
+ readOnlyContent.getChars(where, len, readOnlySegment);
+
+ assertEquals(testSegment.toString(), readOnlySegment.toString(), () -> "Error on offset where:" + where);
+ }
+}
diff --git a/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/ReadOnlyDocumentLauncher.java b/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/ReadOnlyDocumentLauncher.java
new file mode 100644
index 000000000..b65f2ac6a
--- /dev/null
+++ b/RSyntaxTextArea/src/test/java/org/fife/ui/rtextarea/readonly/ReadOnlyDocumentLauncher.java
@@ -0,0 +1,212 @@
+package org.fife.ui.rtextarea.readonly;
+
+import org.fife.io.UnicodeReader;
+import org.fife.ui.rsyntaxtextarea.FileLocation;
+import org.fife.ui.rsyntaxtextarea.TextEditorPane;
+import org.fife.ui.rtextarea.RTextScrollPane;
+
+import javax.swing.*;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static org.fife.ui.rsyntaxtextarea.SyntaxConstants.SYNTAX_STYLE_XML;
+
+public class ReadOnlyDocumentLauncher {
+
+ private static final Logger LOGGER = Logger.getLogger(ReadOnlyDocumentLauncher.class.getName());
+ private static TextEditorPane textArea = new TextEditorPane();
+
+ public static void main(String[] args) throws IOException {
+ TestApp testApp = new TestApp();
+ testApp.show();
+ }
+
+ private static class TestApp {
+
+ private File documentFile;
+ boolean createBom = true;
+ Charset selectedCharset = StandardCharsets.UTF_8;
+
+ JTextField fileContentField = new JTextField("abcd");
+ JTextField fileRowsField = new JTextField(String.valueOf(20));
+ JTextField tailField = new JTextField(String.valueOf(0));
+
+ JLabel statusLabel = new JLabel(" ");
+ private JProgressBar progressBar = new JProgressBar();
+
+ public void show() {
+ JFrame frame = buildAndDisplayGui();
+ frame.setVisible(true);
+ }
+
+ private JFrame buildAndDisplayGui() {
+ JFrame frame = new JFrame("Test Frame");
+ frame.setMinimumSize(new Dimension(640, 480));
+ frame.setResizable(false);
+
+ frame.setContentPane(createPanel());
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.pack();
+ frame.setLocationRelativeTo(null);
+ frame.setResizable(true);
+ return frame;
+ }
+
+ private JComponent createPanel() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.add(createConfigurationPanel(), BorderLayout.EAST);
+ panel.add(createEditorPanel(), BorderLayout.CENTER);
+ return panel;
+ }
+
+ private JComponent createConfigurationPanel() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ JCheckBox bomCheckbox = new JCheckBox(new AbstractAction("Add BOM") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ createBom = ((JCheckBox) e.getSource()).isSelected();
+ }
+ });
+ bomCheckbox.setSelected(createBom);
+ panel.add(bomCheckbox);
+
+ panel.add(createCharsetPanel());
+
+ panel.add(new JLabel("File content"));
+ panel.add(fileContentField);
+
+ panel.add(new JLabel("Rows"));
+ panel.add(fileRowsField);
+
+ panel.add(new JLabel("Skip chars"));
+ panel.add(tailField);
+
+ JButton createFileButton = new JButton(new AbstractAction("Write file") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ createFile();
+ }
+ });
+ panel.add(createFileButton);
+
+ JButton readFileButton = new JButton(new AbstractAction("Read file") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ createReadDocumentWorker().execute();
+ }
+ });
+ panel.add(readFileButton);
+
+ panel.add(statusLabel);
+
+ panel.add(progressBar);
+// progressBar.setEnabled(false);
+ progressBar.setIndeterminate(false);
+
+
+ return panel;
+ }
+
+ private JPanel createCharsetPanel() {
+ ButtonGroup charsetGroup = new ButtonGroup();
+ JPanel panel = new JPanel();
+ BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS);
+ panel.setLayout(layout);
+
+ panel.add(new JLabel("Charset"));
+ panel.add(createCharsetRadioButton(charsetGroup, StandardCharsets.US_ASCII));
+ panel.add(createCharsetRadioButton(charsetGroup, StandardCharsets.ISO_8859_1));
+ panel.add(createCharsetRadioButton(charsetGroup, StandardCharsets.UTF_8));
+ panel.add(createCharsetRadioButton(charsetGroup, StandardCharsets.UTF_16BE));
+ panel.add(createCharsetRadioButton(charsetGroup, StandardCharsets.UTF_16LE));
+ panel.add(createCharsetRadioButton(charsetGroup, StandardCharsets.UTF_16));
+ panel.add(createCharsetRadioButton(charsetGroup, null));
+
+ return panel;
+
+ }
+
+
+ private JRadioButton createCharsetRadioButton(ButtonGroup charsetGroup, Charset charset) {
+ JRadioButton radioButton = new JRadioButton(createRadioButtonAction(charset));
+ radioButton.setSelected(charset == selectedCharset);
+ charsetGroup.add(radioButton);
+ return radioButton;
+ }
+
+ private Action createRadioButtonAction(Charset charset) {
+ String name = charset == null ? "From BOM" : charset.displayName();
+ return new AbstractAction(name) {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ if( charset == null ){
+ try( UnicodeReader reader = new UnicodeReader(documentFile) ){
+ selectedCharset = Charset.forName(reader.getEncoding());
+ } catch( IOException e ){
+ LOGGER.log(Level.WARNING, e.getMessage(), e);
+ }
+
+ } else selectedCharset = charset;
+ }
+ };
+ }
+
+ private void createFile() {
+ try{
+ documentFile = File.createTempFile("ReadOnlyDocumentLauncher", ".txt");
+ documentFile.deleteOnExit();
+ FileBuilderTestUtil.writeTestFile(documentFile.toPath(), selectedCharset, "\n", fileContentField.getText(), Integer.parseInt(fileRowsField.getText()), createBom);
+ statusLabel.setText("File saved");
+ LOGGER.log(Level.INFO, "File saved: " + documentFile.getAbsolutePath());
+ } catch( IOException e ){
+ statusLabel.setText("Save file failed");
+ LOGGER.log(Level.WARNING, e.getMessage(), e);
+ }
+ }
+
+ private JComponent createEditorPanel() {
+ return new RTextScrollPane(textArea, true);
+ }
+
+
+ private SwingWorker