beam-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bchamb...@apache.org
Subject [1/2] incubator-beam git commit: Additional APIs for registering DisplayData
Date Fri, 08 Apr 2016 16:58:37 GMT
Repository: incubator-beam
Updated Branches:
  refs/heads/master d827e1b96 -> 245cffa5e


Additional APIs for registering DisplayData

- Support overriding namespace when adding display data
- Support inferring display data type during registration
- Add APIs to conditionally register display data if not null/default.
- Move additional matchers into DisplayDataMatchers


Project: http://git-wip-us.apache.org/repos/asf/incubator-beam/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-beam/commit/5da22920
Tree: http://git-wip-us.apache.org/repos/asf/incubator-beam/tree/5da22920
Diff: http://git-wip-us.apache.org/repos/asf/incubator-beam/diff/5da22920

Branch: refs/heads/master
Commit: 5da22920c7c5d20695d4309429341c39af760044
Parents: d827e1b
Author: Scott Wegner <swegner@google.com>
Authored: Tue Apr 5 11:42:30 2016 -0700
Committer: bchambers <bchambers@google.com>
Committed: Fri Apr 8 09:46:07 2016 -0700

----------------------------------------------------------------------
 .../sdk/transforms/display/DisplayData.java     | 306 +++++++++++++++++--
 .../sdk/transforms/display/HasDisplayData.java  |   1 +
 .../transforms/display/DisplayDataMatchers.java |  43 +++
 .../display/DisplayDataMatchersTest.java        |  34 ++-
 .../sdk/transforms/display/DisplayDataTest.java | 206 ++++++++++---
 5 files changed, 517 insertions(+), 73 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-beam/blob/5da22920/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayData.java
----------------------------------------------------------------------
diff --git a/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayData.java
b/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayData.java
index 63ec7fc..d23fc0b 100644
--- a/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayData.java
+++ b/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayData.java
@@ -76,6 +76,30 @@ public class DisplayData {
     return InternalBuilder.forRoot(component).build();
   }
 
+  /**
+   * Infer the {@link Type} for the given object.
+   *
+   * <p>Use this method if the type of metadata is not known at compile time. For example:
+   *
+   * <pre>
+   * {@code
+   * @Override
+   * public void populateDisplayData(DisplayData.Builder builder) {
+   *   Optional<DisplayData.Type> type = DisplayData.inferType(foo);
+   *   if (type.isPresent()) {
+   *     builder.add("foo", type.get(), foo);
+   *   }
+   * }
+   * }
+   * </pre>
+   *
+   * @return The inferred {@link Type}, or null if the type cannot be inferred,
+   */
+  @Nullable
+  public static Type inferType(@Nullable Object value) {
+    return Type.tryInferFrom(value);
+  }
+
   public Collection<Item> items() {
     return entries.values();
   }
@@ -101,6 +125,10 @@ public class DisplayData {
     return builder.toString();
   }
 
