udunits/src/main/java/ucar/units/ParseException.java
public String getMessage() {
    if (!specialConstructor) {
      return super.getMessage();
    }
    StringBuilder expected = new StringBuilder();
    int maxSize = 0;
    for (int[] expectedTokenSequence : expectedTokenSequences) {
      if (maxSize < expectedTokenSequence.length) {
        maxSize = expectedTokenSequence.length;
      }
      for (int anExpectedTokenSequence : expectedTokenSequence) {
        expected.append(tokenImage[anExpectedTokenSequence]).append(' ');
      }
      if (expectedTokenSequence[expectedTokenSequence.length - 1] != 0) {
        expected.append("...");
      }
      expected.append(eol).append("    ");
    }

    StringBuilder b = new StringBuilder("Encountered \"");
    Token tok = currentToken.next;
    for (int i = 0; i < maxSize; i++) {
      if (i != 0) b.append(" ");
      if (tok.kind == 0) {
        b.append(tokenImage[0]);
        break;
      }
      b.append(" ").append(tokenImage[tok.kind]).append(" \"");
      b.append(add_escapes(tok.image));
      b.append(" \"");
      tok = tok.next; 
    }
    b.append("\" at line ").append(currentToken.next.beginLine).append(", column ").append(currentToken.next.beginColumn);
    b.append(".").append(eol);
    if (expectedTokenSequences.length == 1) {
      b.append("Was expecting:").append(eol).append("    ");
    } else {
      b.append("Was expecting one of:").append(eol).append("    ");
    }
    expected.append(expected.toString());
    return b.toString();
  }
public String getMessage() {
    if (!specialConstructor) {
      return super.getMessage();
    }
    StringBuilder expected = new StringBuilder();
    int maxSize = 0;
    for (int[] expectedTokenSequence : expectedTokenSequences) {
      if (maxSize < expectedTokenSequence.length) {
        maxSize = expectedTokenSequence.length;
      }
      for (int anExpectedTokenSequence : expectedTokenSequence) {
        expected.append(tokenImage[anExpectedTokenSequence]).append(' ');
      }
      if (expectedTokenSequence[expectedTokenSequence.length - 1] != 0) {
        expected.append("...");
      }
      expected.append(eol).append("    ");
    }

    StringBuilder b = new StringBuilder("Encountered \"");
    Token tok = currentToken.next;
    for (int i = 0; i < maxSize; i++) {
      if (i != 0) b.append(" ");
      if (tok.kind == 0) {
        b.append(tokenImage[0]);
        break;
      }
      b.append(" ").append(tokenImage[tok.kind]).append(" \"");
      b.append(add_escapes(tok.image));
      b.append(" \"");
      tok = tok.next; 
    }
    b.append("\" at line ").append(currentToken.next.beginLine).append(", column ").append(currentToken.next.beginColumn);
    b.append(".").append(eol);
    if (expectedTokenSequences.length == 1) {
      b.append("Was expecting:").append(eol).append("    ");
    } else {
      b.append("Was expecting one of:").append(eol).append("    ");
    }
    expected.append(expected.toString());
    return b.toString();
  }
This method has the standard behavior when this object has been
created using the standard constructors.  Otherwise, it uses
"currentToken" and "expectedTokenSequences" to generate a parse
error message and returns it.  If this object has been created
due to a parse error, and you do not catch it (it gets thrown
from the parser), then this method is called during the printing
of the final stack trace, and hence the correct error message
gets displayed.