0

I am building a small Java utility (using Jackson) to catch errors in Java files, and one part of it is a text area, in which you might paste some JSON context and it will tell you the line and column where it's found it:

enter image description here

I am using the error message to take out the line and column as a string and print it out in the interface for someone using it.

This is the JSON sample I'm working with, and there is an intentional error beside "age", where it's missing a colon:

    {
        "name": "mkyong.com",
        "messages": ["msg 1", "msg 2", "msg 3"],
        "age" 100
    }

What I want to do is also highlight the problematic area in a cyan color, and for that purpose, I have this code for the button that validates what's inserted in the text area:

            cmdValidate.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            
            functionsClass ops = new functionsClass();
            
            String JSONcontent = JSONtextArea.getText();
            
            Results obj = new Results();
            
            ops.validate_JSON_text(JSONcontent, obj);
            
            String result = obj.getResult();
            String caret = obj.getCaret();
            //String lineNum = obj.getLineNum();
            
            //showStatus(result);
            
            if(result==null) {
                textAreaError.setText("JSON code is valid!");
            } else {
                textAreaError.setText(result);
                
                Highlighter.HighlightPainter cyanPainter;
                cyanPainter = new DefaultHighlighter.DefaultHighlightPainter(Color.cyan);
                
                int caretPosition = Integer.parseInt(caret); 
                int lineNumber = 0;
                try {
                    lineNumber = JSONtextArea.getLineOfOffset(caretPosition);
                } catch (BadLocationException e2) {
                    // TODO Auto-generated catch block
                    e2.printStackTrace();
                }
                
                try {
                    JSONtextArea.getHighlighter().addHighlight(lineNumber, caretPosition + 1, cyanPainter);
                } catch (BadLocationException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            }
        }

        
    });
    
}

The "addHighlight" method works with a start range, end range and a color, which didn't become apparent to me immediately, thinking I had to get the reference line based on the column number. Some split functions to extract the numbers, I assigned 11 (in screenshot) to a caret value, not realizing that it only counts character positions from the beginning of the string and represents the end point of the range.

For reference, this is the class that does the work behind the scenes, and the error handling at the bottom is about extracting the line and column numbers. For the record, "x" is the error message that would generate out of an invalid file.

package parsingJSON;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class functionsClass extends JSONTextCompare {
    
    public boolean validate_JSON_text(String JSONcontent, Results obj) {
        
        boolean valid = false;
        
        try {
        
        ObjectMapper objMapper = new ObjectMapper();
        JsonNode validation = objMapper.readTree(JSONcontent);
        
        valid = true;
        
        }
        
        catch (JsonParseException jpe){
            
            String x = jpe.getMessage();
            printTextArea(x, obj);
            //return part_3;
            
        }
        catch (IOException ioe) {
            String x = ioe.getMessage();
            printTextArea(x, obj);
            //return part_3;
        }
        
        return valid;
        
        
    }
    public  void printTextArea(String x, Results obj) {
        // TODO Auto-generated method stub
        System.out.println(x);
        String err = x.substring(x.lastIndexOf("\n"));
        
        String parts[] = err.split(";");
        //String part 1 is the discarded leading edge that is the closing brackets of the JSON content
        String part_2 = parts[1];
        
        //split again to get rid of the closing square bracket
        String parts2[] = part_2.split("]");
        String part_3 = parts2[0];
        
        //JSONTextCompare feedback = new JSONTextCompare();
        
        //split the output to get the exact location of the error to communicate back and highlight it in the JSONTextCompare class
        
        //first need to get the line number from the output
        String[] parts_lineNum = part_3.split("line: ");
        String[] parts_lineNum_final = parts_lineNum[1].split(", column:");
        String lineNum = parts_lineNum_final[0];
        String[] parts_caret = part_3.split("column: ");
        String caret = parts_caret[1];
        
        System.out.println(caret);
        
        obj.setLineNum(lineNum);
        obj.setCaret(caret);
        obj.setResult(part_3);
        System.out.println(part_3);
        
    }

}

Screenshot for what the interface currently looks like: enter image description here