+  private static String namespaceOf(Class<?> clazz) {
+    return clazz.getName();
+  }
+
   /**
    * Utility to build up display metadata from a component and its included
    * subcomponents.
@@ -127,6 +155,20 @@ public class DisplayData {
     ItemBuilder add(String key, String value);
 
     /**
+     * Register the given string display data if the value is not null.
+     *
+     * @see DisplayData.Builder#add(String, String)
+     */
+    ItemBuilder addIfNotNull(String key, @Nullable String value);
+
+    /**
+     * Register the given string display data if the value is different than the specified
default.
+     *
+     * @see DisplayData.Builder#add(String, String)
+     */
+    ItemBuilder addIfNotDefault(String key, @Nullable String value, @Nullable String defaultValue);
+
+    /**
      * Register the given numeric display metadata. The metadata item will be registered
with type
      * {@link DisplayData.Type#INTEGER}, and is identified by the specified key and namespace
from
      * the current transform or component.
@@ -134,6 +176,13 @@ public class DisplayData {
     ItemBuilder add(String key, long value);
 
     /**
+     * Register the given numeric display data if the value is different than the specified
default.
+     *
+     * @see DisplayData.Builder#add(String, long)
+     */
+    ItemBuilder addIfNotDefault(String key, long value, long defaultValue);
+
+    /**
      * Register the given floating point display metadata. The metadata item will be registered
with
      * type {@link DisplayData.Type#FLOAT}, and is identified by the specified key and namespace
      * from the current transform or component.
@@ -141,13 +190,28 @@ public class DisplayData {
     ItemBuilder add(String key, double value);
 
     /**
-     * Register the given floating point display metadata. The metadata item will be registered
with
+     * Register the given floating point display data if the value is different than the
specified
+     * default.
+     *
+     * @see DisplayData.Builder#add(String, double)
+     */
+    ItemBuilder addIfNotDefault(String key, double value, double defaultValue);
+
+    /**
+     * Register the given boolean display metadata. The metadata item will be registered
with
      * type {@link DisplayData.Type#BOOLEAN}, and is identified by the specified key and
namespace
      * from the current transform or component.
      */
     ItemBuilder add(String key, boolean value);
 
     /**
+     * Register the given boolean display data if the value is different than the specified
default.
+     *
+     * @see DisplayData.Builder#add(String, boolean)
+     */
+    ItemBuilder addIfNotDefault(String key, boolean value, boolean defaultValue);
+
+    /**
      * Register the given timestamp display metadata. The metadata item will be registered
with type
      * {@link DisplayData.Type#TIMESTAMP}, and is identified by the specified key and namespace
from
      * the current transform or component.
@@ -155,6 +219,22 @@ public class DisplayData {
     ItemBuilder add(String key, Instant value);
 
     /**
+     * Register the given timestamp display data if the value is not null.
+     *
+     * @see DisplayData.Builder#add(String, Instant)
+     */
+    ItemBuilder addIfNotNull(String key, @Nullable Instant value);
+
+    /**
+     * Register the given timestamp display data if the value is different than the specified
+     * default.
+     *
+     * @see DisplayData.Builder#add(String, Instant)
+     */
+    ItemBuilder addIfNotDefault(
+        String key, @Nullable Instant value, @Nullable Instant defaultValue);
+
+    /**
      * Register the given duration display metadata. The metadata item will be registered
with type
      * {@link DisplayData.Type#DURATION}, and is identified by the specified key and namespace
from
      * the current transform or component.
@@ -162,11 +242,53 @@ public class DisplayData {
     ItemBuilder add(String key, Duration value);
 
     /**
+     * Register the given duration display data if the value is not null.
+     *
+     * @see DisplayData.Builder#add(String, Duration)
+     */
+    ItemBuilder addIfNotNull(String key, @Nullable Duration value);
+
+    /**
+     * Register the given duration display data if the value is different than the specified
+     * default.
+     *
+     * @see DisplayData.Builder#add(String, Duration)
+     */
+    ItemBuilder addIfNotDefault(
+        String key, @Nullable Duration value, @Nullable Duration defaultValue);
+
+    /**
      * Register the given class display metadata. The metadata item will be registered with
type
      * {@link DisplayData.Type#JAVA_CLASS}, and is identified by the specified key and namespace
      * from the current transform or component.
      */
     ItemBuilder add(String key, Class<?> value);
+
+    /**
+     * Register the given class display data if the value is not null.
+     *
+     * @see DisplayData.Builder#add(String, Class)
+     */
+    ItemBuilder addIfNotNull(String key, @Nullable Class<?> value);
+
+    /**
+     * Register the given class display data if the value is different than the specified
default.
+     *
+     * @see DisplayData.Builder#add(String, Class)
+     */
+    ItemBuilder addIfNotDefault(
+        String key, @Nullable Class<?> value, @Nullable Class<?> defaultValue);
+
+  /**
+   * Register the given display metadata with the specified type.
+   *
+   * <p> The added display data is identified by the specified key and namespace from
the current
+   * transform or component.
+   *
+   * @throws ClassCastException if the value cannot be safely cast to the specified type.
+   * @see DisplayData#inferType(Object)
+   */
+    ItemBuilder add(String key, Type type, Object value);
   }
 
   /**
@@ -190,6 +312,14 @@ public class DisplayData {
      * <p>Specifying a null value will clear the URL if it was previously defined.
      */
     ItemBuilder withLinkUrl(@Nullable String url);
