JDK-8180276 : JTextPane inserts extra CR before CR-LF in returned text
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 8,9
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: windows
  • CPU: generic
  • Submitted: 2017-05-12
  • Updated: 2022-04-24
  • Resolved: 2022-04-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdResolved
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 10.0.10586]

EXTRA RELEVANT SYSTEM CONFIGURATION :
JDK 1.8.111

A DESCRIPTION OF THE PROBLEM :
javax.swing.JTextPane has a method getText(), to return the text of its document. It uses StringWriter.

If we extend this class and define an utility method to append some text by:

        public void append(String s) {
            try {
               Document doc = this.getDocument();
               doc.insertString(doc.getLength(), s, null);
            } catch(BadLocationException e) {
                System.err.println(e);
            }
        }

And when adding text, we may use:

        pane.setText(pane.getText() + newText);

or:

        pane.append(newText());

alone and no problem. But if we use append() first and then setText(pane.getText + newText), before every \r\n another \r is added, and in output, extra blank line are added. 

But if we override the getText() method with:

        @Override
        public String getText() {
            String string = "";
            try {
                string = this.getDocument().getText(0, this.getDocument().getLength());
            } catch (BadLocationException e) {
                e.printStackTrace();
            }
            return string;
        }

The problem disappears.

I guess it's because underneath getText() uses StringWriter.write() method, and once I have touched the attached document of a JTextPane, StringWriter behaves differently. But anyway, any side effect should be evaluated.



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run my test case below to see the unexpected result, and uncomment the getText() methods to see the expected result.



EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Three panels should have same content.
ACTUAL -
The first panel has extra line breaks.

If the overwritten getText() method is uncommented, the problem dismisses.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package com.WindThunderStudio.JTextpaneLineSpacing;

import java.awt.BorderLayout;
import java.io.File;

import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.border.EtchedBorder;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

import net.miginfocom.swing.MigLayout;

public class Test1_ChangeStyleAndAppend extends JFrame {
    public class MyJTextPane extends JTextPane {
        /**
         * Append some text to this pane.
         * @param s
         */
        public void append(String s) {
            try {
               Document doc = this.getDocument();
               doc.insertString(doc.getLength(), s, null);
            } catch(BadLocationException e) {
                System.err.println(e);
            }
        }
        
        /**
         * Append some text and change line.
         * @param s
         */
        public void appendLine(String s) {
            try {
                Document doc = this.getDocument();
                doc.insertString(doc.getLength(), s + System.lineSeparator(), null);
            } catch(BadLocationException e) {
                System.err.println(e);
            }
        }
        
//        @Override
//        public String getText() {
//            String string = "";
//            try {
//                string = this.getDocument().getText(0, this.getDocument().getLength());
//            } catch (BadLocationException e) {
//                e.printStackTrace();
//            }
//            return string;
//        }
    }
    public Test1_ChangeStyleAndAppend() {
        begin();
    }

    private void begin() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        MyJTextPane pane0 = new MyJTextPane();
        pane0.appendLine("MyJTextPane using append() and then calling setText()");
        pane0.appendLine("Second line. ");
        pane0.appendLine("Third line");
        pane0.setText(pane0.getText() + "At last" + System.lineSeparator());
        pane0.setBorder(new EtchedBorder(EtchedBorder.RAISED));
        add(pane0, BorderLayout.NORTH);
        
        MyJTextPane pane = new MyJTextPane();
//        changeLineSpacing(pane, 1.5f, false);
        pane.appendLine("MyJTextPane calling appendLine()");
        pane.appendLine("Second line. ");
        pane.appendLine("Third line");
        pane.appendLine("At last");
        pane.setBorder(new EtchedBorder(EtchedBorder.RAISED));
        add(pane, BorderLayout.CENTER);
        
        
        JTextPane pane2 = new JTextPane();
        pane2.setText("Normal JTextPane calling setText()");
        pane2.setText(pane2.getText() + System.lineSeparator() + "Second line. ");
        pane2.setText(pane2.getText() + System.lineSeparator() + "Third line");
        pane2.setText(pane2.getText() + System.lineSeparator() + "At last");
        pane2.setBorder(new EtchedBorder(EtchedBorder.RAISED));
        add(pane2, BorderLayout.SOUTH);
        
        pack();
        setVisible(true);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                Test1_ChangeStyleAndAppend frame = new Test1_ChangeStyleAndAppend();

            }

        });
    }
}


---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
1. Always stick to append() as I suggest, or setText(pane.getText() + newText()). Don't mix them.

2. Override the getText() method in JTextPane by extending it, like I do in test case.


Comments
Using 'System.lineSeparator()' as a Line breaker in a document model introduces CR before CRLF in windows, since 'DefaultEditorKit.EndOfLineStringProperty' is expected to return CRLF. Handling this would likely introduce a regression, which is why the Bug is preferred to close as "Not an Issue". Ref - https://github.com/openjdk/jdk/pull/8122.
18-04-2022

A pull request was submitted for review. URL: https://git.openjdk.java.net/jdk/pull/8122 Date: 2022-04-06 10:02:10 +0000
06-04-2022

Issue reproducible in JDK 8 and 9 in Windows and not in Linux (Ubuntu 16.04.2 LTS) Windows 10 Pro(version 1511) ---------------------------------------- 8 GA : Fail 8u121 : Fail 8u131 : Fail 8u152 : Fail 9-ea+168 : Fail ---------------------------------------- Customer submitted both workarounds works.
12-05-2017