All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.cfg.BindingProperty Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 2008 Google Inc.
 *
 * Licensed 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 com.google.gwt.dev.cfg;

import com.google.gwt.core.ext.linker.PropertyProviderGenerator;
import com.google.gwt.dev.util.collect.IdentityHashSet;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.dev.util.collect.Sets;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.regex.Pattern;

/**
 * Represents a single named deferred binding or configuration property that can
 * answer with its value. The BindingProperty maintains two sets of values, the
 * "defined" set and the "allowed" set. The allowed set must always be a subset
 * of the defined set.
 */
public class BindingProperty extends Property {
  public static final String GLOB_STAR = "*";
  private static final String EMPTY = "";

  private List> collapsedValues = Lists.create();
  private final Map> conditionalValues = new LinkedHashMap>();
  private final SortedSet definedValues = new TreeSet();
  private String fallback;
  private HashMap>> fallbackValueMap;
  private HashMap> fallbackValues = new HashMap>();
  private PropertyProvider provider;
  private Class providerGenerator;
  private final ConditionAll rootCondition = new ConditionAll();

  {
    conditionalValues.put(rootCondition, new TreeSet());
  }

  public BindingProperty(String name) {
    super(name);
    fallback = EMPTY;
  }

  /**
   * Add an equivalence set of property values.
   */
  public void addCollapsedValues(String... values) {

    // Sanity check caller
    for (String value : values) {
      if (value.contains(GLOB_STAR)) {
        // Expanded in normalizeCollapsedValues()
        continue;
      } else if (!definedValues.contains(value)) {
        throw new IllegalArgumentException(
            "Attempting to collapse unknown value " + value);
      }
    }

    // We want a mutable set, because it simplifies normalizeCollapsedValues
    SortedSet temp = new TreeSet(Arrays.asList(values));
    collapsedValues = Lists.add(collapsedValues, temp);
  }

  public void addDefinedValue(Condition condition, String newValue) {
    definedValues.add(newValue);
    SortedSet set = conditionalValues.get(condition);
    if (set == null) {
      set = new TreeSet();
      set.addAll(conditionalValues.get(rootCondition));
      conditionalValues.put(condition, set);
    }
    set.add(newValue);
  }

  /**
   * Adds fall back value to given property name.
   * @param value the property value.
   * @param fallbackValue the fall back value for given property value.
   */
  public void addFallbackValue(String value, String fallbackValue) {
    LinkedList values = fallbackValues.get(fallbackValue);
    if (values == null) {
      values = new LinkedList();
      fallbackValues.put(fallbackValue, values);
    }
    values.addFirst(value);
  }

  /**
   * Returns the set of allowed values in sorted order when a certain condition
   * is satisfied.
   */
  public String[] getAllowedValues(Condition condition) {
    Set allowedValues = conditionalValues.get(condition);
    return allowedValues.toArray(new String[allowedValues.size()]);
  }

  public List> getCollapsedValues() {
    return collapsedValues;
  }

  public Map> getConditionalValues() {
    return Collections.unmodifiableMap(conditionalValues);
  }

  /**
   * If the BindingProperty has exactly one value across all conditions and
   * permutations, return that value otherwise return null.
   */
  public String getConstrainedValue() {
    String constrainedValue = null;
    for (SortedSet allowedValues : conditionalValues.values()) {
      if (allowedValues.size() != 1) {
        return null;
      } else if (constrainedValue == null) {
        constrainedValue = allowedValues.iterator().next();
      } else if (!constrainedValue.equals(allowedValues.iterator().next())) {
        return null;
      }
    }
    return constrainedValue;
  }

  /**
   * Returns the set of defined values in sorted order.
   */
  public String[] getDefinedValues() {
    return definedValues.toArray(new String[definedValues.size()]);
  }

  /**
   * Returns the fallback value for this property, or the empty string if none.
   *
   * @return the fallback value
   */
  public String getFallback() {
    return fallback;
  }

  /**
   * Returns the map of values to fall back values. the list of fall
   * back values is in decreasing order of preference.
   * @return map of property value to fall back values.
   */
  public Map>> getFallbackValuesMap() {
    if (fallbackValueMap == null) {
      HashMap>> valuesMap = new HashMap>>();
      // compute closure of fall back values preserving order
      for (Entry> e : fallbackValues.entrySet()) {
        String from = e.getKey();
        LinkedList> alternates = new LinkedList>();
        valuesMap.put(from, alternates);
        LinkedList childList = fallbackValues.get(from);
        LinkedHashSet children = new LinkedHashSet();
        children.addAll(childList);
        while (children != null && children.size() > 0) {
          alternates.add(children);
          LinkedHashSet newChildren = new LinkedHashSet();
          for (String child : children) {
            childList = fallbackValues.get(child);
            if (null == childList) {
              continue;
            }
            for (String val : childList) {
              newChildren.add(val);
            }
          }
          children = newChildren;
        }
      }
      fallbackValueMap = valuesMap;
    }
    return fallbackValueMap;
  }

