This page describes how to add a map binding prefix to Tapestry 5.

The map binding prefix allows you to specify a map like this:

${map:key1=value1,key2=value2} or <t:component t:parameter="map:key1=value1,key2=value2"/>

This will create a Map<String, Object> with key1 and key2 as keys and the values of the bindings value1 and value2 as values.

First create the MapBinding:

public class MapBinding extends AbstractBinding {

  private final Map<String, Binding> delegates;
  private final boolean invariant;

  public MapBinding(Map<String, Binding> delegates, boolean invariant) {
    this.delegates = delegates;
    this.invariant = invariant;
  }

  public Object get() {
    Map<String, Object> values = new HashMap<String, Object>(delegates.size());

    for (Map.Entry<String, Binding> entry : delegates.entrySet()) {
      values.put(entry.getKey(), entry.getValue().get());
    }

    return values;
  }

  public boolean isInvariant() {
    return invariant;
  }

  public Class<Map> getBindingType() {
    return Map.class;
  }
}

Then create the MapBindingFactory:

/**
 * Factory for map bindings. Map bindings parse comma-delimited lists of key=value pairs into {@link Map maps}. Keys are
 * always string literals while the values can be any binding expression, for which the default prefix is prop.
 */
public class MapBindingFactory implements BindingFactory {

  private final BindingSource bindingSource;

  public MapBindingFactory(BindingSource source) {
    this.bindingSource = source;
  }

  public Binding newBinding(String description, ComponentResources container, ComponentResources component,
                            String expression, Location location) {
    Map<String, Binding> delegates = new HashMap<String, Binding>();
    String[] entries = expression.split(",");
    boolean invariant = true;

    for (String entry : entries) {
      String[] parts = entry.split("=");

      if (parts.length != 2) throw new RuntimeException(String.format("Entry '%s' is malformed", entry));

      String name = parts[0];
      String value = parts[1];

      Binding binding = bindingSource.newBinding(description, container, component,
                                                 BindingConstants.PROP, value, location);
      invariant = invariant && binding.isInvariant();
      delegates.put(name, binding);
    }

    return new MapBinding(delegates, invariant);
  }
}

And then contribute it to the BindingSource configuration:

  public static void contributeBindingSource(MappedConfiguration<String, BindingFactory> configuration,
                                             BindingSource bindingSource) {
    configuration.add("map", new MapBindingFactory(bindingSource));
  }

Again, the keys are always string literals and the values themselves can be any binding expression, for which the default binding prefix is prop.

That's all there is to it.

  • No labels