Package org.waveprotocol.wave.model.experimental.schema

Source Code of org.waveprotocol.wave.model.experimental.schema.IntermediateSchemaFragment$IntermediatePrologueEntry

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.waveprotocol.wave.model.experimental.schema;

import org.waveprotocol.wave.model.experimental.schema.SchemaPattern.Prologue;
import org.waveprotocol.wave.model.experimental.schema.SchemaPattern.PrologueEntry;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

/**
* An intermediate representation for the process of constructing a
* <code>SchemaPattern</code> from a schema document.
*
*/
final class IntermediateSchemaFragment {

  /*
   * A note on how a schema pattern is represented and how the compilation to a
   * schema pattern works:
   *
   * A schema pattern is represented as a graph of references between different
   * pattern objects, with each object representing some subpattern of the full
   * pattern. Each schema pattern contains information about its prologue,
   * non-prologue elements, allowed attributes, and allowed character data. The
   * prologue's pattern is stored as a list, where each item in the list
   * corresponds to an element in the prologue and contains a reference to a
   * schema pattern for that element. The information corresponding to
   * non-prologue child elements is stored as a map from the element type to the
   * schema pattern object that corresponds to that element type.
   *
   * Compilation is split into two stages.
   *
   * In the first stage, only a partial compile (of just the top-level
   * structure) of each definition is performed. The compilation of the patterns
   * corresponding to both prologue and non-prologue child elements of the
   * definition is deferred (but their pattern objects are created and remain
   * uninitialized at this stage). This creates object references to schema
   * pattern objects required by a later full compile and also needed by the
   * partial top-level compiles of other definitions. Any circular references
   * encountered in this first stage will indicate a bad infinite recursion, and
   * should trigger an <code>InvalidSchemaException</code>.
   *
   * In the second stage of the compilation, all deferred compilation is brought
   * to completion. Any circular references encountered in this second stage are
   * the result of safe recursion.
   */

  /**
   * An intermediate format for a prologue entry.
   */
  static final class IntermediatePrologueEntry {

    private final String type;
    private final IntermediateSchemaFragment fragment;

    IntermediatePrologueEntry(String type, IntermediateSchemaFragment fragment) {
      this.type = type;
      this.fragment = fragment;
    }

    String elementType() {
      return type;
    }

    IntermediateSchemaFragment fragment() {
      return fragment;
    }

  }

  /**
   * An intermediate format for a fragment of a prologue, containing a sequence
   * of adjacent prologue entries.
   */
  abstract static class IntermediatePrologueFragment {

    /**
     * Performs a partial compile of the top-level of the prologue fragment,
     * creating a list of prologue entries in the fragment which pairs element
     * types with their uninitialized schema patterns (whose full compilation
     * has been deferred).
     *
     * @param state the state used by the compiler
     * @return the list of prologue entries, where the compilation of each
     *         entry's schema pattern has been deferred
     */
    abstract List<PrologueEntry> resolveTopLevel(CompilerState state) throws InvalidSchemaException;

    /**
     * Performs a full compile of the prologue fragment, returning a list of
     * prologue entries representing the content of the prologue fragment.
     *
     * @param state the state used by the compiler
     * @return the list of prologue entries
     */
    abstract List<PrologueEntry> compile(CompilerState state) throws InvalidSchemaException;

  }

  /**
   * A prologue fragment arising from the direct specification of the fragment's
   * content (as opposed to through referencing a definition).
   */
  static final class DirectPrologueFragment extends IntermediatePrologueFragment {

    private final List<IntermediatePrologueEntry> prologueEntries;

    DirectPrologueFragment(List<IntermediatePrologueEntry> prologueEntries) {
      this.prologueEntries = prologueEntries;
    }

    @Override
    List<PrologueEntry> resolveTopLevel(CompilerState state) {
      List<PrologueEntry> resolved = new ArrayList<PrologueEntry>();
      for (IntermediatePrologueEntry entry : prologueEntries) {
        SchemaPattern pattern = new SchemaPattern();
        IntermediateSchemaFragment fragment = entry.fragment();
        resolved.add(new PrologueEntry(entry.elementType(), pattern));
        state.deferredCompiles.add(new DeferredCompile(fragment, pattern));
      }
      return resolved;
    }

    @Override
    List<PrologueEntry> compile(CompilerState state) throws InvalidSchemaException {
      List<PrologueEntry> compiled = new ArrayList<PrologueEntry>();
      for (IntermediatePrologueEntry entry : prologueEntries) {
        compiled.add(new PrologueEntry(entry.type, entry.fragment().compile(state)));
      }
      return compiled;
    }

  }

  /**
   * A prologue fragment arising from referencing a definition.
   */
  static final class ReferencePrologueFragment extends IntermediatePrologueFragment {

    private final String reference;

    ReferencePrologueFragment(String reference) {
      this.reference = reference;
    }

    @Override
    List<PrologueEntry> resolveTopLevel(CompilerState state) throws InvalidSchemaException {
      IntermediateSchemaFragment fragment = state.lookupDefinition(reference);
      return fragment.resolveTopLevel(reference, state).pattern.prologue();
    }

    @Override
    List<PrologueEntry> compile(CompilerState state) {
      return state.references.get(reference).pattern.prologue();
    }

  }

  /**
   * Accumulates attribute patterns, which can then be used to construct a
   * <code>AttributesValidator</code>.
   */
  static final class AttributesPatternBuilder {

    // NOTE: This could be optimized with a trie.
    private final Set<String> requiredAttributes =
        new TreeSet<String>();

    // NOTE: This could be optimized with a trie.
    private final Map<String, String> attributeValuePatterns =
        new TreeMap<String, String>();

    void addRequired(String attributeName) {
      requiredAttributes.add(attributeName);
    }

    void addValuePattern(String attributeName, String values) throws InvalidSchemaException {
      if (attributeValuePatterns.containsKey(attributeName)) {
        throw new InvalidSchemaException(
            "Attribute pattern defined more than once: " + attributeName);
      }
      attributeValuePatterns.put(attributeName, values);
    }

    void importFrom(AttributesPatternBuilder other) throws InvalidSchemaException {
      requiredAttributes.addAll(other.requiredAttributes);
      for (Map.Entry<String, String> entry : other.attributeValuePatterns.entrySet()) {
        String key = entry.getKey();
        if (attributeValuePatterns.containsKey(key)) {
          throw new InvalidSchemaException("Attribute pattern defined more than once: " + key);
        }
        attributeValuePatterns.put(key, entry.getValue());
      }
    }

    AttributesValidator createValidator() {
      // NOTE: This could be optimized with a trie.
      Map<String, ValueValidator> attributeValidators = new TreeMap<String, ValueValidator>();
      for (Map.Entry<String, String> entry : attributeValuePatterns.entrySet()) {
        String name = entry.getKey();
        String values = entry.getValue();
        attributeValidators.put(name, ValueValidator.fromRegex(values));
      }
      return new AttributesValidator(requiredAttributes, attributeValidators);
    }

  }

  /**
   * Accumulates characters, which can then be used to construct a <code>CharacterValidator</code>.
   */
  static final class CharacterPatternBuilder {

    private Set<Integer> characters = new TreeSet<Integer>();

    private boolean blacklistCharacters = false;

    void whitelistCharacters(Collection<Integer> chars) {
      if (blacklistCharacters) {
        characters.removeAll(chars);
      } else {
        characters.addAll(chars);
      }
    }

    void blacklistCharacters(Collection<Integer> chars) {
      Set<Integer> oldCharacters = characters;
      characters = new TreeSet<Integer>();
      if (blacklistCharacters) {
        for (int character : chars) {
          if (oldCharacters.contains(character)) {
            characters.add(character);
          }
        }
      } else {
        for (int character : chars) {
          if (!oldCharacters.contains(character)) {
            characters.add(character);
          }
        }
        blacklistCharacters = true;
      }
    }

    void importFrom(CharacterPatternBuilder other) {
      if (other.blacklistCharacters) {
        blacklistCharacters(other.characters);
      } else {
        whitelistCharacters(other.characters);
      }
    }

    CharacterValidator createValidator() {
      return blacklistCharacters
          ? CharacterValidator.disallowedCharacters(characters)
          : CharacterValidator.allowedCharacters(characters);
    }

  }

  /**
   * A container for the information needed to perform the deferred compilation
   * of <code>IntermediateSchemaFragment</code>.
   */
  private static final class DeferredCompile {

    private final IntermediateSchemaFragment source;
    private final SchemaPattern target;

    DeferredCompile(IntermediateSchemaFragment source, SchemaPattern target) {
      this.source = source;
      this.target = target;
    }

  }

  /**
   * The result of resolving a reference.
   */
  private static final class ReferenceData {

    final SchemaPattern pattern;
    final Map<String, SchemaPattern> potentialChildren;
    final AttributesPatternBuilder attributesPattern;
    final CharacterPatternBuilder characterPattern;

    ReferenceData(SchemaPattern pattern, Map<String, SchemaPattern> potentialChildren,
        AttributesPatternBuilder attributesPattern, CharacterPatternBuilder characterPattern) {
      this.pattern = pattern;
      this.potentialChildren = potentialChildren;
      this.attributesPattern = attributesPattern;
      this.characterPattern = characterPattern;
    }

  }

  /**
   * The general storage container for state needed by the compiler to function.
   */
  private static final class CompilerState {

    final Map<String, IntermediateSchemaFragment> definitions;
    final Map<String, ReferenceData> references = new TreeMap<String, ReferenceData>();
    final Set<String> undergoingResolution = new TreeSet<String>();
    final List<DeferredCompile> deferredCompiles = new ArrayList<DeferredCompile>();

    CompilerState(Map<String, IntermediateSchemaFragment> definitions) {
      this.definitions = definitions;
    }

    IntermediateSchemaFragment lookupDefinition(String name) throws InvalidSchemaException {
      IntermediateSchemaFragment result = definitions.get(name);
      if (result == null) {
        throw new InvalidSchemaException("Reference to undefined pattern definition: " + name);
      }
      return result;
    }

  }

  private final AttributesPatternBuilder attributesPattern;
  private final List<IntermediatePrologueFragment> prologue;
  private final Map<String, IntermediateSchemaFragment> allowedChildren;
  private final Set<String> references;
  private final CharacterPatternBuilder characterPattern;

  IntermediateSchemaFragment(
      AttributesPatternBuilder attributesPattern,
      List<IntermediatePrologueFragment> prologue,
      Map<String, IntermediateSchemaFragment> allowedChildren,
      Set<String> references,
      CharacterPatternBuilder characterPattern) {
    this.attributesPattern = attributesPattern;
    this.prologue = prologue;
    this.allowedChildren = allowedChildren;
    this.references = references;
    this.characterPattern = characterPattern;
  }

  private void importReferences(Map<String, SchemaPattern> allowedChildrenMap, CompilerState state)
      throws InvalidSchemaException {
    for (String ref : references) {
      ReferenceData refData = state.lookupDefinition(ref).resolveTopLevel(ref, state);
      characterPattern.importFrom(refData.characterPattern);
      attributesPattern.importFrom(refData.attributesPattern);
      for (Map.Entry<String, SchemaPattern> entry : refData.potentialChildren.entrySet()) {
        String key = entry.getKey();
        if (allowedChildrenMap.containsKey(key)) {
          throw new InvalidSchemaException(
              "Subpattern defined more than once for element type: " + key);
        }
        allowedChildrenMap.put(key, entry.getValue());
      }
    }
  }

  /**
   * Performs a partial compile of just the top level of a referenced definition
   * (which is represented by this object).
   *
   * @param reference the name of the definition which this object represents
   * @param state state needed by the compiler
   * @return the result of the partial compilation
   */
  private ReferenceData resolveTopLevel(String reference, CompilerState state)
      throws InvalidSchemaException {
    if (state.undergoingResolution.contains(reference)) {
      throw new InvalidSchemaException("Infinite recursion encountered");
    }
    ReferenceData referenceData = state.references.get(reference);
    if (referenceData != null) {
      return referenceData;
    }
    state.undergoingResolution.add(reference);
    /*
     * NOTE: Is there a nicer way than mutating attributesPattern (and the other
     * fields) that is as efficient?
     */
    List<PrologueEntry> prologueEntries = new ArrayList<PrologueEntry>();
    Map<String, SchemaPattern> allowedChildrenMap = new TreeMap<String, SchemaPattern>();
    for (Map.Entry<String, IntermediateSchemaFragment> entry : allowedChildren.entrySet()) {
      String key = entry.getKey();
      SchemaPattern pattern = new SchemaPattern();
      allowedChildrenMap.put(key, pattern);
      state.deferredCompiles.add(new DeferredCompile(entry.getValue(), pattern));
    }
    importReferences(allowedChildrenMap, state);
    for (IntermediatePrologueFragment prologueFragment : prologue) {
      // TODO(user): Concatenating prologues can by slightly optimized here.
      prologueEntries.addAll(prologueFragment.resolveTopLevel(state));
    }
    SchemaPattern compiledPattern = new SchemaPattern();
    compiledPattern.initialize(attributesPattern.createValidator(), new Prologue(prologueEntries),
        allowedChildrenMap, characterPattern.createValidator());
    state.undergoingResolution.remove(reference);
    referenceData = new ReferenceData(compiledPattern, allowedChildrenMap, attributesPattern,
        characterPattern);
    state.references.put(reference, referenceData);
    return referenceData;
  }

  /**
   * Compiles this intermediate fragment into the given uninitialized pattern object.
   *
   * @param pattern an uninitialized pattern object into which to compile this intermediate fragment
   * @param state state needed by the compiler
   */
  private void compileTo(SchemaPattern pattern, CompilerState state) throws InvalidSchemaException {
    Map<String, SchemaPattern> allowedChildrenMap = new TreeMap<String, SchemaPattern>();
    for (Map.Entry<String, IntermediateSchemaFragment> entry : allowedChildren.entrySet()) {
      allowedChildrenMap.put(entry.getKey(), entry.getValue().compile(state));
    }
    importReferences(allowedChildrenMap, state);
    List<PrologueEntry> compiledPrologue = new ArrayList<PrologueEntry>();
    for (IntermediatePrologueFragment prologueFragment : prologue) {
      compiledPrologue.addAll(prologueFragment.compile(state));
    }
    pattern.initialize(attributesPattern.createValidator(), new Prologue(compiledPrologue),
        allowedChildrenMap, characterPattern.createValidator());
  }

  /**
   * Compiles this intermediate fragment into a new pattern object.
   *
   * @param state state needed by the compiler
   * @return a new pattern object
   */
  private SchemaPattern compile(CompilerState state) throws InvalidSchemaException {
    SchemaPattern pattern = new SchemaPattern();
    compileTo(pattern, state);
    return pattern;
  }

  /**
   * Compiles a map associating <code>IntermediateSchemaFragment</code>s with
   * definition names into a <code>SchemaPattern</code>.
   *
   * @param definitions a map from definition names to the intermediate
   *        representation of the pattern defined by the corresponding
   *        definition
   * @param root the root pattern
   * @return the schema pattern
   */
  static SchemaPattern compile(Map<String, IntermediateSchemaFragment> definitions, String root)
      throws InvalidSchemaException {
    CompilerState state = new CompilerState(definitions);
    for (Map.Entry<String, IntermediateSchemaFragment> definition : definitions.entrySet()) {
      definition.getValue().resolveTopLevel(definition.getKey(), state);
    }
    for (DeferredCompile deferredCompile : state.deferredCompiles) {
      deferredCompile.source.compileTo(deferredCompile.target, state);
    }
    ReferenceData rootReference = state.references.get(root);
    if (rootReference == null) {
      throw new InvalidSchemaException("Root not defined");
    }
    return rootReference.pattern;
  }

}
TOP

Related Classes of org.waveprotocol.wave.model.experimental.schema.IntermediateSchemaFragment$IntermediatePrologueEntry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.