diff --git a/src/buglinky/BlipProcessor.java b/src/buglinky/BlipProcessor.java index e7d066d..5e388ba 100644 --- a/src/buglinky/BlipProcessor.java +++ b/src/buglinky/BlipProcessor.java @@ -30,9 +30,16 @@ abstract class BlipProcessor { private static final Logger LOG = Logger.getLogger(BlipProcessor.class.getName()); + + /** + * The accumulated difference between the original length of the + * blip, and the length after text replacements have been performed. + */ + private int totalCorrection; /** - * Annotate the specified blip. + * Apply this text processor to the specified blip. This function + * is not re-entrant. * * @param blip The blip to process. */ @@ -42,11 +49,13 @@ public void processBlip(Blip blip) { // Adapted from http://senikk.com/min-f%C3%B8rste-google-wave-robot, // a robot which links to @names on Twitter. TextView doc = blip.getDocument(); + totalCorrection = 0; // Reset. Matcher matcher = getCompiledPattern().matcher(doc.getText()); while (matcher.find()) { LOG.fine("Found match to process: " + matcher.group()); - Range range = new Range(matcher.start(), matcher.end()); - processMatch(doc, range, matcher); + int start = matcher.start() + totalCorrection; + int end = matcher.end() + totalCorrection; + processMatch(doc, new Range(start, end), matcher); } } @@ -98,6 +107,7 @@ private Pattern getCompiledPattern() { * because of previous text replacements. * * @see BlipProcessor#annotate(TextView, Range, String, String) + * @see BlipProcessor#replace(TextView, Range, String) */ protected abstract void processMatch(TextView doc, Range range, Matcher match); @@ -129,5 +139,22 @@ protected void annotate(TextView doc, Range range, String name, LOG.fine("Annotating with " + name + "=" + value); doc.setAnnotation(range, name, value); + } + + /** + * Replace the specified range in the TextView with a new string, leaving + * existing annotations alone. + * + * @param doc The TextView to containing the text to annotate. + * @param range The range of text to replace. This must fall entirely + * within the current match. + * @param text The replacement text. + */ + protected void replace(TextView doc, Range range, String text) { + doc.replace(range, text); + + // Update our correction factor to account for this replacement. + int oldLength = range.getEnd() - range.getStart(); + totalCorrection = (totalCorrection - oldLength) + text.length(); } } \ No newline at end of file diff --git a/src/buglinky/BugLinkyServlet.java b/src/buglinky/BugLinkyServlet.java index 2d34107..079d76c 100644 --- a/src/buglinky/BugLinkyServlet.java +++ b/src/buglinky/BugLinkyServlet.java @@ -15,6 +15,7 @@ package buglinky; +import java.util.ArrayList; import java.util.logging.Logger; import com.google.wave.api.AbstractRobotServlet; @@ -68,7 +69,13 @@ private void addInstructionsToWave(RobotMessageBundle bundle) { /** Dispatch events to the appropriate handler method. */ private void dispatchEvents(RobotMessageBundle bundle) { - BlipProcessor annotator = new BugLinkAnnotator(BUG_URL); + // We clean up URLs first, so that we can annotate the newly-created + // text in the second pass. + ArrayList processors = new ArrayList(); + processors.add(new BugUrlReplacer(BUG_URL)); + processors.add(new BugLinkAnnotator(BUG_URL)); + + // Process each event. for (Event e : bundle.getEvents()) { if (!e.getModifiedBy().equals(BOT_ADDRESS)) { switch (e.getType()) { @@ -78,7 +85,8 @@ private void dispatchEvents(RobotMessageBundle bundle) { // BLIP_VERSION_CHANGED, we'll apply our links in real time. case BLIP_SUBMITTED: case BLIP_VERSION_CHANGED: - annotator.processBlip(e.getBlip()); + for (BlipProcessor processor : processors) + processor.processBlip(e.getBlip()); break; default: diff --git a/src/buglinky/BugUrlReplacer.java b/src/buglinky/BugUrlReplacer.java new file mode 100644 index 0000000..1733d87 --- /dev/null +++ b/src/buglinky/BugUrlReplacer.java @@ -0,0 +1,32 @@ +package buglinky; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.wave.api.Range; +import com.google.wave.api.TextView; + +/** + * Convert raw bug tracker URLs into text of the form "issue #NNN". + */ +class BugUrlReplacer extends BlipProcessor { + /** The URL to a bug, minus the actual bug number. */ + private String bugUrl; + + /** Create a BugUrlReplacer for the specified URL. */ + public BugUrlReplacer(String bugUrl) { + super(); + this.bugUrl = bugUrl; + } + + @Override + protected String getPattern() { + return Pattern.quote(bugUrl) + "(\\d+)"; + } + + @Override + protected void processMatch(TextView doc, Range range, Matcher match) { + replace(doc, range, "issue #" + match.group(1)); + } + +}