<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>desktop/src/main/scala/org/talkingpuffin/Constants.scala</filename>
    </added>
    <added>
      <filename>desktop/src/main/scala/org/talkingpuffin/filter/CompoundFilters.scala</filename>
    </added>
    <added>
      <filename>desktop/src/main/scala/org/talkingpuffin/filter/FilterAdder.scala</filename>
    </added>
    <added>
      <filename>desktop/src/main/scala/org/talkingpuffin/filter/FilterSetChanged.scala</filename>
    </added>
    <added>
      <filename>desktop/src/main/scala/org/talkingpuffin/filter/InOutFilters.scala</filename>
    </added>
    <added>
      <filename>desktop/src/main/scala/org/talkingpuffin/filter/TextFilter.scala</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -2,6 +2,4 @@ package org.talkingpuffin
 
 object Globals {
   var sessions: List[Session] = Nil
-  
-  val MaxPeopleForAutoPaneCreation = 2000
 }</diff>
      <filename>desktop/src/main/scala/org/talkingpuffin/Globals.scala</filename>
    </modified>
    <modified>
      <diff>@@ -1,73 +1,45 @@
 package org.talkingpuffin.filter
 
 import org.talkingpuffin.twitter.TwitterStatus
-import java.util.regex.Pattern
-import swing.Publisher
-import swing.event.Event
 import org.talkingpuffin.util.Loggable
 import org.talkingpuffin.filter.RetweetDetector._
-import org.talkingpuffin.ui.LinkExtractor
 
