gt:         if (! deferRRR) {
context: /**
   * Copyright (C) 2010 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;
  
  import org.dom4j.Element;
  import org.orbeon.oxf.common.OXFException;
  import org.orbeon.oxf.common.OrbeonLocationException;
  import org.orbeon.oxf.common.ValidationException;
  import org.orbeon.oxf.pipeline.api.ExternalContext;
  import org.orbeon.oxf.processor.ProcessorImpl;
  import org.orbeon.oxf.util.*;
  import org.orbeon.oxf.xforms.analysis.ElementAnalysis;
  import org.orbeon.oxf.xforms.analysis.model.Instance;
  import org.orbeon.oxf.xforms.analysis.model.Model;
  import org.orbeon.oxf.xforms.analysis.model.Submission;
  import org.orbeon.oxf.xforms.control.Controls;
  import org.orbeon.oxf.xforms.event.*;
  import org.orbeon.oxf.xforms.event.events.*;
  import org.orbeon.oxf.xforms.model.RuntimeBind;
  import org.orbeon.oxf.xforms.model.XFormsModelAction;
  import org.orbeon.oxf.xforms.state.InstanceState;
  import org.orbeon.oxf.xforms.submission.BaseSubmission;
  import org.orbeon.oxf.xforms.submission.SubmissionUtils;
  import org.orbeon.oxf.xforms.submission.XFormsModelSubmission;
  import org.orbeon.oxf.xforms.xbl.Scope;
  import org.orbeon.oxf.xforms.xbl.XBLContainer;
  import org.orbeon.oxf.xml.TransformerUtils;
  import org.orbeon.oxf.xml.dom4j.ExtendedLocationData;
  import org.orbeon.oxf.xml.dom4j.LocationData;
  import org.orbeon.saxon.om.*;
  import org.orbeon.saxon.trans.XPathException;
  import org.orbeon.saxon.value.Value;
  import scala.Option;
  
  import java.io.InputStream;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.util.*;
  
  /**
   * Represents an XForms model.
   */
  public class XFormsModel extends XFormsModelBase implements XFormsEventObserver, XFormsObjectResolver {
  
      public static final String LOGGING_CATEGORY = "model";
  
      // Static representation of this model
      public final Model staticModel;
  
      // Model attributes
      private String effectiveId; // not final because can change if model within repeat iteration
  
      // Instances
      private final List<String> instanceIds;
      private final List<XFormsInstance> instances;
      private final Map<String, XFormsInstance> instancesMap;
  
      // Submissions and actions
      private final Map<String, XFormsModelSubmission> submissions;
      private final Map<String, XFormsModelAction> actions = new HashMap<String, XFormsModelAction>();
  
      // Context and variables
      private BindingContext defaultEvaluationContext;
      private Map<String, ValueRepresentation> topLevelVariables = new LinkedHashMap<String, ValueRepresentation>();
  
      // Binds
      private final XFormsModelBinds _binds;
      private final boolean _mustBindValidate;
  
      public boolean mustBindValidate() { return _mustBindValidate; }
  
      // Container
      private final XBLContainer container;
      private final XFormsContextStack contextStack;    // context stack for evaluation, used by binds, submissions, event handlers
  
      public XFormsModel(XBLContainer container, String effectiveId, Model staticModel) {
          super(container, effectiveId, staticModel);
  
          // Remember static model
          this.staticModel = staticModel;
  
          // Set container
          this.container = container;
  
          this.effectiveId = effectiveId;
  
          // Extract list of instances ids
          {
              final Collection<Instance> staticInstances = staticModel.instancesMap().values();
              if (staticInstances.isEmpty()) {
                  // No instance in this model
                  instanceIds = Collections.emptyList();
                  instances = Collections.emptyList();
                  instancesMap = Collections.emptyMap();
              } else {
                  // At least one instance in this model
                  instanceIds = new ArrayList<String>(staticInstances.size());
                  for (final Instance instance : staticInstances)
                      instanceIds.add(instance.staticId());
                  instances = Arrays.asList(new XFormsInstance[staticInstances.size()]);
                  instancesMap = new HashMap<String, XFormsInstance>(staticInstances.size());
              }
          }
  
          // Get submissions
          {
              final List<Submission> staticSubmissions = staticModel.jSubmissions();
              if (staticSubmissions.isEmpty()) {
                  // No submission in this model
                  submissions = Collections.emptyMap();
              } else {
                  // At least one submission in this model
                  submissions = new HashMap<String, XFormsModelSubmission>();
                  for (final Submission staticSubmission : staticSubmissions)
                      submissions.put(staticSubmission.staticId(), new XFormsModelSubmission(this.container, staticSubmission, this));
              }
          }
  
          // Get all event handlers
          for (final EventHandlerImpl staticEventHandler : staticModel.jEventHandlers()) {
              final ElementAnalysis staticParent = staticEventHandler.parent().get();
  
              final XFormsEventObserver parent;
              if (staticParent instanceof Submission) {
                  parent = submissions.get(staticParent.staticId());
              } else {
                  parent = XFormsModel.this;
              }
  
              actions.put(staticEventHandler.staticId(), new XFormsModelAction(parent, staticEventHandler));
          }
  
          // Create binds object
          _binds = XFormsModelBinds.create(this);
          _mustBindValidate = _binds != null;
  
          // Create context stack
          this.contextStack = new XFormsContextStack(container());
  
          // Temporarily initialize the evaluation context to an empty context, so that handlers upon xforms-model-construct can work
          this.defaultEvaluationContext = XFormsContextStack.defaultContext(null, container, this);
      }
  
      // Evaluate all top-level variables
      public void resetAndEvaluateVariables() {
  
          // NOTE: This method is called during RRR and by submission processing. Need to do dependency handling.
  
          // Reset context to this model, including evaluating the model variables
          contextStack.resetBindingContext(this);
  
          // Remember context and variables
          defaultEvaluationContext = contextStack.getCurrentBindingContext();
      }
  
      // Return the value of the given model variable
      public SequenceIterator getVariable(String variableName) throws XPathException {
          return Value.asIterator(topLevelVariables.get(variableName));
      }
  
      public void updateEffectiveId(String effectiveId) {
          this.effectiveId = effectiveId;
      }
  
      public String getPrefixedId() {
          return staticModel.prefixedId();
      }
  
      public IndentedLogger getIndentedLogger() {
          return indentedLogger();
      }
  
      public XFormsContextStack getContextStack() {
          return contextStack;
      }
  
      public XBLContainer container() {
          return container;
      }
  
      public Model getStaticModel() {
          return staticModel;
      }
  
      /**
       * Get object with the id specified.
       */
      public XFormsObject getObjectByEffectiveId(String effectiveId) {
  
          // If prefixes or suffixes don't match, object can't be found here
          if (!container().getFullPrefix().equals(XFormsUtils.getEffectiveIdPrefix(effectiveId))
                  || !XFormsUtils.getEffectiveIdSuffix(container().getEffectiveId()).equals(XFormsUtils.getEffectiveIdSuffix(effectiveId))) {
              return null;
          }
  
          // Find by static id
          return resolveObjectById(null, XFormsUtils.getStaticIdFromId(effectiveId), null);
      }
  
      /**
       * Resolve an object. This optionally depends on a source, and involves resolving whether the source is within a
       * repeat or a component.
       *
       * @param sourceEffectiveId  effective id of the source, or null
       * @param targetStaticId     static id of the target
       * @param contextItem        context item, or null (used for bind resolution only)
       * @return                   object, or null if not found
       */
      public XFormsObject resolveObjectById(String sourceEffectiveId, String targetStaticId, Item contextItem) {
  
          if (XFormsUtils.isEffectiveId(targetStaticId) || XFormsUtils.isAbsoluteId(targetStaticId))
              throw new OXFException("Target id must be static id: " + targetStaticId);
  
          // Check this id
          if (targetStaticId.equals(getId()))
              return this;
  
          // Search instances
          final XFormsInstance instance = instancesMap.get(targetStaticId);
          if (instance != null)
              return instance;
  
          // Search submissions
          if (submissions != null) {
              final XFormsModelSubmission resultSubmission = submissions.get(targetStaticId);
              if (resultSubmission != null)
                  return resultSubmission;
          }
  
          // Search actions
          {
              final XFormsModelAction action = actions.get(targetStaticId);
              if (action != null)
                  return action;
          }
  
          // Search binds
          if (_binds != null) {
              final RuntimeBind bind = _binds.resolveBind(targetStaticId, contextItem);
              if (bind != null)
                  return bind;
          }
  
          return null;
      }
  
      /**
       * Return the default instance for this model, i.e. the first instance. Return null if there is
       * no instance in this model.
       *
       * @return  XFormsInstance or null
       */
      public XFormsInstance getDefaultInstance() {
          return ! instances.isEmpty() ? instances.get(0) : null;
      }
  
      /**
       * Return all XFormsInstance objects for this model, in the order they appear in the model.
       */
      public List<XFormsInstance> getInstances() {
          return instances;
      }
  
      /**
       * Return the XFormsInstance with given id, null if not found.
       */
      public XFormsInstance getInstance(String instanceStaticId) {
          return instancesMap.get(instanceStaticId);
      }
  
      /**
       * Return the XFormsInstance object containing the given node.
       */
      public XFormsInstance getInstanceForNode(NodeInfo nodeInfo) {
  
          final DocumentInfo documentInfo = nodeInfo.getDocumentRoot();
  
          // NOTE: We shouldn't even be called if the parent control is not relevant.
          if (container.isRelevant()) {
              for (final XFormsInstance currentInstance: instances) {
                  if (currentInstance.documentInfo().isSameNodeInfo(documentInfo))
                      return currentInstance;
              }
          }
  
          return null;
      }
  
      /**
       * Set an instance. The id of the instance must exist in the model.
       */
      public void indexInstance(XFormsInstance instance) {
          final String instanceId = instance.instance().staticId();
          final int instancePosition = instanceIds.indexOf(instanceId);
          instances.set(instancePosition, instance);
          instancesMap.put(instanceId, instance);
      }
  
      public String getId() {
          return staticModel.staticId();
      }
  
      public String getEffectiveId() {
          return effectiveId;
      }
  
      public Scope scope() {
          return staticModel.scope();
      }
  
      public Scope getResolutionScope() {
          return container.getPartAnalysis().scopeForPrefixedId(getPrefixedId());
      }
  
      public LocationData getLocationData() {
          return staticModel.locationData();
      }
  
      public XFormsModelBinds getBinds() {
          return _binds;
      }
  
      /**
       * Restore the state of the model when the model object was just recreated.
       */
      public void restoreState(boolean deferRRR) {
          // Ensure schema are loaded
          schemaValidator();
  
          // Refresh binds, but do not recalculate (only evaluate "computed expression binds")
          // TODO: We used to not redo recalculations upon state restore. Does this cause a problem? Shouldn't
          // recalculations not depend on the number of times they run anyway?
          deferredActionContext().markStructuralChange();
  
  