  /**
   * Returns the first legal value for this property that can be found.
   */
  public String getFirstLegalValue() {
    if (getConstrainedValue() != null) {
      return getConstrainedValue();
    } else if (getDefinedValues().length > 0) {
      return getDefinedValues()[0];
    }
    return fallback;
  }

  public PropertyProvider getProvider() {
    return provider;
  }

  /**
   * @return the provider generator class, or null if none.
   */
  public Class getProviderGenerator() {
    return providerGenerator;
  }

  public Set getRequiredProperties() {
    Set toReturn = Sets.create();
    for (Condition cond : conditionalValues.keySet()) {
      toReturn = Sets.addAll(toReturn, cond.getRequiredProperties());
    }
    return toReturn;
  }

  public ConditionAll getRootCondition() {
    return rootCondition;
  }

  /**
   * Returns true if the supplied value is legal under some condition.
   */
  public boolean isAllowedValue(String value) {
    for (Set values : conditionalValues.values()) {
      if (values.contains(value)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns true if the value was previously provided to
   * {@link #addDefinedValue(Condition,String)}.
   */
  public boolean isDefinedValue(String value) {
    return definedValues.contains(value);
  }

  /**
   * Returns true if the value of this BindingProperty is always
   * derived from other BindingProperties. That is, for each Condition in the
   * BindingProperty, there is exactly one allowed value.
   */
  public boolean isDerived() {
    for (Set allowedValues : conditionalValues.values()) {
      if (allowedValues.size() != 1) {
        return false;
      }
    }
    return true;
  }

  /**
   * Set the currently allowed values. The values provided must be a subset of
   * the currently-defined values.
   *
   * @throws IllegalArgumentException if any of the provided values were not
   *     provided to {@link #addDefinedValue(Condition,String)}.
   */
  public void setAllowedValues(Condition condition, String... values) {
    SortedSet temp = new TreeSet(Arrays.asList(values));
    if (!definedValues.containsAll(temp)) {
      throw new IllegalArgumentException(
          "Attempted to set an allowed value that was not previously defined");
    }

    // XML has a last-one-wins semantic which we reflect in our evaluation order
    if (condition == rootCondition) {
      /*
       * An unconditional set-property would undo any previous conditional
       * setters, so we can just clear out this map.
       */
      conditionalValues.clear();
    } else {
      /*
       * Otherwise, we'll just ensure that this condition is moved to the end.
       */
      conditionalValues.remove(condition);
    }
    conditionalValues.put(condition, temp);
  }

  public void setFallback(String token) {
    fallback = token;
  }

  public void setProvider(PropertyProvider provider) {
    this.provider = provider;
  }

  /**
   * Set a provider generator for this property.
   *
   * @param generator
   */
  public void setProviderGenerator(Class generator) {
    providerGenerator = generator;
  }

  /**
   * Create a minimal number of equivalence sets, expanding any glob patterns.
   */
  void normalizeCollapsedValues() {
    if (collapsedValues.isEmpty()) {
      return;
    }

    // Expand globs
    for (Set set : collapsedValues) {
      // Compile a regex that matches all glob expressions that we see
      StringBuilder pattern = new StringBuilder();
      for (Iterator it = set.iterator(); it.hasNext();) {
        String value = it.next();
        if (value.contains(GLOB_STAR)) {
          it.remove();
          if (pattern.length() > 0) {
            pattern.append("|");
          }

          // a*b ==> (a.*b)
          pattern.append("(");
          // We know value is a Java ident, so no special escaping is needed
          pattern.append(value.replace(GLOB_STAR, ".*"));
          pattern.append(")");
        }
      }

      if (pattern.length() == 0) {
        continue;
      }

      Pattern p = Pattern.compile(pattern.toString());
      for (String definedValue : definedValues) {
        if (p.matcher(definedValue).matches()) {
          set.add(definedValue);
        }
      }
    }

    // Minimize number of sets

    // Maps a value to the set that contains that value
    Map> map = new HashMap>();

    // For each equivalence set we have
    for (SortedSet set : collapsedValues) {
      // Examine each original value in the set
      for (String value : new LinkedHashSet(set)) {
        // See if the value was previously assigned to another set
        SortedSet existing = map.get(value);
        if (existing == null) {
          map.put(value, set);
        } else {
          // If so, merge the existing set into this one and update pointers
          set.addAll(existing);
          for (String mergedValue : existing) {
            map.put(mergedValue, set);
          }
        }
      }
    }

    // The values of the maps will now contain the minimal number of sets
    collapsedValues = new ArrayList>(
        new IdentityHashSet>(map.values()));

    // Sort the list
    Lists.sort(collapsedValues, new Comparator>() {
      public int compare(SortedSet o1, SortedSet o2) {
        String s1 = o1.toString();
        String s2 = o2.toString();
        assert !s1.equals(s2) : "Should not have seen equal sets";
        return s1.compareTo(s2);
      }
    });
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy