NullPointerException при работе с сохранением состояния PartialFunction и collectFirst - PullRequest
0 голосов
/ 18 августа 2011

Учтите это (очень уродливый код):

object ExternalReferences2 {
  import java.util.regex._

  implicit def symbol2string(sym: Symbol) = sym.name

  object Mapping {
    def fromXml(mapping: scala.xml.NodeSeq) = {
      new Mapping(mapping \ 'vendor text, 
                  mapping \ 'match text, 
                  mapping \ 'format text)
    }
  }
  case class Mapping(vendor: String, 
                     matches: String,
                     format: String) extends PartialFunction[String, String] {
    private val pattern = Pattern.compile(matches)
    private var _currentMatcher: Matcher = null
    private def currentMatcher = 
      { println("Getting matcher: " + _currentMatcher); _currentMatcher }
    private def currentMatcher_=(matcher: Matcher) = 
      { println("Setting matcher: " + matcher); _currentMatcher = matcher }

    def isDefinedAt(entity: String) = 
      { currentMatcher = pattern.matcher(entity); currentMatcher.matches }

    def apply(entity: String) = apply

    def apply = {
      val range = 0 until currentMatcher.groupCount()
      val groups = range 
                     map (currentMatcher.group(_)) 
                     filterNot (_ == null) 
                     map (_.replace('.', '/'))
      format.format(groups: _*)
    }
  }

  val config =
    <external-links>
      <mapping>
        <vendor>OpenJDK</vendor>
        <match>{ """^(javax?|sunw?|com.sun|org\.(ietf\.jgss|omg|w3c\.dom|xml\.sax))(\.[^.]+)+$""" }</match>
        <format>{ "http://download.oracle.com/javase/7/docs/api/%s.html" }</format>
      </mapping>
    </external-links>

  def getLinkNew(entity: String) =
     (config \ 'mapping) 
       collectFirst({ case m => Mapping.fromXml(m)})
       map(_.apply)

  def getLinkOld(entity: String) =
    (config \ 'mapping).view 
      map(m => Mapping.fromXml(m)) 
      find(_.isDefinedAt(entity)) 
      map(_.apply)
}

Я пытался улучшить метод getLinkOld, используя collectFirst, как показано в getLinkNew, но я всегда получаю NullPointerException, потому что_currentMatcher по-прежнему установлен на null

scala> ExternalReferences2.getLinkNew("java.util.Date")
Getting matcher: null
java.lang.NullPointerException
    at ExternalReferences2$Mapping.apply(<console>:32)
    at ExternalReferences2$$anonfun$getLinkNew$2.apply(<console>:58)
    at ExternalReferences2$$anonfun$getLinkNew$2.apply(<console>:58)
    at scala.Option.map(Option.scala:131)
    at ExternalReferences2$.getLinkNew(<console>:58)
    at .<init>(<console>:13)
    at .<clinit>(<console>)
    at .<init>(<console>:11)
    at .<clinit>(<console>)

, хотя он отлично работает с getLinkOld.

В чем здесь проблема?

1 Ответ

3 голосов
/ 18 августа 2011

Ваш сопоставитель создан как побочный эффект в isDefined.Передача побочных эффектов в рутину, такую ​​как map, обычно является причиной катастрофы, но это даже не то, что происходит здесь.Ваш код требует, чтобы isDefined был вызван непосредственно перед apply с тем же аргументом.Это делает ваш код очень хрупким, и это то, что вы должны изменить.

Клиенты PartialFunction вообще не обязаны следовать этому протоколу.Представьте себе, например,

if (f.isDefinedAt(x) && f.isDefinedAt(y)) {fx = f(x); fy = f(y)}. 

А здесь код, который вызывает apply, даже не ваш, а классы коллекции, так что вы не контролируете, что происходит.

Ваша конкретная проблема вgetLinkNew означает, что isDefined просто никогда не вызывается. Аргумент PartialFunction для collectFirst равен {case m => ...}.isDefined, который вызывается, является isDefined этой функции.Поскольку m является неопровержимым шаблоном, это всегда верно, и collectFirst всегда будет возвращать первый элемент, если он есть.То, что частичная функция возвращает другую частичную функцию (Mapping), которая не определена на m, не имеет значения.

Редактировать - Возможный обходной путь

Очень легким изменением было бы проверить, доступен ли matcher, и создать его, если его нет.Лучше сохраните строку entity, которая также использовалась для его создания, чтобы вы могли проверить, является ли она правильной.Это должно сделать побочный эффект мягким, если нет многопоточности.Но, кстати, не используйте null, используйте Option, поэтому компилятор не позволит вам игнорировать вероятность того, что это может быть None.

var _currentMatcher : Option[(String, Matcher)] = None
def currentMatcher(entity: String) : Matcher = _currentMatcher match{
  case Some(e,m) if e == entity => m
  case _ => {
    _currentMatcher = (entity, pattern.matcher(entity))
    _currentmatcher._2
  }
}

Редактировать снова.Глупый меня

Извините, так называемый обходной путь действительно делает класс более безопасным, но это не делает решение collectFirst работать.Опять же, частичная функция case m => всегда определяется (примечание: entity даже не появляется в вашем коде getLinkNew, что должно беспокоить).Проблема заключается в том, что для NodeSeq потребуется PartialFunction (не для объекта, который будет известен функции, но не будет передан в качестве аргумента).Будет вызвано isDefined, затем применить.Шаблон и сопоставитель зависят от NodeSeq, поэтому их нельзя создать заранее, а только в isDefined и / или apply.В том же духе вы можете кэшировать то, что вычисляется в isDefined для повторного использования в Apply.Это определенно не красиво

def linkFor(entity: String) = new PartialFunction[NodeSeq, String] {
  var _matcher : Option[String, Matcher] = None
  def matcher(regexp: String) = _matcher match {
    case Some(r, m) where r == regexp => m
    case None => {
      val pattern = Pattern.compile(regexp)
      _matcher = (regexp, pattern.matcher(entity))
      _matcher._2
    }
  }
  def isDefined(mapping: NodeSeq) = {
    matcher(mapping \ "match" text).matches
  }
  def apply(mapping: NodeSeq) = {
     // call matcher(...), it is likely to reuse previous matcher, build result
  }

}

Вы используете это с (config \ mapping).collectFirst(linkFor(entity))

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...