Skip to content

Commit

Permalink
Factor annotation code out into separate classes
Browse files Browse the repository at this point in the history
This will eventually make it easier to have more than one annotator.
  • Loading branch information
emk committed Aug 30, 2009
1 parent c13a832 commit 30c13d6
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 57 deletions.
78 changes: 78 additions & 0 deletions src/buglinky/Annotator.java
@@ -0,0 +1,78 @@
// buglinky - A robot for adding bugtracker links to a wave
// Copyright 2009 Eric Kidd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package buglinky;

import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.wave.api.Annotation;
import com.google.wave.api.Blip;
import com.google.wave.api.Range;
import com.google.wave.api.TextView;

/**
* Adds annotations to a blip.
*/
abstract class Annotator {
private static final Logger LOG =
Logger.getLogger(Annotator.class.getName());

/** Return a regular expression matching the text we want to process. */
protected abstract Pattern getPattern();

/** Process a regular expression match. */
protected abstract void processMatch(TextView doc, Range range,
Matcher match);

/** Add links to the specified blip. */
public void processBlip(Blip blip) {
LOG.fine("Annotating blip " + blip.getBlipId());
// Adapted from http://senikk.com/min-f%C3%B8rste-google-wave-robot,
// a robot which links to @names on Twitter.
TextView doc = blip.getDocument();
Matcher matcher = getPattern().matcher(doc.getText());
while (matcher.find()) {
LOG.fine("Found text to annotate: " + matcher.group());
Range range = new Range(matcher.start(), matcher.end());
processMatch(doc, range, matcher);
}
}

/**
* Add an annotation if it isn't already present.
*
* The Wave Robot API does not currently filter out duplicate annotation
* requests, which causes extra network traffic and more possibilities for
* nasty bot loops. So we do this screening on our end.
*/
protected void maybeAnnotate(TextView doc, Range range, String name,
String value) {
// If this annotation is already present, give up now. Note that
// we allow the existing annotation to be bigger than the one we're
// creating, because in that case, setting the new annotation won't
// do anything useful.
for (Annotation annotation : doc.getAnnotations(range, name)) {
if (annotation.getValue().equals(value) &&
annotation.getRange().getStart() <= range.getStart() &&
range.getEnd() <= annotation.getRange().getEnd())
return;
}

LOG.fine("Annotating with " + name + "=" + value);
doc.setAnnotation(range, name, value);
}
}
49 changes: 49 additions & 0 deletions src/buglinky/BugLinkAnnotator.java
@@ -0,0 +1,49 @@
// buglinky - A robot for adding bugtracker links to a wave
// Copyright 2009 Eric Kidd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package buglinky;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.wave.api.Range;
import com.google.wave.api.TextView;

/** Add bug links to a blip. */
class BugLinkAnnotator extends Annotator {
/** The URL to a bug, minus the actual bug number. */
private String bugUrl;

/** Create a BugLinkAnnotator for the specified URL. */
public BugLinkAnnotator(String bugUrl) {
this.bugUrl = bugUrl;
}

/** Return a regular expression matching the text we want to process. */
protected Pattern getPattern() {
// Regex used to find bug numbers in the text. Note that we require at
// least one non-numeric character after the bug number (and not a
// newline). This ensures that when the user is adding text at the
// end of a paragraph, we won't add any links until the user is safely
// outside the area that we need to modify. Users making modifications
// inside of paragraphs will have to live with minor glitches.
return Pattern.compile("(?:bug|issue) #(\\d+)(?!\\d|\\r|\\n)");
}

/** Process a regular expression match. */
protected void processMatch(TextView doc, Range range, Matcher match) {
maybeAnnotate(doc, range, "link/manual", bugUrl.concat(match.group(1)));
}
}
67 changes: 10 additions & 57 deletions src/buglinky/BugLinkyServlet.java
Expand Up @@ -16,10 +16,12 @@
package buglinky;