-class CompoundFilters extends Publisher {
-  var list = List[CompoundFilter]()
-  def clear = list = List[CompoundFilter]()
-  def matchesAll(status: TwitterStatus): Boolean = list.forall(_.matches(status))
-  def matchesAny(status: TwitterStatus): Boolean = list.exists(_.matches(status))
-  def add(cf: CompoundFilter) = {
-    list = list ::: List(cf)
-    publish
-  }
-  def publish: Unit = publish(new CompoundFiltersChanged)
-  override def toString = list.map(_.toString).mkString(&quot;&#8593;&quot;)
-}
-
-class CompoundFiltersChanged extends Event
-
-case class CompoundFilter(val textFilters: List[TextFilter], val retweet: Option[Boolean], 
-    val commentedRetweet: Option[Boolean]) extends Loggable {
+/**
+ * Used to match a tweet against a set of TextFilters and retweet options.
+ */
+case class CompoundFilter(val textFilters: List[TextFilter], 
+    val retweet: Option[Boolean], val commentedRetweet: Option[Boolean]) 
+    extends Loggable {
 
+  /**
+   * Whether the specified tweet matches all the elements of this CompoundFilter.
+   */
   def matches(status: TwitterStatus): Boolean = {
-    textFilters.foreach(tf =&gt; {
-      val elementMatches = if (tf.isRegEx)
-        Pattern.compile(tf.text).matcher(tf.getCompareWith(status)).find
+
+    def allTextFiltersMatch = textFilters.forall(tf =&gt; {
+      val text = tf.text
+      val compareWith = tf.getCompareWith(status)
+      
+      if (tf.isRegEx)
+        text.r.findFirstIn(compareWith).isDefined
       else
-        tf.getCompareWith(status).toUpperCase.contains(tf.text.toUpperCase)
-      if (! elementMatches) {
-        return false
-      }
+        compareWith.toUpperCase.contains(text.toUpperCase)
     })
-    (retweet match {
-      case Some(rt) if rt =&gt; status.isRetweet
-      case _ =&gt; true
-    }) &amp;&amp; 
-    (commentedRetweet match {
-      case Some(rt) if rt =&gt; status.isCommentedRetweet
-      case _ =&gt; true
-    })  
-  }
-  
-  override def toString = 
-    List(textFilters.map(_.toString).mkString(&quot;&#8645;&quot;), retweet.toString, commentedRetweet.toString).mkString(&quot;&#8597;&quot;)
-}
-
-sealed abstract case class TextFilter (val text: String, val isRegEx: Boolean, 
-    getCompareWith: (TwitterStatus) =&gt; String) {
-  override def toString = List(getClass.getName, text, isRegEx.toString).mkString(&quot;&#8595;&quot;)
-}
 
-case class FromTextFilter(override val text: String, override val isRegEx: Boolean) 
-    extends TextFilter(text, isRegEx, (status) =&gt; status.user.screenName)
+    def retweetFilterMatches = booleanFilterMatches(retweet, status.isRetweet)
 
-case class TextTextFilter(override val text: String, override val isRegEx: Boolean) 
-    extends TextFilter(text, isRegEx, (status) =&gt; status.text)
+    def commentedRetweetFilterMatches = booleanFilterMatches(
+        commentedRetweet, status.isCommentedRetweet)
 
-case class ToTextFilter(override val text: String, override val isRegEx: Boolean) 
-    extends TextFilter(text, isRegEx, 
-    (status) =&gt; LinkExtractor.getReplyToInfo(status.inReplyToStatusId, status.text) match {
-      case Some(screenNameAndId) =&gt; screenNameAndId._1
-      case _ =&gt; &quot;&quot;
-    }) 
+    def booleanFilterMatches(filter: Option[Boolean], value: Boolean) = filter match {
+      case Some(f) =&gt; f == value
+      case _ =&gt; true
+    }
 
-case class SourceTextFilter(override val text: String, override val isRegEx: Boolean) 
-    extends TextFilter(text, isRegEx, (status) =&gt; status.sourceName) 
+    allTextFiltersMatch &amp;&amp; retweetFilterMatches &amp;&amp; commentedRetweetFilterMatches
+  }
+  
+  override def toString = List(textFilters.map(_.toString).mkString(&quot;&#8645;&quot;), 
+    retweet.toString, commentedRetweet.toString).mkString(&quot;&#8597;&quot;)
+}
 </diff>
      <filename>desktop/src/main/scala/org/talkingpuffin/filter/CompoundFilter.scala</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,5 @@
 package org.talkingpuffin.filter
 
-import scala.swing.event.Event
 import scala.swing.Publisher
 import org.talkingpuffin.ui.{Relationships}
 import org.talkingpuffin.twitter.TwitterStatus
@@ -12,17 +11,14 @@ import org.talkingpuffin.util.Loggable
  */
 class FilterSet(tagUsers: TagUsers) extends Publisher with Loggable {
   
-  class InOutSet {
-    var cpdFilters = new CompoundFilters
-    var tags = List[String]()
-    def tagMatches(userId: Long) = tags.exists(tagUsers.contains(_, userId))
-  }
-  
   var excludeFriendRetweets: Boolean = false
   var excludeNonFollowers: Boolean = false
   var useNoiseFilters: Boolean = false
-  val includeSet = new InOutSet
-  val excludeSet = new InOutSet
+  
+  val includeSet = new InOutFilters(tagUsers)
+  val excludeSet = new InOutFilters(tagUsers)
+  
+  val adder = new FilterAdder(this)
   
   /**
    * Filter the given list of statuses, returning a list of only those that pass the filters
@@ -50,41 +46,6 @@ class FilterSet(tagUsers: TagUsers) extends Publisher with Loggable {
     statuses.filter(includeStatus)
   }
   
-  def muteApps(apps: List[String]) {
-    apps.foreach(app =&gt; excludeSet.cpdFilters.add(
-        CompoundFilter(List(SourceTextFilter(app, false)), None, None)))
-    publish
-  }
-
-  def muteSenders(senders: List[String]) {
-    senders.foreach(sender =&gt; excludeSet.cpdFilters.add(
-        CompoundFilter(List(FromTextFilter(sender, false)), None, None)))
-    publish
-  }
-
-  def muteRetweetUsers(senders: List[String]) {
-    senders.foreach(sender =&gt; excludeSet.cpdFilters.add(
-        CompoundFilter(List(FromTextFilter(sender, false)), Some(true), None)))
-    publish
-  }
-
-  def muteSelectedUsersCommentedRetweets(senders: List[String]) {
-    senders.foreach(sender =&gt; {
-      val filters = List(FromTextFilter(sender, false))
-      excludeSet.cpdFilters.add(CompoundFilter(filters, Some(true), None))
-      excludeSet.cpdFilters.add(CompoundFilter(filters, None, Some(true)))
-    })
-    publish
-  }
-
-  def muteSenderReceivers(srs: List[(String, String)]) {
-    srs.foreach(sr =&gt; excludeSet.cpdFilters.add(
-        CompoundFilter(List(FromTextFilter(sr._1, false), ToTextFilter(sr._2, false)), None, None)))
-    publish
-  }
-
   def publish: Unit = publish(new FilterSetChanged(this))
 }
 
-case class FilterSetChanged(filterSet: FilterSet) extends Event
-</diff>
      <filename>desktop/src/main/scala/org/talkingpuffin/filter/FilterSet.scala</filename>
    </modified>
    <modified>
      <diff>@@ -2,49 +2,52 @@ package org.talkingpuffin.filter
 
 import scala.util.matching.Regex
 import scala.io.Source
+import org.talkingpuffin.Constants
 import org.talkingpuffin.util.Loggable
 
+/**
+ * Loads noise filters from a repository and finds noisy tweets.
+ */
 object NoiseFilter extends Loggable {
-  var exprs = List[Regex]()
-  var loadError: Exception = _
-  
+  var expressions = List[Regex]()
+  var loadError: Option[Exception] = None
+
+  /**
+   * Returns whether the provided tweet is noise.
+   */
   def isNoise(text: String): Boolean = {
     if (needsLoading) 
       load
     
     val textOneLine = text.replaceAll(&quot;(\n|\r)&quot;, &quot;&quot;) // Easier to match text all on one line
     
-    exprs.exists(e =&gt; {
-      textOneLine match {
-        case e() =&gt; {
-          debug(textOneLine + &quot; matched &quot; + e)
-          true
-        }
-        case _ =&gt; false
-      }
-    })
+    val noise = expressions.exists(_.findFirstIn(textOneLine).isDefined)
+    if (noise)
+      debug(textOneLine + &quot; matched&quot;)
+    noise
   }
-  
+
+  /**
+   * Loads noise-matching regular expressions from an external repository.
+   */
   def load {
     try {
-      val regExStrings = Source.fromURL(&quot;http://talkingpuffin.appspot.com/filters/noise&quot;).getLines.
+      val regExStrings = Source.fromURL(Constants.NoiseRepository).getLines.
           map(_.trim).toList.filter(_.length &gt; 0)
       info(&quot;Loaded &quot; + regExStrings)
-      exprs = regExStrings.map(_.r)
-      loadError = null
+      expressions = regExStrings.map(_.r)
+      loadError = None
     } catch {
       case e: Exception =&gt; {
-        loadError = e
+        loadError = Some(e)
         error(e.toString) 
       }
     }
   }
   
   private def needsLoading: Boolean = {
-    exprs == Nil /* None loaded */ &amp;&amp; loadError == null /* We didn&#8217;t previously fail on loading */
+    expressions == Nil /* None loaded */ &amp;&amp; 
+        ! loadError.isDefined /* We didn&#8217;t previously fail on loading */
   }
   
-  private def loadSamples {
-    exprs = List(&quot;.*web.*&quot;, &quot;.*Twitter.*&quot;).map(_.r)
-  }
 }
\ No newline at end of file</diff>
      <filename>desktop/src/main/scala/org/talkingpuffin/filter/NoiseFilter.scala</filename>
    </modified>
    <modified>
      <diff>@@ -132,7 +132,7 @@ class StatusTableModel(val options: StatusTableOptions, val tweetsProvider: Base
   private def mapToIdTuple(users: List[User]) = users.map(user =&gt; (user.id, user))
   
   def muteSelectedUsers(rows: List[Int]) = {
-    filterSet.muteSenders(getScreenNames(rows))
+    filterSet.adder.muteSenders(getScreenNames(rows))
     filterAndNotify
   }
 
@@ -145,24 +145,24 @@ class StatusTableModel(val options: StatusTableOptions, val tweetsProvider: Base
       if rti.isDefined
     } yield (sender, rti.get._1)
     
-    filterSet.muteSenderReceivers(senderReceivers)
+    filterSet.adder.muteSenderReceivers(senderReceivers)
     if (andViceVersa)
-      filterSet.muteSenderReceivers(senderReceivers map (t =&gt; (t._2, t._1)))
+      filterSet.adder.muteSenderReceivers(senderReceivers map (t =&gt; (t._2, t._1)))
     filterAndNotify
   }
 
   def muteSelectedUsersRetweets(rows: List[Int]) = {
-    filterSet.muteRetweetUsers(getScreenNames(rows))
+    filterSet.adder.muteRetweetUsers(getScreenNames(rows))
     filterAndNotify
   }
 
   def muteSelectedUsersCommentedRetweets(rows: List[Int]) = {
-    filterSet.muteSelectedUsersCommentedRetweets(getScreenNames(rows))
+    filterSet.adder.muteSelectedUsersCommentedRetweets(getScreenNames(rows))
     filterAndNotify
   }
 
   def muteSelectedApps(rows: List[Int]) = {
-    filterSet.muteApps(rows.map(i =&gt; filteredStatuses_(i).sourceName))
+    filterSet.adder.muteApps(rows.map(i =&gt; filteredStatuses_(i).sourceName))
     filterAndNotify
   }
 </diff>
      <filename>desktop/src/main/scala/org/talkingpuffin/ui/StatusTableModel.scala</filename>
    </modified>
    <modified>
      <diff>@@ -1,15 +1,15 @@
 package org.talkingpuffin.ui
 
-import _root_.scala.swing.event.{WindowClosing}
+import java.awt.{Point, Dimension}
+import java.text.NumberFormat
 import javax.swing.{ImageIcon}
+import scala.swing.event.{WindowClosing}
 import swing.TabbedPane.Page
 import swing.{Reactor, Frame, TabbedPane, Label, GridBagPanel}
+import org.talkingpuffin.{Main, Globals, Session, Constants}
 import org.talkingpuffin.filter.TagUsers
-import org.talkingpuffin.{Main, Globals, Session}
-import org.talkingpuffin.state.{StateSaver}
-import java.text.NumberFormat
+import org.talkingpuffin.state.StateSaver
 import org.talkingpuffin.twitter.{RateLimitStatusEvent, TwitterUser, AuthenticatedSession}
-import java.awt.{Point, Dimension}
 import org.talkingpuffin.util.{FetchRequest, Loggable}
 
 /**
@@ -77,7 +77,7 @@ class TopFrame(service: String, twitterSession: AuthenticatedSession) extends Fr
   reactions += {
     case ic: IdsChanged =&gt; 
       if (peoplePane == null &amp;&amp; 
-              (rels.followerIds.length + rels.friendIds.length &lt; Globals.MaxPeopleForAutoPaneCreation)) {
+              (rels.followerIds.length + rels.friendIds.length &lt; Constants.MaxPeopleForAutoPaneCreation)) {
         debug(&quot;Not too many people, so automatically creating people pane&quot;)
         createPeoplePane
       } else {</diff>
      <filename>desktop/src/main/scala/org/talkingpuffin/ui/TopFrame.scala</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>d025ced312d808e55c372c2bbf2baaad357b9d3b</id>
    </parent>
  </parents>
  <author>
    <name>Dave Briccetti</name>
    <email>daveb@davebsoft.com</email>
  </author>
  <url>http://github.com/dcbriccetti/talking-puffin/commit/b947d4dc762d96d72a6551ea237c9725e235b052</url>
  <id>b947d4dc762d96d72a6551ea237c9725e235b052</id>
  <committed-date>2009-11-07T13:22:05-08:00</committed-date>
  <authored-date>2009-11-07T13:22:05-08:00</authored-date>
  <message>Refactor, comment the filters code</message>
  <tree>5a5ac8774d6bf912fd2ef1713415c22be6c3cdd1</tree>
  <committer>
    <name>Dave Briccetti</name>
    <email>daveb@davebsoft.com</email>
  </committer>
</commit>
