<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.control.controls

import org.orbeon.dom._
import org.orbeon.oxf.util.CollectionUtils._
import org.orbeon.oxf.util.CoreUtils._
import org.orbeon.oxf.xforms.XFormsConstants._
import org.orbeon.oxf.xforms._
import org.orbeon.oxf.xforms.analysis.PartAnalysisImpl
import org.orbeon.oxf.xforms.analysis.controls.LHHA
import org.orbeon.oxf.xforms.control.Controls._
import org.orbeon.oxf.xforms.control.controls.InstanceMirror._
import org.orbeon.oxf.xforms.control.controls.XXFormsDynamicControl._
import org.orbeon.oxf.xforms.control.{Controls, XFormsComponentControl, XFormsControl, XFormsSingleNodeContainerControl}
import org.orbeon.oxf.xforms.event.Dispatch.EventListener
import org.orbeon.oxf.xforms.event.XFormsEvents._
import org.orbeon.oxf.xforms.event.events.{XFormsDeleteEvent, XFormsInsertEvent, XXFormsValueChangedEvent}
import org.orbeon.oxf.xforms.model.{NoDefaultsStrategy, XFormsModel}
import org.orbeon.oxf.xforms.state.{ControlState, InstancesControls}
import org.orbeon.oxf.xforms.xbl.{Scope, XBLContainer}
import org.orbeon.oxf.xml._
import org.orbeon.oxf.xml.dom4j.Dom4jUtils
import org.orbeon.saxon.`type`.{Type ⇒ SaxonType}
import org.orbeon.saxon.om.{NodeInfo, VirtualNode}
import org.orbeon.scaxon.NodeConversions._
import org.orbeon.scaxon.SimplePath._
import org.w3c.dom.Node.ELEMENT_NODE

import scala.collection.JavaConverters._
import scala.collection.generic.Growable
import scala.collection.mutable

/**
 * xxf:dynamic control
 *
 * This control must bind to an XHTML+XForms document held in an instance. The document is processed as an XForms
 * sub-document and handle as a shadow tree of xxf:dynamic. Changes taking place in the document are dynamically
 * reported into the shadow tree.
 *
 * The following changes are handled specially:
 *
 * - changes to inline instance content on both sides are directly mirrored
 * - changes to content nested within top-level bound nodes cause re-evaluation of the binding only
 * - changes to nested binds cause incremental add/remove of binds
 *
 * All other changes cause the entire sub-document to be reprocessed.
 *
 * In the future the hope is to make any change fully incremental.
 */
class XXFormsDynamicControl(container: XBLContainer, parent: XFormsControl, element: Element, effectiveId: String)
  extends XFormsSingleNodeContainerControl(container, parent, element, effectiveId) {

  case class Nested(container: XBLContainer, partAnalysis: PartAnalysisImpl, template: SAXStore, outerListener: EventListener)

  private var _nested: Option[Nested] = None
  def nested: Option[Nested] = _nested

  private var fullUpdateChange = false
  private val xblChanges       = mutable.Buffer[(String, Element)]()
  private val bindChanges      = mutable.Buffer[(String, Element)]()

  // New scripts created during an update (not functional as of 2018-05-03)
  // NOTE: This should instead be accumulated at the level of the request.
  private var _newScripts: List[ShareableScript] = Nil
  def newScripts: List[ShareableScript] = _newScripts
  def clearNewScripts(): Unit = _newScripts = Nil

  // TODO: This might blow if the control is non-relevant
  override def bindingContextForChildOpt: Option[BindingContext] =
    _nested map { case Nested(container, _, _, _) ⇒
      val contextStack = container.getContextStack
      contextStack.setParentBindingContext(bindingContext)
      contextStack.resetBindingContext()
      contextStack.getCurrentBindingContext
    }

  override def onCreate(restoreState: Boolean, state: Option[ControlState]): Unit = {
    super.onCreate(restoreState, state)
    getBoundElement match {
      case Some(boundElem) ⇒
        updateSubTree(create = true, boundElem)
      case _ ⇒
        // Don't create binding (maybe we could for a read-only instance)
        _nested = None
    }
  }

  override def onDestroy(): Unit = {
    // TODO: XXX remove child container from parent
    _nested = None
    fullUpdateChange = false
  }

  override def onBindingUpdate(oldBinding: BindingContext, newBinding: BindingContext): Unit = {

    if (! SaxonUtils.compareItemSeqs(oldBinding.nodeset.asScala, newBinding.nodeset.asScala))
      fullUpdateChange = true

    getBoundElement foreach { boundElem ⇒
      updateSubTree(create = false, boundElem)
    }
  }

  private def updateSubTree(create: Boolean, boundElem: VirtualNode): Unit =
    if (create || fullUpdateChange) {
      // Document has changed and needs to be fully recreated
      processFullUpdate(create, boundElem)
    } else {
      // Changes to nested binds
      if (bindChanges.nonEmpty)
        processBindsUpdates()

      // Changes to top-level XBL bindings
      if (xblChanges.nonEmpty)
        processXBLUpdates()
    }

  private def processFullUpdate(create: Boolean, boundElem: VirtualNode): Unit = {
    fullUpdateChange = false
    xblChanges.clear()
    bindChanges.clear()<commit_msg>For #3826: more change detection fixes<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.control.controls

import org.orbeon.dom._
import org.orbeon.oxf.util.CollectionUtils._
import org.orbeon.oxf.util.CoreUtils._
import org.orbeon.oxf.xforms.XFormsConstants._
import org.orbeon.oxf.xforms._
import org.orbeon.oxf.xforms.analysis.PartAnalysisImpl
import org.orbeon.oxf.xforms.analysis.controls.LHHA
import org.orbeon.oxf.xforms.control.Controls._
import org.orbeon.oxf.xforms.control.controls.InstanceMirror._
import org.orbeon.oxf.xforms.control.controls.XXFormsDynamicControl._
import org.orbeon.oxf.xforms.control.{Controls, XFormsComponentControl, XFormsControl, XFormsSingleNodeContainerControl}
import org.orbeon.oxf.xforms.event.Dispatch.EventListener
import org.orbeon.oxf.xforms.event.XFormsEvents._
import org.orbeon.oxf.xforms.event.events.{XFormsDeleteEvent, XFormsInsertEvent, XXFormsValueChangedEvent}
import org.orbeon.oxf.xforms.model.{NoDefaultsStrategy, XFormsModel}
import org.orbeon.oxf.xforms.state.{ControlState, InstancesControls}
import org.orbeon.oxf.xforms.xbl.{Scope, XBLContainer}
import org.orbeon.oxf.xml._
import org.orbeon.oxf.xml.dom4j.Dom4jUtils
import org.orbeon.saxon.`type`.{Type ⇒ SaxonType}
import org.orbeon.saxon.om.{NodeInfo, VirtualNode}
import org.orbeon.scaxon.NodeConversions._
import org.orbeon.scaxon.SimplePath._
import org.w3c.dom.Node.ELEMENT_NODE

import scala.collection.JavaConverters._
import scala.collection.generic.Growable
import scala.collection.mutable

/**
 * xxf:dynamic control
 *
 * This control must bind to an XHTML+XForms document held in an instance. The document is processed as an XForms
 * sub-document and handle as a shadow tree of xxf:dynamic. Changes taking place in the document are dynamically
 * reported into the shadow tree.
 *
 * The following changes are handled specially:
 *
 * - changes to inline instance content on both sides are directly mirrored
 * - changes to content nested within top-level bound nodes cause re-evaluation of the binding only
 * - changes to nested binds cause incremental add/remove of binds
 *
 * All other changes cause the entire sub-document to be reprocessed.
 *
 * In the future the hope is to make any change fully incremental.
 */
class XXFormsDynamicControl(container: XBLContainer, parent: XFormsControl, element: Element, effectiveId: String)
  extends XFormsSingleNodeContainerControl(container, parent, element, effectiveId) {

  case class Nested(container: XBLContainer, partAnalysis: PartAnalysisImpl, template: SAXStore, outerListener: EventListener)

  private var _nested: Option[Nested] = None
  def nested: Option[Nested] = _nested

  private var fullUpdateChange = false
  private val xblChanges       = mutable.Buffer[(String, Element)]()
  private val bindChanges      = mutable.Buffer[(String, Element)]()

  // New scripts created during an update (not functional as of 2018-05-03)
  // NOTE: This should instead be accumulated at the level of the request.
  private var _newScripts: List[ShareableScript] = Nil
  def newScripts: List[ShareableScript] = _newScripts
  def clearNewScripts(): Unit = _newScripts = Nil

  // TODO: This might blow if the control is non-relevant
  override def bindingContextForChildOpt: Option[BindingContext] =
    _nested map { case Nested(container, _, _, _) ⇒
      val contextStack = container.getContextStack
      contextStack.setParentBindingContext(bindingContext)
      contextStack.resetBindingContext()
      contextStack.getCurrentBindingContext
    }

  override def onCreate(restoreState: Boolean, state: Option[ControlState]): Unit = {
    super.onCreate(restoreState, state)
    getBoundElement match {
      case Some(boundElem) ⇒
        updateSubTree(create = true, boundElem)
      case _ ⇒
        // Don't create binding (maybe we could for a read-only instance)
        _nested = None
    }
  }

  override def onDestroy(): Unit = {
    // TODO: XXX remove child container from parent
    _nested = None
    fullUpdateChange = false
  }

  override def onBindingUpdate(oldBinding: BindingContext, newBinding: BindingContext): Unit = {

    if (! SaxonUtils.compareItemSeqs(oldBinding.nodeset.asScala, newBinding.nodeset.asScala)) {
      fullUpdateChange = true
      containingDocument.addControlStructuralChange(prefixedId)
    }

    getBoundElement foreach { boundElem ⇒
      updateSubTree(create = false, boundElem)
    }
  }

  private def updateSubTree(create: Boolean, boundElem: VirtualNode): Unit =
    if (create || fullUpdateChange) {
      // Document has changed and needs to be fully recreated
      processFullUpdate(create, boundElem)
    } else {
      // Changes to nested binds
      if (bindChanges.nonEmpty)
        processBindsUpdates()

      // Changes to top-level XBL bindings
      if (xblChanges.nonEmpty)
        processXBLUpdates()
    }

  private def processFullUpdate(create: Boolean, boundElem: VirtualNode): Unit = {
    fullUpdateChange = false
    xblChanges.clear()
    bindChanges.clear()
{'fraction_non_alphanumeric': 0.08240534521158129, 'fraction_numerical': 0.0033853006681514477, 'mean_word_length': 4.206864564007422, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 4}, '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': '5222096', 'n_tokens_mistral': 3309, 'n_tokens_neox': 3221, 'n_words': 985}