+
+    /**
+     * Adds an explicit namespace to the most-recently added display metadata. The namespace
+     * and key uniquely identify the display metadata.
+     *
+     * <p>Leaving the namespace unspecified will default to the registering instance's
class.
+     */
+    ItemBuilder withNamespace(Class<?> namespace);
   }
 
   /**
@@ -207,10 +337,11 @@ public class DisplayData {
     private final String label;
     private final String url;
 
-    private static <T> Item create(String namespace, String key, Type type, T value)
{
+    private static Item create(Class<?> nsClass, String key, Type type, Object value)
{
       FormattedItemValue formatted = type.format(value);
+      String namespace = namespaceOf(nsClass);
       return new Item(
-        namespace, key, type, formatted.getLongValue(), formatted.getShortValue(), null,
null);
+          namespace, key, type, formatted.getLongValue(), formatted.getShortValue(), null,
null);
     }
 
     private Item(
@@ -337,6 +468,12 @@ public class DisplayData {
     private Item withUrl(String url) {
       return new Item(this.ns, this.key, this.type, this.value, this.shortValue, url, this.label);
     }
+
+    private Item withNamespace(Class<?> nsClass) {
+      String namespace = namespaceOf(nsClass);
+      return new Item(
+          namespace, this.key, this.type, this.value, this.shortValue, this.url, this.label);
+    }
   }
 
   /**
@@ -354,7 +491,7 @@ public class DisplayData {
     private final String key;
 
     public static Identifier of(Class<?> namespace, String key) {
-      return of(namespace.getName(), key);
+      return of(namespaceOf(namespace), key);
     }
 
     public static Identifier of(String namespace, String key) {
@@ -403,13 +540,14 @@ public class DisplayData {
     STRING {
       @Override
       FormattedItemValue format(Object value) {
-        return new FormattedItemValue((String) value);
+        return new FormattedItemValue(value.toString());
       }
     },
     INTEGER {
       @Override
       FormattedItemValue format(Object value) {
-        return new FormattedItemValue(Long.toString((long) value));
+        Number number = (Number) value;
+        return new FormattedItemValue(Long.toString(number.longValue()));
       }
     },
     FLOAT {
@@ -451,6 +589,28 @@ public class DisplayData {
      * <p>Internal-only. Value objects can be safely cast to the expected Java type.
      */
     abstract FormattedItemValue format(Object value);
