<commit_before>/**
 * Copyright (C) 2011 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms.analysis

import org.orbeon.oxf.common.OXFException
import org.orbeon.oxf.util.CollectionUtils._
import org.orbeon.oxf.util.Logging
import org.orbeon.oxf.util.StaticXPath.VirtualNodeType
import org.orbeon.oxf.xforms._
import org.orbeon.oxf.xforms.analysis.controls._
import org.orbeon.oxf.xforms.analysis.model.ModelDefs.MIP
import org.orbeon.oxf.xforms.analysis.model.{Model, ModelDefs, StaticBind}
import org.orbeon.oxf.xforms.model.{XFormsInstance, XFormsModel}
import org.orbeon.oxf.xml.SaxonUtils
import org.orbeon.saxon.om
import org.orbeon.scaxon.SimplePath._
import org.orbeon.xforms.XFormsId
import org.orbeon.xforms.analysis.model.ValidationLevel
import org.w3c.dom.Node._

import scala.collection.{mutable => m}


class PathMapXPathDependencies(
  containingDocument: XFormsContainingDocument
) extends XPathDependencies with Logging {

  import PathMapXPathDependencies._

  private implicit val logger = containingDocument.getIndentedLogger("dependencies")

  // Represent the state of changes to a model
  private class ModelState(val modelKey: ModelOrInstanceKey, val model: XFormsModel) {

    var hasStructuralChanges = false

    var calculateMIPsEvaluatedOnce = false  // start dirty
    var validateMIPsEvaluatedOnce  = false  // start dirty

    // Meaning of a change: "the string value of the node has changed"
    var recalculateChangeset = new MapSet[ModelOrInstanceKey, String]   // changeset for recalculate MIPs
    var revalidateChangeset  = recalculateChangeset                     // changeset for revalidate MIPs

    def markValueChangedForTests(instance: XFormsInstance, path: String): Unit = {
      if (! hasStructuralChanges) {

        val instanceKey  = ModelOrInstanceKey(instance)
        val instancePath = instanceKey -> path

        recalculateChangeset += instancePath
        if (revalidateChangeset ne recalculateChangeset)
          revalidateChangeset += instancePath // also add to revalidate changeset if it is different

        RefreshState.instancesByKey   += instanceKey -> instance
        RefreshState.refreshChangeset += instancePath
      }
    }

    def markValueChanged(node: om.NodeInfo): Unit = {
      // Only care about path changes if there is no structural change for this model, since structural changes
      // for now disable any more subtle path-based check.
      if (! hasStructuralChanges) {

        // Create instance/path combo
        val instance = containingDocument.instanceForNodeOpt(node).orNull // TODO: `Option`

        def processNode(n: om.NodeInfo): Unit = {
          val path = SaxonUtils.createFingerprintedPath(n)

          val instanceKey  = ModelOrInstanceKey(instance)
          val instancePath = instanceKey -> path

          // Update model and view changesets
          recalculateChangeset += instancePath
          if (revalidateChangeset ne recalculateChangeset)
            revalidateChangeset += instancePath // also add to revalidate changeset if it is different

          RefreshState.instancesByKey   += instanceKey -> instance
          RefreshState.refreshChangeset += instancePath

          // Add parent elements as well. The idea is that if the string value of /a/b/c changed, then the
          // string value of /a/b did as well, and so did /a's.
          // This adds more entries to the changeset, but handles cases such as detecting changes impacting
          // the string() or serialize() functions.
          n parent * foreach processNode
        }

        processNode(node)
      }
    }

    def markStructuralChange(): Unit = {

      // Update model and view information
      hasStructuralChanges = true
      RefreshState.structuralChangeModelKeys += modelKey

      markBindsDirty()
    }

    def rebuildDone(): Unit = {
      hasStructuralChanges = false

      markBindsDirty()
    }

    private def markBindsDirty(): Unit = {
      calculateMIPsEvaluatedOnce = false
      validateMIPsEvaluatedOnce = false

      // Changesets won't be used
      recalculateChangeset.clear()
      revalidateChangeset = recalculateChangeset
    }

    // Say that for this model, calculate binds are clean and can be checked for modifications based on value changes
    def recalculateDone(): Unit = {
      calculateMIPsEvaluatedOnce = true
      recalculateChangeset = clearChangeset(recalculateChangeset, revalidateChangeset)
    }

    // Say that for this model, validate binds are clean and can be checked for modifications based on value changes
    def revalidateDone(): Unit = {
      validateMIPsEvaluatedOnce = true
      revalidateChangeset = clearChangeset(revalidateChangeset, recalculateChangeset)
    }

    // Return an empty changeset, trying to point to the empty right changeset if possible
    // This is so that we can try to avoid adding changes to both changesets later
    private def clearChangeset(left: MapSet[ModelOrInstanceKey, String], right: MapSet[ModelOrInstanceKey, String]) =
      if (right isEmpty)
        right
      else if (left ne right) {
        left.clear()
        left
      } else
        new MapSet[ModelOrInstanceKey, String]

    def refreshDone() = ()

    def isMIPInitiallyDirty(mip: StaticBind.MIP) =
      mip.isValidateMIP && ! validateMIPsEvaluatedOnce || ! mip.isValidateMIP && ! calculateMIPsEvaluatedOnce
  }

  // Keep state related to the view
  private object RefreshState {

    val modelStates = new m.HashMap[ModelOrInstanceKey, ModelState]

    def getOrCreateModelState(model: XFormsModel) = {
      val modelKey = ModelOrInstanceKey(model)
      modelStates.getOrElseUpdate(modelKey, new ModelState(modelKey, model))
    }

    // Used between refresh/binding update start/done
    var inRefresh = false
    var inBindingUpdate = false

    // Structural changes
    val structuralChangeModelKeys = new m.HashSet[ModelOrInstanceKey]

    // Modified paths by instance key
    val refreshChangeset = new MapSet[ModelOrInstanceKey, String]
    val instancesByKey   = m.Map[ModelOrInstanceKey, XFormsInstance]()

    // Caches to speedup checks on repeated items
    val modifiedBindingCacheForRepeats = new m.HashMap[RepeatCacheKey, UpdateResult]<commit_msg>Minor: types, etc.<commit_after>/**
 * Copyright (C) 2011 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms.analysis

import org.orbeon.oxf.common.OXFException
import org.orbeon.oxf.util.CollectionUtils._
import org.orbeon.oxf.util.{IndentedLogger, Logging}
import org.orbeon.oxf.util.StaticXPath.VirtualNodeType
import org.orbeon.oxf.xforms._
import org.orbeon.oxf.xforms.analysis.controls._
import org.orbeon.oxf.xforms.analysis.model.ModelDefs.MIP
import org.orbeon.oxf.xforms.analysis.model.{Model, ModelDefs, StaticBind}
import org.orbeon.oxf.xforms.model.{XFormsInstance, XFormsModel}
import org.orbeon.oxf.xml.SaxonUtils
import org.orbeon.saxon.om
import org.orbeon.scaxon.SimplePath._
import org.orbeon.xforms.XFormsId
import org.orbeon.xforms.analysis.model.ValidationLevel
import org.w3c.dom.Node._

import scala.collection.{mutable => m}


class PathMapXPathDependencies(
  containingDocument: XFormsContainingDocument
) extends XPathDependencies with Logging {

  import PathMapXPathDependencies._

  private implicit val logger: IndentedLogger = containingDocument.getIndentedLogger("dependencies")

  // Represent the state of changes to a model
  private class ModelState(val modelKey: ModelOrInstanceKey, val model: XFormsModel) {

    var hasStructuralChanges = false

    var calculateMIPsEvaluatedOnce = false  // start dirty
    var validateMIPsEvaluatedOnce  = false  // start dirty

    // Meaning of a change: "the string value of the node has changed"
    var recalculateChangeset = new MapSet[ModelOrInstanceKey, String]   // changeset for recalculate MIPs
    var revalidateChangeset  = recalculateChangeset                     // changeset for revalidate MIPs

    def markValueChangedForTests(instance: XFormsInstance, path: String): Unit = {
      if (! hasStructuralChanges) {

        val instanceKey  = ModelOrInstanceKey(instance)
        val instancePath = instanceKey -> path

        recalculateChangeset += instancePath
        if (revalidateChangeset ne recalculateChangeset)
          revalidateChangeset += instancePath // also add to revalidate changeset if it is different

        RefreshState.instancesByKey   += instanceKey -> instance
        RefreshState.refreshChangeset += instancePath
      }
    }

    def markValueChanged(node: om.NodeInfo): Unit = {
      // Only care about path changes if there is no structural change for this model, since structural changes
      // for now disable any more subtle path-based check.
      if (! hasStructuralChanges) {

        // Create instance/path combo
        val instance = containingDocument.instanceForNodeOpt(node).orNull // TODO: `Option`

        def processNode(n: om.NodeInfo): Unit = {
          val path = SaxonUtils.createFingerprintedPath(n)

          val instanceKey  = ModelOrInstanceKey(instance)
          val instancePath = instanceKey -> path

          // Update model and view changesets
          recalculateChangeset += instancePath
          if (revalidateChangeset ne recalculateChangeset)
            revalidateChangeset += instancePath // also add to revalidate changeset if it is different

          RefreshState.instancesByKey   += instanceKey -> instance
          RefreshState.refreshChangeset += instancePath

          // Add parent elements as well. The idea is that if the string value of /a/b/c changed, then the
          // string value of /a/b did as well, and so did /a's.
          // This adds more entries to the changeset, but handles cases such as detecting changes impacting
          // the string() or serialize() functions.
          n parent * foreach processNode
        }

        processNode(node)
      }
    }

    def markStructuralChange(): Unit = {

      // Update model and view information
      hasStructuralChanges = true
      RefreshState.structuralChangeModelKeys += modelKey

      markBindsDirty()
    }

    def rebuildDone(): Unit = {
      hasStructuralChanges = false

      markBindsDirty()
    }

    private def markBindsDirty(): Unit = {
      calculateMIPsEvaluatedOnce = false
      validateMIPsEvaluatedOnce = false

      // Changesets won't be used
      recalculateChangeset.clear()
      revalidateChangeset = recalculateChangeset
    }

    // Say that for this model, calculate binds are clean and can be checked for modifications based on value changes
    def recalculateDone(): Unit = {
      calculateMIPsEvaluatedOnce = true
      recalculateChangeset = clearChangeset(recalculateChangeset, revalidateChangeset)
    }

    // Say that for this model, validate binds are clean and can be checked for modifications based on value changes
    def revalidateDone(): Unit = {
      validateMIPsEvaluatedOnce = true
      revalidateChangeset = clearChangeset(revalidateChangeset, recalculateChangeset)
    }

    // Return an empty changeset, trying to point to the empty right changeset if possible
    // This is so that we can try to avoid adding changes to both changesets later
    private def clearChangeset(left: MapSet[ModelOrInstanceKey, String], right: MapSet[ModelOrInstanceKey, String]) =
      if (right.isEmpty)
        right
      else if (left ne right) {
        left.clear()
        left
      } else
        new MapSet[ModelOrInstanceKey, String]

    def refreshDone(): Unit = ()

    def isMIPInitiallyDirty(mip: StaticBind.MIP): Boolean =
      mip.isValidateMIP && ! validateMIPsEvaluatedOnce || ! mip.isValidateMIP && ! calculateMIPsEvaluatedOnce
  }

  // Keep state related to the view
  private object RefreshState {

    val modelStates = new m.HashMap[ModelOrInstanceKey, ModelState]

    def getOrCreateModelState(model: XFormsModel): ModelState = {
      val modelKey = ModelOrInstanceKey(model)
      modelStates.getOrElseUpdate(modelKey, new ModelState(modelKey, model))
    }

    // Used between refresh/binding update start/done
    var inRefresh = false
    var inBindingUpdate = false

    // Structural changes
    val structuralChangeModelKeys = new m.HashSet[ModelOrInstanceKey]

    // Modified paths by instance key
    val refreshChangeset = new MapSet[ModelOrInstanceKey, String]
    val instancesByKey   = m.Map[ModelOrInstanceKey, XFormsInstance]()

    // Caches to speedup checks on repeated items
    val modifiedBindingCacheForRepeats = new m.HashMap[RepeatCacheKey, UpdateResult]
{'fraction_non_alphanumeric': 0.06981981981981981, 'fraction_numerical': 0.001017146178436501, 'mean_word_length': 3.2629296995973984, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5017490', 'n_tokens_mistral': 3823, 'n_tokens_neox': 3672, 'n_words': 1193}