Author: christian.bauer(a)jboss.com
Date: 2008-06-02 07:54:08 -0400 (Mon, 02 Jun 2008)
New Revision: 8318
Modified:
trunk/ui/src/main/java/org/jboss/seam/ui/validator/FormattedTextValidator.java
Log:
JBSEAM-3061, better customizable FormattedTextValidator
Modified: trunk/ui/src/main/java/org/jboss/seam/ui/validator/FormattedTextValidator.java
===================================================================
---
trunk/ui/src/main/java/org/jboss/seam/ui/validator/FormattedTextValidator.java 2008-06-02
11:47:36 UTC (rev 8317)
+++
trunk/ui/src/main/java/org/jboss/seam/ui/validator/FormattedTextValidator.java 2008-06-02
11:54:08 UTC (rev 8318)
@@ -12,8 +12,7 @@
import org.jboss.seam.text.SeamTextLexer;
import org.jboss.seam.text.SeamTextParser;
-import antlr.RecognitionException;
-import antlr.TokenStreamException;
+import antlr.*;
/**
* Formatted Text validator
@@ -27,19 +26,24 @@
* and call the static convenience method
* <tt>FormattedTextValidator.getErrorMessage(originalText,
recognitionException)</tt>
* if you want to display or log a nice error message.
- *
+ * </p>
+ * <p>
+ * Uses an instance of <tt>SeamTextParser</tt> by default, override if you
require
+ * validation with your customized instance of <tt>SeamTextParser</tt>.
+ * </p>
+ *
* @author matthew.drees
* @author Christian Bauer
*/
-public class FormattedTextValidator implements javax.faces.validator.Validator,
- Serializable {
+public class FormattedTextValidator implements javax.faces.validator.Validator,
Serializable {
+
private static final long serialVersionUID = 1L;
-
private static final int NUMBER_OF_CONTEXT_CHARS_AFTER = 10;
private static final int NUMBER_OF_CONTEXT_CHARS_BEFORE = 10;
+ private static final String END_OF_TEXT = "END OF TEXT";
+ String firstError;
+ String firstErrorDetail;
- String firstError;
-
/**
* Validate the given value as well-formed Seam Text. If there are parse
* errors, throw a ValidatorException including the first parse error.
@@ -47,6 +51,7 @@
public void validate(FacesContext context, UIComponent component,
Object value) throws ValidatorException {
firstError = null;
+ firstErrorDetail = null;
if (value == null) {
return;
}
@@ -56,9 +61,7 @@
+ value);
}
String text = (String) value;
- Reader r = new StringReader(text);
- SeamTextLexer lexer = new SeamTextLexer(r);
- SeamTextParser parser = new SeamTextParser(lexer);
+ SeamTextParser parser = getSeamTextParser(text);
try {
parser.startRule();
}
@@ -68,40 +71,146 @@
// Problem with the token input stream
throw new RuntimeException(tse);
} catch (RecognitionException re) {
- // A parser error, just log and swallow
+ // A parser error
if (firstError == null) {
- firstError = getErrorMessage(text, re);
+ firstError = getParserErrorMessage(text, re);
+ firstErrorDetail =
re.getMessage().replace("\uFFFF",END_OF_TEXT);
}
}
if (firstError != null) {
- throw new ValidatorException(new FacesMessage("Invalid markup: "
- + firstError));
+ throw new ValidatorException(new FacesMessage(firstError,
firstErrorDetail));
}
}
/**
+ * Override to instantiate a custom <tt>SeamTextLexer</tt> and
<tt>SeamTextParser</tt>.
+ *
+ * @param text the raw markup text
+ * @return an instance of <tt>SeamTextParser</tt>
+ */
+ public SeamTextParser getSeamTextParser(String text) {
+ Reader r = new StringReader(text);
+ SeamTextLexer lexer = new SeamTextLexer(r);
+ return new SeamTextParser(lexer);
+ }
+
+ public String getParserErrorMessage(String originalText, RecognitionException re) {
+ String parserErrorMsg;
+ if (NoViableAltException.class.isAssignableFrom(re.getClass())) {
+ parserErrorMsg = getNoViableAltErrorMessage(
+ re.getMessage(),
+ getErrorLocation(originalText, re, getNumberOfCharsBeforeErrorLocation(),
getNumberOfCharsAfterErrorLocation())
+ );
+ } else if (MismatchedTokenException.class.isAssignableFrom(re.getClass())) {
+ parserErrorMsg = getMismatchedTokenErrorMessage(
+ re.getMessage(),
+ getErrorLocation(originalText, re, getNumberOfCharsBeforeErrorLocation(),
getNumberOfCharsAfterErrorLocation())
+ );
+ } else if (SemanticException.class.isAssignableFrom(re.getClass())) {
+ parserErrorMsg = getSemanticErrorMessage(re.getMessage());
+ } else {
+ parserErrorMsg = re.getMessage();
+ }
+ return parserErrorMsg;
+ }
+
+ public int getNumberOfCharsBeforeErrorLocation() {
+ return NUMBER_OF_CONTEXT_CHARS_BEFORE;
+ }
+
+ public int getNumberOfCharsAfterErrorLocation() {
+ return NUMBER_OF_CONTEXT_CHARS_AFTER;
+ }
+
+ /**
+ * Override (e.g. for i18n) ANTLR parser error messages.
+ *
+ * @param originalMessage the ANTLR parser error message of the RecognitionException
+ * @param location a snippet that indicates the location in the original markup,
might be null
+ * @return a message that is thrown by this validator
+ */
+ public String getNoViableAltErrorMessage(String originalMessage, String location) {
+ return location != null
+ ? "Text parsing error at '..." + location.trim() +
"...'."
+ : "Text parsing error, " +
originalMessage.replace("\uFFFF",END_OF_TEXT);
+ }
+
+ /**
+ * Override (e.g. for i18n) ANTLR parser error messages.
+ *
+ * @param originalMessage the ANTLR parser error message of the RecognitionException
+ * @param location a snippet that indicates the location in the original markup,
might be null
+ * @return a message that is thrown by this validator
+ */
+ public String getMismatchedTokenErrorMessage(String originalMessage, String location)
{
+ return location != null
+ ? "Text parsing error at '..." + location.trim() +
"...'."
+ : "Text parsing error, " +
originalMessage.replace("\uFFFF",END_OF_TEXT);
+ }
+
+ /**
+ * Override (e.g. for i18n) ANTLR parser error messages.
+ *
+ * @param originalMessage the ANTLR parser error message of the RecognitionException
+ * @return a message that is thrown by this validator
+ */
+ public String getSemanticErrorMessage(String originalMessage) {
+ return "Text parsing error, " +
originalMessage.replace("\uFFFF",END_OF_TEXT) + ".";
+ }
+
+ /**
* Extracts the error from the <tt>RecognitionException</tt> and
generates
- * a message with some helpful context.
+ * a location of the error by extracting the original text at the exceptions
+ * line and column.
*
* @param originalText
* the original Seam Text markup as fed into the parser
* @param re
* an ANTLR <tt>RecognitionException</tt> thrown by the
parser
+ * @param charsBefore
+ * characters before error location included in message
+ * @param charsAfter
+ * characters after error location included in message
* @return an error message with some helpful context about where the error
* occured
*/
- public static String getErrorMessage(String originalText,
- RecognitionException re) {
+ public static String getErrorLocation(String originalText, RecognitionException re,
int charsBefore, int charsAfter) {
+ int beginIndex = Math.max(re.getColumn() - 1 - charsBefore, 0);
+ int endIndex = Math.min(re.getColumn() + charsAfter, originalText.length());
+
+ String location = null;
+
// Avoid IOOBE even if what we show is wrong, we need to figure out why the
indexes are off sometimes
- int beginIndex = Math.max(re.getColumn() - 1 - NUMBER_OF_CONTEXT_CHARS_BEFORE,
0);
- int endIndex = Math.min(re.getColumn() + NUMBER_OF_CONTEXT_CHARS_AFTER,
originalText.length());
- String snippet = originalText.length() > 50 ? originalText.substring(0, 50) :
originalText;
if (beginIndex > 0 && beginIndex < endIndex && endIndex
> 0 && endIndex < originalText.length())
- snippet = "..." + originalText.substring(beginIndex, endIndex) +
"...";
+ location = originalText.substring(beginIndex, endIndex);
- String msg = re.getMessage() + " at '" + snippet +
"'";
- return msg.replace("\n", " ").replace("\r", "
").replace("\uFFFF","END OF TEXT").replace("#{",
"# {");
+ if (location == null) return location;
+
+ // Filter some dangerous characters we do not want in error messages
+ return location.replace("\n", " ").replace("\r",
" ").replace("#{", "# {");
}
+
+ /**
+ * Extracts the error from the <tt>RecognitionException</tt> and
generates
+ * a message including the location of the error.
+ *
+ * @param originalText
+ * the original Seam Text markup as fed into the parser
+ * @param re
+ * an ANTLR <tt>RecognitionException</tt> thrown by the
parser
+ * @return an error message with some helpful context about where the error
+ * occured
+ */
+ public static String getErrorMessage(String originalText, RecognitionException re) {
+ return re.getMessage().replace("\uFFFF",END_OF_TEXT)
+ + " at '"
+ + getErrorLocation(
+ originalText, re,
+ NUMBER_OF_CONTEXT_CHARS_BEFORE, NUMBER_OF_CONTEXT_CHARS_AFTER
+ )
+ + "'";
+
+ }
}