+
+    @Nullable
+    private static Type tryInferFrom(@Nullable Object value) {
+      Type type;
+      if (value instanceof Integer || value instanceof Long) {
+        return INTEGER;
+      } else if (value instanceof Double || value instanceof Float) {
+        return  FLOAT;
+      } else if (value instanceof Boolean) {
+        return  BOOLEAN;
+      } else if (value instanceof Instant) {
+        return  TIMESTAMP;
+      } else if (value instanceof Duration) {
+        return  DURATION;
+      } else if (value instanceof Class<?>) {
+        return  JAVA_CLASS;
+      } else if (value instanceof String) {
+        return  STRING;
+      } else {
+        return null;
+      }
+    }
   }
 
   static class FormattedItemValue {
@@ -480,8 +640,9 @@ public class DisplayData {
     private final Set<Object> visited;
 
     private Class<?> latestNs;
+
+    @Nullable
     private Item latestItem;
-    private Identifier latestIdentifier;
 
     private InternalBuilder() {
       this.entries = Maps.newHashMap();
@@ -503,6 +664,9 @@ public class DisplayData {
     @Override
     public Builder include(HasDisplayData subComponent, Class<?> namespace) {
       checkNotNull(subComponent);
+      checkNotNull(namespace);
+
+      commitLatest();
       boolean newComponent = visited.add(subComponent);
       if (newComponent) {
         Class prevNs = this.latestNs;
@@ -517,75 +681,165 @@ public class DisplayData {
     @Override
     public ItemBuilder add(String key, String value) {
       checkNotNull(value);
-      return addItem(key, Type.STRING, value);
+      return addItemIf(true, key, Type.STRING, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotNull(String key, @Nullable String value) {
+      return addItemIf(value != null, key, Type.STRING, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotDefault(
+        String key, @Nullable String value, @Nullable String defaultValue) {
+      return addItemIf(!Objects.equals(value, defaultValue), key, Type.STRING, value);
     }
 
     @Override
     public ItemBuilder add(String key, long value) {
-      return addItem(key, Type.INTEGER, value);
+      return addItemIf(true, key, Type.INTEGER, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotDefault(String key, long value, long defaultValue) {
+      return addItemIf(value != defaultValue, key, Type.INTEGER, value);
     }
 
     @Override
     public ItemBuilder add(String key, double value) {
-      return addItem(key, Type.FLOAT, value);
+      return addItemIf(true, key, Type.FLOAT, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotDefault(String key, double value, double defaultValue) {
+      return addItemIf(value != defaultValue, key, Type.FLOAT, value);
     }
 
     @Override
     public ItemBuilder add(String key, boolean value) {
-      return addItem(key, Type.BOOLEAN, value);
+      return addItemIf(true, key, Type.BOOLEAN, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotDefault(String key, boolean value, boolean defaultValue) {
+      return addItemIf(value != defaultValue, key, Type.BOOLEAN, value);
     }
 
     @Override
     public ItemBuilder add(String key, Instant value) {
       checkNotNull(value);
-      return addItem(key, Type.TIMESTAMP, value);
+      return addItemIf(true, key, Type.TIMESTAMP, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotNull(String key, @Nullable Instant value) {
+      return addItemIf(value != null, key, Type.TIMESTAMP, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotDefault(
+        String key, @Nullable Instant value, @Nullable Instant defaultValue) {
+      return addItemIf(!Objects.equals(value, defaultValue), key, Type.TIMESTAMP, value);
     }
 
     @Override
     public ItemBuilder add(String key, Duration value) {
       checkNotNull(value);
-      return addItem(key, Type.DURATION, value);
+      return addItemIf(true, key, Type.DURATION, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotNull(String key, @Nullable Duration value) {
+      return addItemIf(value != null, key, Type.DURATION, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotDefault(
+        String key, @Nullable Duration value, @Nullable Duration defaultValue) {
+      return addItemIf(!Objects.equals(value, defaultValue), key, Type.DURATION, value);
     }
 
     @Override
     public ItemBuilder add(String key, Class<?> value) {
       checkNotNull(value);
-      return addItem(key, Type.JAVA_CLASS, value);
+      return addItemIf(true, key, Type.JAVA_CLASS, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotNull(String key, @Nullable Class<?> value) {
+      return addItemIf(value != null, key, Type.JAVA_CLASS, value);
+    }
+
+    @Override
+    public ItemBuilder addIfNotDefault(
+        String key, @Nullable Class<?> value, @Nullable Class<?> defaultValue)
{
+      return addItemIf(!Objects.equals(value, defaultValue), key, Type.JAVA_CLASS, value);
+    }
+
+    @Override
+    public ItemBuilder add(String key, Type type, Object value) {
+      checkNotNull(value);
+      checkNotNull(type);
+      return addItemIf(true, key, type, value);
     }
 
-    private <T> ItemBuilder addItem(String key, Type type, T value) {
+    private ItemBuilder addItemIf(boolean condition, String key, Type type, Object value)
{
       checkNotNull(key);
       checkArgument(!key.isEmpty());
 
-      Identifier id = Identifier.of(latestNs, key);
+      commitLatest();
+      if (condition) {
+        latestItem = Item.create(latestNs, key, type, value);
+      }
+
+      return this;
+    }
+
+    private void commitLatest() {
+      if (latestItem == null) {
+        return;
+      }
+
+      Identifier id = Identifier.of(latestItem.getNamespace(), latestItem.getKey());
       if (entries.containsKey(id)) {
         throw new IllegalArgumentException("DisplayData key already exists. All display data
"
           + "for a component must be registered with a unique key.\nKey: " + id);
       }
-      Item item = Item.create(id.getNamespace(), key, type, value);
-      entries.put(id, item);
 
-      latestItem = item;
-      latestIdentifier = id;
+      entries.put(id, latestItem);
+      latestItem = null;
+    }
+
+    @Override
+    public ItemBuilder withLabel(@Nullable String label) {
+      if (latestItem != null) {
+        latestItem = latestItem.withLabel(label);
+      }
 
       return this;
     }
 
     @Override
-    public ItemBuilder withLabel(String label) {
-      latestItem = latestItem.withLabel(label);
-      entries.put(latestIdentifier, latestItem);
+    public ItemBuilder withLinkUrl(@Nullable String url) {
+      if (latestItem != null) {
+        latestItem = latestItem.withUrl(url);
+      }
+
       return this;
     }
 
     @Override
-    public ItemBuilder withLinkUrl(String url) {
-      latestItem = latestItem.withUrl(url);
-      entries.put(latestIdentifier, latestItem);
+    public ItemBuilder withNamespace(Class<?> namespace) {
+      checkNotNull(namespace);
+      if (latestItem != null) {
+        latestItem = latestItem.withNamespace(namespace);
+      }
+
       return this;
     }
 
     private DisplayData build() {
+      commitLatest();
       return new DisplayData(this.entries);
     }
   }

http://git-wip-us.apache.org/repos/asf/incubator-beam/blob/5da22920/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/HasDisplayData.java
----------------------------------------------------------------------
diff --git a/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/HasDisplayData.java
b/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/HasDisplayData.java
index 6ec1eca..36f7a31 100644
--- a/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/HasDisplayData.java
+++ b/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/HasDisplayData.java
@@ -40,6 +40,7 @@ public interface HasDisplayData {
    *  builder
    *     .include(subComponent)
    *     .add("minFilter", 42)
+   *     .addIfNotDefault("useTransactions", this.txn, false)
    *     .add("topic", "projects/myproject/topics/mytopic")
    *       .withLabel("Pub/Sub Topic")
    *     .add("serviceInstance", "myservice.com/fizzbang")

http://git-wip-us.apache.org/repos/asf/incubator-beam/blob/5da22920/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchers.java
----------------------------------------------------------------------
diff --git a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchers.java
b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchers.java
index d540b4b..2832414 100644
--- a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchers.java
+++ b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchers.java
@@ -248,10 +248,44 @@ public class DisplayDataMatchers {
     };
   }
 
+  /**
+   * Creates a matcher that matches if the examined {@link DisplayData.Item} contains the
+   * specified namespace.
+   */
+  public static Matcher<DisplayData.Item> hasNamespace(Class<?> namespace) {
+    return hasNamespace(Matchers.<Class<?>>is(namespace));
+  }
+
+  /**
+   * Creates a matcher that matches if the examined {@link DisplayData.Item} contains a namespace
+   * matching the specified namespace matcher.
+   */
+  public static Matcher<DisplayData.Item> hasNamespace(Matcher<Class<?>>
namespaceMatcher) {
+    return new FeatureMatcher<DisplayData.Item, Class<?>>(
+        namespaceMatcher, "display item with namespace", "namespace") {
+      @Override
+      protected Class<?> featureValueOf(DisplayData.Item actual) {
+        try {
+          return Class.forName(actual.getNamespace());
+        } catch (ClassNotFoundException e) {
+          return null;
+        }
+      }
+    };
+  }
+
+  /**
+   * Creates a matcher that matches if the examined {@link DisplayData.Item} matches the
+   * specified type.
+   */
   public static Matcher<DisplayData.Item> hasType(DisplayData.Type type) {
     return hasType(Matchers.is(type));
   }
 
+  /**
+   * Creates a matcher that matches if the examined {@link DisplayData.Item} has a type
+   * matching the specified type matcher.
+   */
   public static Matcher<DisplayData.Item> hasType(Matcher<DisplayData.Type> typeMatcher)
{
     return new FeatureMatcher<DisplayData.Item, DisplayData.Type>(
             typeMatcher, "with type", "type") {
@@ -262,10 +296,19 @@ public class DisplayDataMatchers {
     };
   }
 
+  /**
+   * Creates a matcher that matches if the examined {@link DisplayData.Item} has the specified
+   * value.
+   */
+
   public static Matcher<DisplayData.Item> hasValue(String value) {
     return hasValue(Matchers.is(value));
   }
 
+  /**
+   * Creates a matcher that matches if the examined {@link DisplayData.Item} contains a value
+   * matching the specified value matcher.
+   */
   public static Matcher<DisplayData.Item> hasValue(Matcher<String> valueMatcher)
{
     return new FeatureMatcher<DisplayData.Item, String>(
             valueMatcher, "with value", "value") {

http://git-wip-us.apache.org/repos/asf/incubator-beam/blob/5da22920/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchersTest.java
----------------------------------------------------------------------
diff --git a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchersTest.java
b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchersTest.java
index 1b43ff7..f24288e 100644
--- a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchersTest.java
+++ b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchersTest.java
@@ -19,6 +19,7 @@ package com.google.cloud.dataflow.sdk.transforms.display;
 
 import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasDisplayItem;
 import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasKey;
+import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasNamespace;
 import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasType;
 import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasValue;
 import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.includes;
@@ -97,6 +98,15 @@ public class DisplayDataMatchersTest {
   }
 
   @Test
+  public void testHasNamespace() {
+    Matcher<DisplayData> matcher = hasDisplayItem(hasNamespace(SampleTransform.class));
+
+    assertFalse(matcher.matches(DisplayData.from(
+        new PTransform<PCollection<String>, PCollection<String>>(){})));
+    assertThat(createDisplayDataWithItem("foo", "bar"), matcher);
+  }
+
+  @Test
   public void testIncludes() {
     final HasDisplayData subComponent = new HasDisplayData() {
       @Override
@@ -125,13 +135,23 @@ public class DisplayDataMatchersTest {
     assertThat(DisplayData.from(subComponent), matcher);
   }
 
+
   private DisplayData createDisplayDataWithItem(final String key, final String value) {
-    return DisplayData.from(
-        new PTransform<PCollection<String>, PCollection<String>>() {
-          @Override
-          public void populateDisplayData(Builder builder) {
-            builder.add(key, value);
-          }
-        });
+    return DisplayData.from(new SampleTransform(key, value));
+  }
+
+  static class SampleTransform extends PTransform<PCollection<String>, PCollection<String>>
{
+    private final String key;
+    private final String value;
+
+    SampleTransform(String key, String value) {
+      this.key = key;
+      this.value = value;
+    }
+
+    @Override
+    public void populateDisplayData(Builder builder) {
+      builder.add(key, value);
+    }
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-beam/blob/5da22920/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataTest.java
----------------------------------------------------------------------
diff --git a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataTest.java
b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataTest.java
index 4d75e26..9f8d509 100644
--- a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataTest.java
+++ b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataTest.java
@@ -19,6 +19,7 @@ package com.google.cloud.dataflow.sdk.transforms.display;
 
 import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasDisplayItem;
 import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasKey;
+import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasNamespace;
 import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasType;
 import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasValue;
 import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.includes;
@@ -86,20 +87,25 @@ public class DisplayDataTest {
           }
         };
 
+
     PTransform<?, ?> transform =
         new PTransform<PCollection<String>, PCollection<String>>() {
+          final Instant defaultStartTime = new Instant(0);
+          Instant startTime = defaultStartTime;
+
           @Override
           public void populateDisplayData(DisplayData.Builder builder) {
             builder
                 .include(subComponent1)
                 .include(subComponent2)
-                .add("MinSproggles", 200)
-                .withLabel("Mimimum Required Sproggles")
-                .add("FireLazers", true)
-                .add("TimeBomb", Instant.now().plus(Duration.standardDays(1)))
-                .add("FilterLogic", subComponent1.getClass())
-                .add("ServiceUrl", "google.com/fizzbang")
-                .withLinkUrl("http://www.google.com/fizzbang");
+                .add("minSproggles", 200)
+                  .withLabel("Mimimum Required Sproggles")
+                .add("fireLazers", true)
+                .addIfNotDefault("startTime", startTime, defaultStartTime)
+                .add("timeBomb", Instant.now().plus(Duration.standardDays(1)))
+                .add("filterLogic", subComponent1.getClass())
+                .add("serviceUrl", "google.com/fizzbang")
+                  .withLinkUrl("http://www.google.com/fizzbang");
           }
         };
 
@@ -158,14 +164,22 @@ public class DisplayDataTest {
   @Test
   public void testItemProperties() {
     final Instant value = Instant.now();
-    DisplayData data = DisplayData.from(new ConcreteComponent(value));
+    DisplayData data = DisplayData.from(new HasDisplayData() {
+      @Override
+      public void populateDisplayData(DisplayData.Builder builder) {
+        builder.add("now", value)
+            .withLabel("the current instant")
+            .withLinkUrl("http://time.gov")
+            .withNamespace(DisplayDataTest.class);
+      }
+    });
 
     @SuppressWarnings("unchecked")
     DisplayData.Item item = (DisplayData.Item) data.items().toArray()[0];
     assertThat(
         item,
-        allOf(
-            hasNamespace(Matchers.<Class<?>>is(ConcreteComponent.class)),
+        Matchers.allOf(
+            hasNamespace(DisplayDataTest.class),
             hasKey("now"),
             hasType(DisplayData.Type.TIMESTAMP),
             hasValue(ISO_FORMATTER.print(value)),
@@ -174,19 +188,6 @@ public class DisplayDataTest {
             hasUrl(is("http://time.gov"))));
   }
 
-  static class ConcreteComponent implements HasDisplayData {
-    private Instant value;
-
-    ConcreteComponent(Instant value) {
-      this.value = value;
-    }
-
-    @Override
-    public void populateDisplayData(DisplayData.Builder builder) {
-      builder.add("now", value).withLabel("the current instant").withLinkUrl("http://time.gov");
-    }
-  }
-
   @Test
   public void testUnspecifiedOptionalProperties() {
     DisplayData data =
@@ -204,6 +205,54 @@ public class DisplayDataTest {
   }
 
   @Test
+  public void testAddIfNotDefault() {
+    final int defaultValue = 10;
+
+    DisplayData data = DisplayData.from(new HasDisplayData() {
+      @Override
+      public void populateDisplayData(Builder builder) {
+        builder
+            .addIfNotDefault("isDefault", defaultValue, defaultValue)
+            .addIfNotDefault("notDefault", defaultValue + 1, defaultValue);
+      }
+    });
+
+    assertThat(data, not(hasDisplayItem(hasKey("isDefault"))));
+    assertThat(data, hasDisplayItem("notDefault", defaultValue + 1));
+  }
+
+  @Test
+  public void testAddIfNotNull() {
+    DisplayData data = DisplayData.from(new HasDisplayData() {
+      @Override
+      public void populateDisplayData(Builder builder) {
+        builder
+            .addIfNotNull("isNull", (Class<?>) null)
+            .addIfNotNull("notNull", DisplayDataTest.class);
+      }
+    });
+
+    assertThat(data, not(hasDisplayItem(hasKey("isNull"))));
+    assertThat(data, hasDisplayItem(hasKey("notNull")));
+  }
+
+  @Test
+  public void testModifyingConditionalItemIsSafe() {
+    HasDisplayData component = new HasDisplayData() {
+      @Override
+      public void populateDisplayData(Builder builder) {
+        builder.addIfNotNull("nullItem", (Class<?>) null)
+            .withLinkUrl("http://abc")
+            .withNamespace(DisplayDataTest.class)
+            .withLabel("Null item shoudl be safe");
+      }
+    };
+
+    DisplayData.from(component); // should not throw
+  }
+
+
+  @Test
   public void testIncludes() {
     final HasDisplayData subComponent =
         new HasDisplayData() {
@@ -250,6 +299,18 @@ public class DisplayDataTest {
     assertThat(data, includes(subComponent, namespaceOverride.getClass()));
   }
 
+  @Test
+  public void testNullNamespaceOverride() {
+    thrown.expect(NullPointerException.class);
+
+    DisplayData.from(new HasDisplayData() {
+      @Override
+      public void populateDisplayData(Builder builder) {
+        builder.add("foo", "bar")
+            .withNamespace(null);
+      }
+    });
+  }
 
   @Test
   public void testIdentifierEquality() {
@@ -350,6 +411,22 @@ public class DisplayDataTest {
   }
 
   @Test
+  public void testDuplicateKeyWithNamespaceOverrideDoesntThrow() {
+    DisplayData displayData = DisplayData.from(
+        new HasDisplayData() {
+          @Override
+          public void populateDisplayData(DisplayData.Builder builder) {
+            builder
+                .add("foo", "bar")
+                .add("foo", "baz")
+                  .withNamespace(DisplayDataTest.class);
+          }
+        });
+
+    assertThat(displayData.items(), hasSize(2));
+  }
+
+  @Test
   public void testToString() {
     HasDisplayData component = new HasDisplayData() {
       @Override
@@ -428,6 +505,7 @@ public class DisplayDataTest {
     }
 
     @Override
+    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
     public boolean equals(Object obj) {
       return true;
     }
@@ -480,6 +558,50 @@ public class DisplayDataTest {
   }
 
   @Test
+  public void testExplicitItemType() {
+    DisplayData data = DisplayData.from(new HasDisplayData() {
+      @Override
+      public void populateDisplayData(Builder builder) {
+        builder
+            .add("integer", DisplayData.Type.INTEGER, 1234)
+            .add("string", DisplayData.Type.STRING, "foobar");
+      }
+    });
+
+    assertThat(data, hasDisplayItem("integer", 1234));
+    assertThat(data, hasDisplayItem("string", "foobar"));
+  }
+
+  @Test
+  public void testInvalidExplicitItemType() {
+    HasDisplayData component = new HasDisplayData() {
+      @Override
+      public void populateDisplayData(Builder builder) {
+        builder.add("integer", DisplayData.Type.INTEGER, "foobar");
+      }
+    };
+
+    thrown.expect(ClassCastException.class);
+    DisplayData.from(component);
+  }
+
+  @Test
+  public void testKnownTypeInference() {
+    assertEquals(DisplayData.Type.INTEGER, DisplayData.inferType(1234));
+    assertEquals(DisplayData.Type.INTEGER, DisplayData.inferType(1234L));
+    assertEquals(DisplayData.Type.FLOAT, DisplayData.inferType(12.3));
+    assertEquals(DisplayData.Type.FLOAT, DisplayData.inferType(12.3f));
+    assertEquals(DisplayData.Type.BOOLEAN, DisplayData.inferType(true));
+    assertEquals(DisplayData.Type.TIMESTAMP, DisplayData.inferType(Instant.now()));
+    assertEquals(DisplayData.Type.DURATION, DisplayData.inferType(Duration.millis(1234)));
+    assertEquals(DisplayData.Type.JAVA_CLASS, DisplayData.inferType(DisplayDataTest.class));
+    assertEquals(DisplayData.Type.STRING, DisplayData.inferType("hello world"));
+
+    assertEquals(null, DisplayData.inferType(null));
+    assertEquals(null, DisplayData.inferType(new Object() {}));
+  }
+
+  @Test
   public void testStringFormatting() throws IOException {
     final Instant now = Instant.now();
     final Duration oneHour = Duration.standardHours(1);
@@ -534,7 +656,7 @@ public class DisplayDataTest {
         hasItem(
             allOf(
                 hasKey("alpha"),
-                hasNamespace(Matchers.<Class<?>>is(component.getClass())))));
+                hasNamespace(component.getClass()))));
   }
 
   @Test
@@ -556,6 +678,23 @@ public class DisplayDataTest {
   }
 
   @Test
+  public void testIncludeNullNamespace() {
+    final HasDisplayData subComponent = new HasDisplayData() {
+      @Override
+      public void populateDisplayData(Builder builder) {
+      }
+    };
+
+    thrown.expect(NullPointerException.class);
+    DisplayData.from(new HasDisplayData() {
+        @Override
+        public void populateDisplayData(Builder builder) {
+          builder.include(subComponent, null);
+        }
+      });
+  }
+
+  @Test
   public void testNullKey() {
     thrown.expect(NullPointerException.class);
     DisplayData.from(
@@ -610,28 +749,15 @@ public class DisplayDataTest {
         @Override
         public void populateDisplayData(Builder builder) {
           builder.add("key", "value")
-                  .withLabel(null)
-                  .withLinkUrl(null);
+              .withLabel(null)
+              .withLinkUrl(null)
+              .withNamespace(null);
         }
       });
 
     // Should not throw
   }
 
-  private static Matcher<DisplayData.Item> hasNamespace(Matcher<Class<?>>
nsMatcher) {
-    return new FeatureMatcher<DisplayData.Item, Class<?>>(
-        nsMatcher, "display item with namespace", "namespace") {
-      @Override
-      protected Class<?> featureValueOf(DisplayData.Item actual) {
-        try {
-          return Class.forName(actual.getNamespace());
-        } catch (ClassNotFoundException e) {
-          return null;
-        }
-      }
-    };
-  }
-
   private static Matcher<DisplayData.Item> hasLabel(Matcher<String> labelMatcher)
{
     return new FeatureMatcher<DisplayData.Item, String>(
         labelMatcher, "display item with label", "label") {


Mime
View raw message