From aebe05fed347da328cac72e3f4be64f8ac050cf9 Mon Sep 17 00:00:00 2001
From: Danny van Heumen
Date: Sat, 23 Aug 2014 01:58:09 +0200
Subject: [PATCH] Replaced various processing methods with separate
implementation.
---
.../gui/main/chat/ChatConversationPanel.java | 547 +++++++++---------
1 file changed, 284 insertions(+), 263 deletions(-)
diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java
index 452257a2b..8bb5163e8 100644
--- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java
+++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java
@@ -577,8 +577,7 @@ else if (messageType.equals(Chat.STATUS_MESSAGE))
chatString +=
GuiUtils.formatTime(date)
+ " "
- + processLinksAndHTMLChars(contactName, true,
- ChatHtmlUtils.TEXT_CONTENT_TYPE) + " "
+ + StringEscapeUtils.escapeHtml4(contactName) + " "
+ formatMessageAsHTML(message, contentType, keyword)
+ endHeaderTag;
}
@@ -589,8 +588,7 @@ else if (messageType.equals(Chat.ACTION_MESSAGE))
endHeaderTag = "
";
chatString += "* " + GuiUtils.formatTime(date)
- + " " + processLinksAndHTMLChars(contactName, true,
- ChatHtmlUtils.TEXT_CONTENT_TYPE) + " "
+ + " " + StringEscapeUtils.escapeHtml4(contactName) + " "
+ formatMessageAsHTML(message, contentType, keyword)
+ endHeaderTag;
}
@@ -623,10 +621,8 @@ else if (messageType.equals(Chat.ERROR_MESSAGE))
if (messageTitle != null)
{
chatString +=
- errorIcon
- + processLinksAndHTMLChars(messageTitle, true,
- ChatHtmlUtils.TEXT_CONTENT_TYPE) + endHeaderTag
- + ""
+ errorIcon + StringEscapeUtils.escapeHtml4(messageTitle)
+ + endHeaderTag + ""
+ formatMessageAsHTML(message, contentType, keyword)
+ "
";
}
@@ -1021,55 +1017,13 @@ private void deleteAllMessagesWithoutHeader()
deleteAllMessagesWithoutHeader();
}
- /**
- * Highlights keywords searched in the history.
- *
- * @param message the source message
- * @param contentType the content type
- * @param keyword the searched keyword
- * @return the formatted message
- */
- private String processKeyword(final String message,
- final String contentType, final String keyword)
- {
- if(message == null)
- return message;
-
- Matcher m
- = Pattern.compile(Pattern.quote(keyword), Pattern.CASE_INSENSITIVE)
- .matcher(message);
- StringBuffer msgBuffer = new StringBuffer();
- int prevEnd = 0;
-
- while (m.find())
- {
- msgBuffer.append(StringEscapeUtils.escapeHtml4(message.substring(
- prevEnd, m.start())));
- prevEnd = m.end();
-
- String keywordMatch = m.group().trim();
-
- msgBuffer.append("");
- msgBuffer.append(StringEscapeUtils.escapeHtml4(keywordMatch));
- msgBuffer.append("");
- }
-
- /*
- * If the keyword didn't match, let the outside world be able to
- * discover it.
- */
- if (prevEnd == 0)
- return message;
-
- msgBuffer.append(StringEscapeUtils.escapeHtml4(message
- .substring(prevEnd)));
- return msgBuffer.toString();
- }
-
/**
* Formats the given message. Processes all smiley chars, new lines and
* links.
*
+ * TODO correctly name this method: we only expect content in either HTML or
+ * PLAIN TEXT format. We DON'T WANT THE HEADER STUFF OF A HTML MESSAGE!!!
+ *
* @param message the message to be formatted
* @param contentType the content type of the message to be formatted
* @param keyword the word to be highlighted
@@ -1079,182 +1033,320 @@ private String formatMessageAsHTML(final String original,
final String contentType,
final String keyword)
{
- if(original == null)
+ if (original == null)
+ {
return "";
+ }
- String message = original;
+ // prepare initial message source
+ String source;
+ if (ChatHtmlUtils.HTML_CONTENT_TYPE.equals(contentType))
+ {
+ source = original;
+ }
+ else
+ {
+ source = StringEscapeUtils.escapeHtml4(original);
+ }
- // If the message content type is HTML we won't process links and
- // new lines, but only the smileys.
- if (!ChatHtmlUtils.HTML_CONTENT_TYPE.equals(contentType))
+ final Replacer[] replacers = new Replacer[]
{
+ new NewlineReplacer(),
+ new URLReplacer(),
+ new KeywordReplacer(keyword),
+ new BrTagReplacer(),
+ new ImgTagReplacer()
+ };
- /*
- * We disallow HTML in plain-text messages. But processKeyword
- * introduces HTML. So we'll allow HTML if processKeyword has
- * introduced it in order to not break highlighting.
- */
- boolean processHTMLChars;
+ return processReplacers(source, replacers);
+ }
- if ((keyword != null) && (keyword.length() != 0))
+ // FIXME decent comments
+ private String processReplacers(final String content, final Replacer... replacers)
+ {
+ StringBuilder source = new StringBuilder(content);
+ for (Replacer replacer : replacers)
+ {
+ final StringBuilder target = new StringBuilder();
+ if (replacer.expectsPlainText())
{
- // TODO Doesn't replacing keywords first cause hyperlinks to be
- // broken if the keyword is in the hyperlink? Maybe we should
- // insert anchors first, highlighting keywords.
- String messageWithProcessedKeyword
- = processKeyword(message, contentType, keyword);
-
- /*
- * The same String instance will be returned if there was no
- * keyword match. Calling #equals() is expensive so == is
- * intentional.
- */
- processHTMLChars = (messageWithProcessedKeyword == message);
- message = messageWithProcessedKeyword;
+ int startPos = 0;
+ final Matcher plainTextInHtmlMatcher =
+ TEXT_TO_REPLACE_PATTERN.matcher(source);
+ while (plainTextInHtmlMatcher.find())
+ {
+ final String plainTextAsHtml =
+ plainTextInHtmlMatcher.group(1);
+ final int startMatchPosition =
+ plainTextInHtmlMatcher.start(1);
+ final int endMatchPosition = plainTextInHtmlMatcher.end(1);
+ target.append(source
+ .substring(startPos, startMatchPosition));
+ final String plaintext =
+ StringEscapeUtils.unescapeHtml4(plainTextAsHtml);
+
+ // Invoke replacer.
+ try
+ {
+ replacer.replace(target, plaintext);
+ }
+ catch (RuntimeException e)
+ {
+ logger.error("An error occurred in replacer: "
+ + replacer.getClass().getName(), e);
+ }
+
+ startPos = endMatchPosition;
+ }
+ target.append(source.substring(startPos));
}
else
- processHTMLChars = true;
+ {
+ // Invoke replacer.
+ try
+ {
+ replacer.replace(target, source.toString());
+ }
+ catch (RuntimeException e)
+ {
+ logger.error("An error occurred in replacer: "
+ + replacer.getClass().getName(), e);
+ }
+ }
+ source = target;
+ }
+ return source.toString();
+ }
+
+ // TODO check all processors for correct html escaping!
+ // FIXME decent comments
+ private static abstract class Replacer
+ {
+ /**
+ * If a replacer expects plain text strings, then html content is
+ * automatically unescaped. The replacer is responsible for correctly
+ * escaping normal text.
+ *
+ * @return returns true if it needs plain text or false if it wants html
+ * content
+ */
+ abstract boolean expectsPlainText();
+ // FIXME javadoc
+ abstract void replace(StringBuilder target, String piece);
+ }
- message = processNewLines(processLinksAndHTMLChars(
- message, processHTMLChars, contentType), contentType);
+ private static final class URLReplacer
+ extends Replacer
+ {
+
+ @Override
+ boolean expectsPlainText()
+ {
+ return true;
}
- // If the message content is HTML, we process br and img tags.
- else
+
+ @Override
+ void replace(final StringBuilder target, final String piece)
{
- // For HTML message, also check for hyperlinks.
- int startPos = 0;
- final StringBuilder buff = new StringBuilder();
- final Matcher plainTextInHtmlMatcher =
- TEXT_TO_REPLACE_PATTERN.matcher(message);
- while (plainTextInHtmlMatcher.find())
+ final Matcher m = URL_PATTERN.matcher(piece);
+ int prevEnd = 0;
+
+ while (m.find())
{
- final String plainTextAsHtml = plainTextInHtmlMatcher.group(1);
- final int startMatchPosition = plainTextInHtmlMatcher.start(1);
- final int endMatchPosition = plainTextInHtmlMatcher.end(1);
+ target.append(StringEscapeUtils.escapeHtml4(piece.substring(
+ prevEnd, m.start())));
+ prevEnd = m.end();
- if (!StringUtils.isNullOrEmpty(plainTextAsHtml))
+ String url = m.group().trim();
+ target.append("");
+ target.append(StringEscapeUtils.escapeHtml4(url));
+ target.append("");
}
- buff.append(message.substring(startPos));
- message = buff.toString();
+ target.append(StringEscapeUtils.escapeHtml4(piece
+ .substring(prevEnd)));
+ }
+ }
+
+ private static final class NewlineReplacer
+ extends Replacer
+ {
- if ((keyword != null) && (keyword.length() != 0))
- message = processKeyword(message, contentType, keyword);
- message = processImgTags(processBrTags(message));
+ @Override
+ boolean expectsPlainText()
+ {
+ return false;
}
- return message;
+ @Override
+ void replace(final StringBuilder target, final String piece)
+ {
+ /*
+ *
tags are needed to visualize a new line in the html format,
+ * but when copied to the clipboard they are exported to the plain
+ * text format as ' ' and not as '\n'.
+ *
+ * See bug N4988885:
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988885
+ *
+ * To fix this we need "
" - the HTML-Code for ASCII-Character
+ * No.10 (Line feed).
+ */
+ target.append(piece.replaceAll("\n", "
"));
+ }
}
- /**
- * Formats all links in a given message and optionally escapes special HTML
- * characters such as <, >, & and " in order to prevent HTML
- * injection in plain-text messages such as writing
- * </PLAINTEXT>, HTML which is going to be rendered as
- * such and <PLAINTEXT>. The two procedures are carried
- * out in one call in order to not break URLs which contain special HTML
- * characters such as &.
- *
- * @param message The source message string.
- * @param processHTMLChars true to escape the special HTML chars;
- * otherwise, false
- * @param contentType the message content type (html or plain text)
- * @return The message string with properly formatted links.
- */
- private String processLinksAndHTMLChars(final String message,
- final boolean processHTMLChars,
- final String contentType)
+ private static final class KeywordReplacer
+ extends Replacer
{
- Matcher m = URL_PATTERN.matcher(message);
- StringBuffer msgBuffer = new StringBuffer();
- int prevEnd = 0;
+ private final String keyword;
- while (m.find())
+ private KeywordReplacer(final String keyword)
+ {
+ this.keyword = keyword;
+ }
+
+ @Override
+ boolean expectsPlainText()
{
- final String rawMessage = message.substring(prevEnd, m.start());
- final String fromPrevEndToStart;
- if (processHTMLChars)
+ return true;
+ }
+
+ @Override
+ void replace(final StringBuilder target, final String piece)
+ {
+ if (this.keyword == null || this.keyword.isEmpty())
{
- fromPrevEndToStart = StringEscapeUtils.escapeHtml4(rawMessage);
+ target.append(StringEscapeUtils.escapeHtml4(piece));
+ return;
}
- else
+
+ final Matcher m =
+ Pattern.compile(Pattern.quote(keyword),
+ Pattern.CASE_INSENSITIVE).matcher(piece);
+ int prevEnd = 0;
+ while (m.find())
{
- fromPrevEndToStart = rawMessage;
+ target.append(StringEscapeUtils.escapeHtml4(piece.substring(
+ prevEnd, m.start())));
+ prevEnd = m.end();
+ final String keywordMatch = m.group().trim();
+ target.append("");
+ target.append(StringEscapeUtils.escapeHtml4(keywordMatch));
+ target.append("");
}
- msgBuffer.append(fromPrevEndToStart);
- prevEnd = m.end();
+ target.append(StringEscapeUtils.escapeHtml4(piece
+ .substring(prevEnd)));
+ }
+ }
- String url = m.group().trim();
+ private static final class BrTagReplacer extends Replacer {
- msgBuffer.append("");
- msgBuffer.append(StringEscapeUtils.escapeHtml4(url));
- msgBuffer.append("");
+ @Override
+ boolean expectsPlainText()
+ {
+ return false;
}
- final String rawMessage = message.substring(prevEnd);
- final String fromPrevEndToEnd;
- if (processHTMLChars)
+ @Override
+ void replace(final StringBuilder target, final String piece)
{
- fromPrevEndToEnd = StringEscapeUtils.escapeHtml4(rawMessage);
+ // Compile the regex to match something like
or
.
+ // This regex is case sensitive and keeps the style or other
+ // attributes of the
tag.
+ Matcher m =
+ Pattern.compile("<\\s*[bB][rR](.*?)(/\\s*>)").matcher(piece);
+ int start = 0;
+
+ // while we find some
closing tags with a slash inside.
+ while (m.find())
+ {
+ // First, we have to copy all the message preceding the
+ // tag.
+ target.append(piece.substring(start, m.start()));
+ // Then, we find the position of the slash inside the tag.
+ final int slashIndex = m.group().lastIndexOf("/");
+ // We copy the
tag till the slash exclude.
+ target.append(m.group().substring(0, slashIndex));
+ // We copy all the end of the tag following the slash exclude.
+ target.append(m.group().substring(slashIndex + 1));
+ start = m.end();
+ }
+ // Finally, we have to add the end of the message following the last
+ //
tag, or the whole message if there is no
tag.
+ target.append(piece.substring(start));
}
- else
+ }
+
+ private static final class ImgTagReplacer
+ extends Replacer
+ {
+
+ @Override
+ boolean expectsPlainText()
{
- fromPrevEndToEnd = rawMessage;
+ return false;
}
- msgBuffer.append(fromPrevEndToEnd);
+ @Override
+ void replace(final StringBuilder target, final String piece)
+ {
+ // Compile the regex to match something like
or
+ //
. This regex is case sensitive and keeps the style,
+ // src or other attributes of the
tag.
+ final Pattern p = Pattern.compile("<\\s*[iI][mM][gG](.*?)(/\\s*>)");
+ final Matcher m = p.matcher(piece);
+ int slashIndex;
+ int start = 0;
- return msgBuffer.toString();
+ // while we find some
self-closing tags with a slash inside.
+ while (m.find())
+ {
+ // First, we have to copy all the message preceding the
+ // tag.
+ target.append(piece.substring(start, m.start()));
+ // Then, we find the position of the slash inside the tag.
+ slashIndex = m.group().lastIndexOf("/");
+ // We copy the
tag till the slash exclude.
+ target.append(m.group().substring(0, slashIndex));
+ // We copy all the end of the tag following the slash exclude.
+ target.append(m.group().substring(slashIndex + 1));
+ // We close the tag with a separate closing tag.
+ target.append("");
+ start = m.end();
+ }
+ // Finally, we have to add the end of the message following the last
+ //
tag, or the whole message if there is no
tag.
+ target.append(piece.substring(start));
+ }
}
+ // FIXME delete this after everything works
/**
- * Formats message new lines.
+ * Formats all links in a given message and optionally escapes special HTML
+ * characters such as <, >, & and " in order to prevent HTML
+ * injection in plain-text messages such as writing
+ * </PLAINTEXT>, HTML which is going to be rendered as
+ * such and <PLAINTEXT>. The two procedures are carried
+ * out in one call in order to not break URLs which contain special HTML
+ * characters such as &.
*
* @param message The source message string.
- * @param contentType message contentType (html or plain text)
- * @return The message string with properly formatted new lines.
+ * @param processHTMLChars true to escape the special HTML chars;
+ * otherwise, false
+ * @param contentType the message content type (html or plain text)
+ * @return The message string with properly formatted links.
*/
- private String processNewLines(String message, String contentType)
- {
-
- /*
- *
tags are needed to visualize a new line in the html format, but
- * when copied to the clipboard they are exported to the plain text
- * format as ' ' and not as '\n'.
- *
- * See bug N4988885:
- * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988885
- *
- * To fix this we need "
" - the HTML-Code for ASCII-Character No.10
- * (Line feed).
- */
- Matcher divMatcher = DIV_PATTERN.matcher(message);
- String openingTag = "";
- String closingTag = "";
- if (divMatcher.find())
- {
- openingTag = divMatcher.group(1);
- message = divMatcher.group(2);
- closingTag = divMatcher.group(3);
- }
- return openingTag + message.replaceAll("\n", "
") + closingTag;
- }
+// processLinksAndHTMLChars(final String message,
+// final boolean processHTMLChars,
+// final String contentType)
+// {
+// }
/**
* Opens a link in the default browser when clicked and shows link url in a
@@ -1552,6 +1644,7 @@ public Date getPageLastMsgTimestamp()
return timestamp;
}
+ // FIXME delete this after everything works
/**
* Formats HTML tags <br/> to <br> or <BR/> to <BR>.
* The reason of this function is that the ChatPanel does not support
@@ -1560,38 +1653,11 @@ public Date getPageLastMsgTimestamp()
* @param message The source message string.
* @return The message string with properly formatted <br> tags.
*/
- private String processBrTags(String message)
- {
- // The resulting message after being processed by this function.
- StringBuffer processedMessage = new StringBuffer();
-
- // Compile the regex to match something like
or
.
- // This regex is case sensitive and keeps the style or other
- // attributes of the
tag.
- Matcher m
- = Pattern.compile("<\\s*[bB][rR](.*?)(/\\s*>)").matcher(message);
- int start = 0;
-
- // while we find some
closing tags with a slash inside.
- while(m.find())
- {
- // First, we have to copy all the message preceding the
tag.
- processedMessage.append(message.substring(start, m.start()));
- // Then, we find the position of the slash inside the tag.
- int slash_index = m.group().lastIndexOf("/");
- // We copy the
tag till the slash exclude.
- processedMessage.append(m.group().substring(0, slash_index));
- // We copy all the end of the tag following the slash exclude.
- processedMessage.append(m.group().substring(slash_index+1));
- start = m.end();
- }
- // Finally, we have to add the end of the message following the last
- //
tag, or the whole message if there is no
tag.
- processedMessage.append(message.substring(start));
-
- return processedMessage.toString();
- }
+// processBrTags(String message)
+// {
+// }
+ // FIXME delete this after everything works
/**
* Formats HTML tags <img ... /> to < img ... ></img> or
* <IMG ... /> to <IMG></IMG>.
@@ -1602,40 +1668,9 @@ private String processBrTags(String message)
* @param message The source message string.
* @return The message string with properly formatted <img> tags.
*/
- private String processImgTags(String message)
- {
- // The resulting message after being processed by this function.
- StringBuffer processedMessage = new StringBuffer();
-
- // Compile the regex to match something like
or
- //
. This regex is case sensitive and keeps the style,
- // src or other attributes of the
tag.
- Pattern p = Pattern.compile("<\\s*[iI][mM][gG](.*?)(/\\s*>)");
- Matcher m = p.matcher(message);
- int slash_index;
- int start = 0;
-
- // while we find some
self-closing tags with a slash inside.
- while(m.find())
- {
- // First, we have to copy all the message preceding the
tag.
- processedMessage.append(message.substring(start, m.start()));
- // Then, we find the position of the slash inside the tag.
- slash_index = m.group().lastIndexOf("/");
- // We copy the
tag till the slash exclude.
- processedMessage.append(m.group().substring(0, slash_index));
- // We copy all the end of the tag following the slash exclude.
- processedMessage.append(m.group().substring(slash_index+1));
- // We close the tag with a separate closing tag.
- processedMessage.append("");
- start = m.end();
- }
- // Finally, we have to add the end of the message following the last
- //
tag, or the whole message if there is no
tag.
- processedMessage.append(message.substring(start));
-
- return processedMessage.toString();
- }
+// processImgTags(String message)
+// {
+// }
/**
* Extend Editor pane to add URL tooltips.
@@ -1748,20 +1783,6 @@ public void loadSkin()
getRightButtonMenu().loadSkin();
}
- /**
- * Highlights the string in multi user chat.
- *
- * @param message the message to process
- * @param contentType the content type of the message
- * @param keyWord the keyword to highlight
- * @return the message string with the keyword highlighted
- */
- public String processChatRoomHighlight(String message, String contentType,
- String keyWord)
- {
- return processKeyword(message, contentType, keyWord);
- }
-
/**
* Processes /me command in group chats.
*