import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.wave.api.*;
import com.google.wave.api.AbstractRobotServlet;
import com.google.wave.api.Blip;
import com.google.wave.api.Event;
import com.google.wave.api.RobotMessageBundle;
import com.google.wave.api.TextView;

/** Called via JSON-RPC whenever an event occurs on one of our waves. */
@SuppressWarnings("serial")
Expand All @@ -28,24 +30,13 @@ public class BugLinkyServlet extends AbstractRobotServlet {
Logger.getLogger(BugLinkyServlet.class.getName());

private static final String ME = "buglinky@appspot.com";
private static final String LINK = "link/manual";

private static final String INSTRUCTIONS =
"buglinky will attempt to link \"bug #NNN\" to a bug tracker.";

/** The URL to a specific bug in our bug tracker, minus the number. */
private static final String BUG_URL =
"http://code.google.com/p/google-wave-resources/issues/detail?id=";

/**
* Regex used to find bug numbers in the text. Note that we require at least
* one non-numeric character after the bug number (and not a newline). This
* ensures that when the user is adding text at the end of a paragraph, we
* won't add any links until the user is safely outside the area that we
* need to modify. Users making modifications inside of paragraphs will have
* to live with minor glitches.
*/
private static final Pattern REGEX =
Pattern.compile("(?:bug|issue) #(\\d+)(?!\\d|\\r|\\n)");


/** Called when we receive events from the Wave server. */
@Override
public void processEvents(RobotMessageBundle bundle) {
Expand All @@ -65,6 +56,7 @@ private void addInstructionsToWave(RobotMessageBundle bundle) {

/** Dispatch events to the appropriate handler method. */
private void dispatchEvents(RobotMessageBundle bundle) {
Annotator annotator = new BugLinkAnnotator(BUG_URL);
for (Event e : bundle.getEvents()) {
if (!e.getModifiedBy().equals(ME)) {
switch (e.getType()) {
Expand All @@ -74,7 +66,7 @@ private void dispatchEvents(RobotMessageBundle bundle) {
// BLIP_VERSION_CHANGED, we'll apply our links in real time.
case BLIP_SUBMITTED:
case BLIP_VERSION_CHANGED:
addLinksToBlip(e.getBlip());
annotator.processBlip(e.getBlip());
break;

default:
Expand All @@ -83,43 +75,4 @@ private void dispatchEvents(RobotMessageBundle bundle) {
}
}
}

/** Add links to the specified blip. */
private void addLinksToBlip(Blip blip) {
LOG.fine("Adding links to blip " + blip.getBlipId());
// Adapted from http://senikk.com/min-f%C3%B8rste-google-wave-robot,
// a robot which links to @names on Twitter.
TextView doc = blip.getDocument();
Matcher matcher = REGEX.matcher(doc.getText());
while (matcher.find()) {
LOG.fine("Found a link: " + matcher.group());
Range range = new Range(matcher.start(), matcher.end());
String url = BUG_URL.concat(matcher.group(1));
maybeAnnotate(doc, range, LINK, url);
}
}

/**
* Add an annotation if it isn't already present.
*
* The Wave Robot API does not currently filter out duplicate annotation
* requests, which causes extra network traffic and more possibilities for
* nasty bot loops. So we do this screening on our end.
*/
private void maybeAnnotate(TextView doc, Range range, String name,
String value) {
// If this annotation is already present, give up now. Note that
// we allow the existing annotation to be bigger than the one we're
// creating, because in that case, setting the new annotation won't
// do anything useful.
for (Annotation annotation : doc.getAnnotations(range, name)) {
if (annotation.getValue().equals(value) &&
annotation.getRange().getStart() <= range.getStart() &&
range.getEnd() <= annotation.getRange().getEnd())
return;
}

LOG.fine("Annotating with " + value);
doc.setAnnotation(range, name, value);
}
}

0 comments on commit 30c13d6

Please sign in to comment.