Long story short - how do I turn the coordinates Line 4, Col 11 into a caret value (e.g. it's value 189, for the sake of argument) that I can use to get the highlighter to work properly. Some kind of custom parsing formula might be possible, but in general, is that even possible to do?

epicUsername
  • 611
  • 2
  • 8
  • 21

2 Answers2

1

how do I turn the coordinates Line 4, Col 11 into a caret value (e.g. it's value 189,

Check out: Text Utilities for methods that might be helpful when working with text components. It has methods like:

  1. centerLineInScrollPane
  2. getColumnAtCaret
  3. getLineAtCaret
  4. getLines
  5. gotoStartOfLine
  6. gotoFirstWordOnLine
  7. getWrappedLines

In particular the gotoStartOfLine() method contains code you can modify to get the offset of the specified row/column.offset.

The basic code would be:

int line = 4;
int column = 11;
Element root = textArea.getDocument().getDefaultRootElement();
int offset = root.getElement( line - 1 ).getStartOffset() + column;
System.out.println(offset);
camickr
  • 321,443
  • 19
  • 166
  • 288
  • thank you for this reference - it was a very useful perspective that helped me understand how to manipulate text in a textComponent, as you've put it. I found a way based on this to cycle through the rows in the string and retrieve the caret position by adding the length of each row to the one before. I will post a solution here. – epicUsername Oct 14 '21 at 02:58
0

The way it works is essentially counting the number of characters in each line, up until the line in which the error is occurring, and adding the caretPosition to that sum of characters, which is what the Highlighter needs to apply the marking to the correct location.

I've added the code for the Validate button for context.

            functionsClass ops = new functionsClass();
            
            String JSONcontent = JSONtextArea.getText();
            
            Results obj = new Results();
            
            ops.validate_JSON_text(JSONcontent, obj);
            
            String result = obj.getResult();
            String caret = obj.getCaret();
            String lineNum = obj.getLineNum();
            
            //showStatus(result);
            
            if(result==null) {
                textAreaError.setText("JSON code is valid!");
            } else {
                textAreaError.setText(result);
                
                Highlighter.HighlightPainter cyanPainter;
                cyanPainter = new DefaultHighlighter.DefaultHighlightPainter(Color.cyan);
                
                //the column number as per the location of the error
                int caretPosition = Integer.parseInt(caret); //JSONtextArea.getCaretPosition();
                //the line number as per the location of the error
                int lineNumber = Integer.parseInt(lineNum);
                
                //get the number of characters in the string up to the line in which the error is found
                int totalChars = 0;
                int counter = 0; //used to only go to the line above where the error is located
                
                String[] lines = JSONcontent.split("\\r?\\n");
                
                for (String line : lines) {
                    
                    counter = counter + 1;
                    
                    //as long as we're above the line of the error (lineNumber variable), keep counting characters
                    if (counter < lineNumber) 
                    {
                        totalChars = totalChars + line.length();
                    } 
                    
                    //if we are at the line that contains the error, only add the caretPosition value to get the final position where the highlighting should go
                    if (counter == lineNumber)
                    {
                        totalChars = totalChars + caretPosition;
                        break;
                    }
                }
                
                //put down the highlighting in the area where the JSON file is having a problem
                try {
                    JSONtextArea.getHighlighter().addHighlight(totalChars - 2, totalChars + 2, cyanPainter);
                } catch (BadLocationException e1) {
                    // TODO Auto-generated catch block
                    e1.getMessage();
                }
            }

The contents of the JSON file is treated as a string, and that's why I'm also iterating through it in that fashion. There are certainly better ways to go through lines in the string, and I'll add some reference topics on SO:

What is the easiest/best/most correct way to iterate through the characters of a string in Java? - Link

Check if a string contains \n - Link

Split Java String by New Line - Link

What is the best way to iterate over the lines of a Java String? - Link

Generally a combination of these led to this solution, and I am also not targeting it for use on very large JSON files.

A screenshot of the output, with the interface highlighting the same area that Notepad++ would complain about, if it could debug code:

enter image description here

I'll post the project on GitHub after I clean it up and comment it some, and will give a link to that later, but for now, hopefully this helps the next dev in a similar situation.

epicUsername
  • 611
  • 2
  • 8
  • 21
  • Your solution does work correctly in getting the line number and the caret position. The problem is in the Highlighter, which as far as I am seeing needs a starting and ending position, but it is not smart enough to understand the "4" as a line, it treats it as the 4th character from the start of the file, whereas I need to be at the 4th line. I need to reparse the string and add up the characters, precisely because that's the only way for the Highlighter to be accurate. – epicUsername Oct 14 '21 at 20:27
  • Oops I pointed you to the wrong method. The `Utilities` class does not provide what you need. Instead you need to modify code from the `Text Utilities`. See updated answer. – camickr Oct 14 '21 at 21:20
  • Yup, you're right. I got it to work without parsing the string. Thank you! – epicUsername Oct 14 '21 at 21:58