| /* |
| * 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.apache.beam.sdk.transforms.display; |
| |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasDisplayItem; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasKey; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasLabel; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasNamespace; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasPath; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasType; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasValue; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.includesDisplayDataFor; |
| import static org.hamcrest.Matchers.allOf; |
| import static org.hamcrest.Matchers.empty; |
| import static org.hamcrest.Matchers.everyItem; |
| import static org.hamcrest.Matchers.hasItem; |
| import static org.hamcrest.Matchers.hasItems; |
| import static org.hamcrest.Matchers.hasSize; |
| import static org.hamcrest.Matchers.is; |
| import static org.hamcrest.Matchers.isA; |
| import static org.hamcrest.Matchers.isEmptyOrNullString; |
| import static org.hamcrest.Matchers.not; |
| import static org.hamcrest.Matchers.notNullValue; |
| import static org.hamcrest.Matchers.nullValue; |
| import static org.hamcrest.Matchers.startsWith; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.fail; |
| |
| import com.fasterxml.jackson.databind.JsonNode; |
| import com.fasterxml.jackson.databind.ObjectMapper; |
| import com.google.auto.value.AutoValue; |
| import com.google.common.testing.EqualsTester; |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.util.Collection; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| import org.apache.beam.sdk.options.ValueProvider; |
| import org.apache.beam.sdk.options.ValueProvider.StaticValueProvider; |
| import org.apache.beam.sdk.transforms.PTransform; |
| import org.apache.beam.sdk.transforms.display.DisplayData.Builder; |
| import org.apache.beam.sdk.transforms.display.DisplayData.Item; |
| import org.apache.beam.sdk.util.SerializableUtils; |
| import org.apache.beam.sdk.values.PCollection; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableMap; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableMultimap; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Iterables; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Multimap; |
| import org.hamcrest.CustomTypeSafeMatcher; |
| import org.hamcrest.FeatureMatcher; |
| import org.hamcrest.Matcher; |
| import org.hamcrest.Matchers; |
| import org.joda.time.Duration; |
| import org.joda.time.Instant; |
| import org.joda.time.format.DateTimeFormatter; |
| import org.joda.time.format.ISODateTimeFormat; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.ExpectedException; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests for {@link DisplayData} class. */ |
| @RunWith(JUnit4.class) |
| public class DisplayDataTest implements Serializable { |
| @Rule public transient ExpectedException thrown = ExpectedException.none(); |
| |
| private static final DateTimeFormatter ISO_FORMATTER = ISODateTimeFormat.dateTime(); |
| private static final ObjectMapper MAPPER = new ObjectMapper(); |
| |
| @Test |
| public void testTypicalUsage() { |
| final HasDisplayData subComponent1 = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("ExpectedAnswer", 42)); |
| } |
| }; |
| |
| final HasDisplayData subComponent2 = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder |
| .add(DisplayData.item("Location", "Seattle")) |
| .add(DisplayData.item("Forecast", "Rain")); |
| } |
| }; |
| |
| PTransform<?, ?> transform = |
| new PTransform<PCollection<String>, PCollection<String>>() { |
| final Instant defaultStartTime = new Instant(0); |
| Instant startTime = defaultStartTime; |
| |
| @Override |
| public PCollection<String> expand(PCollection<String> begin) { |
| throw new IllegalArgumentException("Should never be applied"); |
| } |
| |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder |
| .include("p1", subComponent1) |
| .include("p2", subComponent2) |
| .add(DisplayData.item("minSproggles", 200).withLabel("Minimum Required Sproggles")) |
| .add(DisplayData.item("fireLasers", true)) |
| .addIfNotDefault(DisplayData.item("startTime", startTime), defaultStartTime) |
| .add(DisplayData.item("timeBomb", Instant.now().plus(Duration.standardDays(1)))) |
| .add(DisplayData.item("filterLogic", subComponent1.getClass())) |
| .add( |
| DisplayData.item("serviceUrl", "google.com/fizzbang") |
| .withLinkUrl("http://www.google.com/fizzbang")); |
| } |
| }; |
| |
| DisplayData data = DisplayData.from(transform); |
| |
| assertThat(data.items(), not(empty())); |
| assertThat( |
| data.items(), |
| everyItem( |
| allOf( |
| hasKey(not(isEmptyOrNullString())), |
| hasNamespace( |
| Matchers.<Class<?>>isOneOf( |
| transform.getClass(), subComponent1.getClass(), subComponent2.getClass())), |
| hasType(notNullValue(DisplayData.Type.class)), |
| hasValue(not(isEmptyOrNullString()))))); |
| } |
| |
| @Test |
| public void testDefaultInstance() { |
| DisplayData none = DisplayData.none(); |
| assertThat(none.items(), empty()); |
| } |
| |
| @Test |
| public void testCanBuildDisplayData() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }); |
| |
| assertThat(data.items(), hasSize(1)); |
| assertThat(data, hasDisplayItem("foo", "bar")); |
| } |
| |
| @Test |
| public void testStaticValueProviderDate() { |
| final Instant value = Instant.now(); |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", StaticValueProvider.of(value))); |
| } |
| }); |
| |
| @SuppressWarnings("unchecked") |
| DisplayData.Item item = (DisplayData.Item) data.items().toArray()[0]; |
| |
| @SuppressWarnings("unchecked") |
| Matcher<Item> matchesAllOf = |
| allOf( |
| hasKey("foo"), |
| hasType(DisplayData.Type.TIMESTAMP), |
| hasValue(ISO_FORMATTER.print(value))); |
| |
| assertThat(item, matchesAllOf); |
| } |
| |
| @Test |
| public void testStaticValueProviderString() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", StaticValueProvider.of("bar"))); |
| } |
| }); |
| |
| assertThat(data.items(), hasSize(1)); |
| assertThat(data, hasDisplayItem("foo", "bar")); |
| } |
| |
| @Test |
| public void testStaticValueProviderInt() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", StaticValueProvider.of(1))); |
| } |
| }); |
| |
| assertThat(data.items(), hasSize(1)); |
| assertThat(data, hasDisplayItem("foo", 1)); |
| } |
| |
| @Test |
| public void testInaccessibleValueProvider() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add( |
| DisplayData.item( |
| "foo", |
| new ValueProvider<String>() { |
| @Override |
| public boolean isAccessible() { |
| return false; |
| } |
| |
| @Override |
| public String get() { |
| return "bar"; |
| } |
| |
| @Override |
| public String toString() { |
| return "toString"; |
| } |
| })); |
| } |
| }); |
| |
| assertThat(data.items(), hasSize(1)); |
| assertThat(data, hasDisplayItem("foo", "toString")); |
| } |
| |
| @Test |
| public void testAsMap() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }); |
| |
| Map<DisplayData.Identifier, DisplayData.Item> map = data.asMap(); |
| assertEquals(1, map.size()); |
| assertThat(data, hasDisplayItem("foo", "bar")); |
| assertEquals(map.values(), data.items()); |
| } |
| |
| @Test |
| public void testItemProperties() { |
| final Instant value = Instant.now(); |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add( |
| DisplayData.item("now", value) |
| .withLabel("the current instant") |
| .withLinkUrl("http://time.gov") |
| .withNamespace(DisplayDataTest.class)); |
| } |
| }); |
| |
| @SuppressWarnings("unchecked") |
| DisplayData.Item item = (DisplayData.Item) data.items().toArray()[0]; |
| |
| @SuppressWarnings("unchecked") |
| Matcher<Item> matchesAllOf = |
| allOf( |
| hasNamespace(DisplayDataTest.class), |
| hasKey("now"), |
| hasType(DisplayData.Type.TIMESTAMP), |
| hasValue(ISO_FORMATTER.print(value)), |
| hasShortValue(nullValue(String.class)), |
| hasLabel("the current instant"), |
| hasUrl(is("http://time.gov"))); |
| |
| assertThat(item, matchesAllOf); |
| } |
| |
| @Test |
| public void testUnspecifiedOptionalProperties() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }); |
| |
| assertThat( |
| data, |
| hasDisplayItem(allOf(hasLabel(nullValue(String.class)), hasUrl(nullValue(String.class))))); |
| } |
| |
| @Test |
| public void testAddIfNotDefault() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder |
| .addIfNotDefault(DisplayData.item("defaultString", "foo"), "foo") |
| .addIfNotDefault(DisplayData.item("notDefaultString", "foo"), "notFoo") |
| .addIfNotDefault(DisplayData.item("defaultInteger", 1), 1) |
| .addIfNotDefault(DisplayData.item("notDefaultInteger", 1), 2) |
| .addIfNotDefault(DisplayData.item("defaultDouble", 123.4), 123.4) |
| .addIfNotDefault(DisplayData.item("notDefaultDouble", 123.4), 234.5) |
| .addIfNotDefault(DisplayData.item("defaultBoolean", true), true) |
| .addIfNotDefault(DisplayData.item("notDefaultBoolean", true), false) |
| .addIfNotDefault( |
| DisplayData.item("defaultInstant", new Instant(0)), new Instant(0)) |
| .addIfNotDefault( |
| DisplayData.item("notDefaultInstant", new Instant(0)), Instant.now()) |
| .addIfNotDefault( |
| DisplayData.item("defaultDuration", Duration.ZERO), Duration.ZERO) |
| .addIfNotDefault( |
| DisplayData.item("notDefaultDuration", Duration.millis(1234)), |
| Duration.ZERO) |
| .addIfNotDefault( |
| DisplayData.item("defaultClass", DisplayDataTest.class), |
| DisplayDataTest.class) |
| .addIfNotDefault( |
| DisplayData.item("notDefaultClass", DisplayDataTest.class), null); |
| } |
| }); |
| |
| assertThat(data.items(), hasSize(7)); |
| assertThat(data.items(), everyItem(hasKey(startsWith("notDefault")))); |
| } |
| |
| @Test |
| @SuppressWarnings("UnnecessaryBoxing") |
| public void testInterpolatedTypeDefaults() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder |
| .addIfNotDefault(DisplayData.item("integer", 123), 123) |
| .addIfNotDefault( |
| DisplayData.item("Integer", Integer.valueOf(123)), Integer.valueOf(123)) |
| .addIfNotDefault(DisplayData.item("long", 123L), 123L) |
| .addIfNotDefault(DisplayData.item("Long", Long.valueOf(123)), Long.valueOf(123)) |
| .addIfNotDefault(DisplayData.item("float", 1.23f), 1.23f) |
| .addIfNotDefault( |
| DisplayData.item("Float", Float.valueOf(1.23f)), Float.valueOf(1.23f)) |
| .addIfNotDefault(DisplayData.item("double", 1.23), 1.23) |
| .addIfNotDefault( |
| DisplayData.item("Double", Double.valueOf(1.23)), Double.valueOf(1.23)) |
| .addIfNotDefault(DisplayData.item("boolean", true), true) |
| .addIfNotDefault(DisplayData.item("Boolean", Boolean.TRUE), Boolean.TRUE); |
| } |
| }); |
| |
| assertThat(data.items(), empty()); |
| } |
| |
| @Test |
| public void testAddIfNotNull() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder |
| .addIfNotNull(DisplayData.item("nullString", (String) null)) |
| .addIfNotNull(DisplayData.item("nullVPString", (ValueProvider<String>) null)) |
| .addIfNotNull(DisplayData.item("nullierVPString", StaticValueProvider.of(null))) |
| .addIfNotNull(DisplayData.item("notNullString", "foo")) |
| .addIfNotNull(DisplayData.item("nullLong", (Long) null)) |
| .addIfNotNull(DisplayData.item("notNullLong", 1234L)) |
| .addIfNotNull(DisplayData.item("nullDouble", (Double) null)) |
| .addIfNotNull(DisplayData.item("notNullDouble", 123.4)) |
| .addIfNotNull(DisplayData.item("nullBoolean", (Boolean) null)) |
| .addIfNotNull(DisplayData.item("notNullBoolean", true)) |
| .addIfNotNull(DisplayData.item("nullInstant", (Instant) null)) |
| .addIfNotNull(DisplayData.item("notNullInstant", Instant.now())) |
| .addIfNotNull(DisplayData.item("nullDuration", (Duration) null)) |
| .addIfNotNull(DisplayData.item("notNullDuration", Duration.ZERO)) |
| .addIfNotNull(DisplayData.item("nullClass", (Class<?>) null)) |
| .addIfNotNull(DisplayData.item("notNullClass", DisplayDataTest.class)); |
| } |
| }); |
| |
| assertThat(data.items(), hasSize(7)); |
| assertThat(data.items(), everyItem(hasKey(startsWith("notNull")))); |
| } |
| |
| @Test |
| public void testModifyingConditionalItemIsSafe() { |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.addIfNotNull( |
| DisplayData.item("nullItem", (Class<?>) null) |
| .withLinkUrl("http://abc") |
| .withNamespace(DisplayDataTest.class) |
| .withLabel("Null item should be safe")); |
| } |
| }; |
| |
| DisplayData.from(component); // should not throw |
| } |
| |
| @Test |
| public void testRootPath() { |
| DisplayData.Path root = DisplayData.Path.root(); |
| assertThat(root.getComponents(), empty()); |
| } |
| |
| @Test |
| public void testExtendPath() { |
| DisplayData.Path a = DisplayData.Path.root().extend("a"); |
| assertThat(a.getComponents(), hasItems("a")); |
| |
| DisplayData.Path b = a.extend("b"); |
| assertThat(b.getComponents(), hasItems("a", "b")); |
| } |
| |
| @Test |
| public void testExtendNullPathValidation() { |
| DisplayData.Path root = DisplayData.Path.root(); |
| thrown.expect(NullPointerException.class); |
| root.extend(null); |
| } |
| |
| @Test |
| public void testExtendEmptyPathValidation() { |
| DisplayData.Path root = DisplayData.Path.root(); |
| thrown.expect(IllegalArgumentException.class); |
| root.extend(""); |
| } |
| |
| @Test |
| public void testAbsolute() { |
| DisplayData.Path path = DisplayData.Path.absolute("a", "b", "c"); |
| assertThat(path.getComponents(), hasItems("a", "b", "c")); |
| } |
| |
| @Test |
| public void testAbsoluteValidationNullFirstPath() { |
| thrown.expect(NullPointerException.class); |
| DisplayData.Path.absolute(null, "foo", "bar"); |
| } |
| |
| @Test |
| public void testAbsoluteValidationEmptyFirstPath() { |
| thrown.expect(IllegalArgumentException.class); |
| DisplayData.Path.absolute("", "foo", "bar"); |
| } |
| |
| @Test |
| public void testAbsoluteValidationNullSubsequentPath() { |
| thrown.expect(NullPointerException.class); |
| DisplayData.Path.absolute("a", "b", null, "c"); |
| } |
| |
| @Test |
| public void testAbsoluteValidationEmptySubsequentPath() { |
| thrown.expect(IllegalArgumentException.class); |
| DisplayData.Path.absolute("a", "b", "", "c"); |
| } |
| |
| @Test |
| public void testPathToString() { |
| assertEquals("root string", "[]", DisplayData.Path.root().toString()); |
| assertEquals("single component", "[a]", DisplayData.Path.absolute("a").toString()); |
| assertEquals("hierarchy", "[a/b/c]", DisplayData.Path.absolute("a", "b", "c").toString()); |
| } |
| |
| @Test |
| public void testPathEquality() { |
| new EqualsTester() |
| .addEqualityGroup(DisplayData.Path.root(), DisplayData.Path.root()) |
| .addEqualityGroup(DisplayData.Path.root().extend("a"), DisplayData.Path.absolute("a")) |
| .addEqualityGroup( |
| DisplayData.Path.root().extend("a").extend("b"), DisplayData.Path.absolute("a", "b")) |
| .testEquals(); |
| } |
| |
| @Test |
| public void testIncludes() { |
| final HasDisplayData subComponent = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }; |
| |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.include("p", subComponent); |
| } |
| }); |
| |
| assertThat(data, includesDisplayDataFor("p", subComponent)); |
| } |
| |
| @Test |
| public void testIncludeSameComponentAtDifferentPaths() { |
| final HasDisplayData subComponent1 = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }; |
| final HasDisplayData subComponent2 = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("foo2", "bar2")); |
| } |
| }; |
| |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.include("p1", subComponent1).include("p2", subComponent2); |
| } |
| }; |
| |
| DisplayData data = DisplayData.from(component); |
| assertThat(data, includesDisplayDataFor("p1", subComponent1)); |
| assertThat(data, includesDisplayDataFor("p2", subComponent2)); |
| } |
| |
| @Test |
| public void testIncludesComponentsAtSamePath() { |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.include("p", new NoopDisplayData()).include("p", new NoopDisplayData()); |
| } |
| }; |
| |
| thrown.expectCause(isA(IllegalArgumentException.class)); |
| DisplayData.from(component); |
| } |
| |
| @Test |
| public void testNullNamespaceOverride() { |
| thrown.expectCause(isA(NullPointerException.class)); |
| |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("foo", "bar").withNamespace(null)); |
| } |
| }); |
| } |
| |
| @Test |
| public void testIdentifierEquality() { |
| new EqualsTester() |
| .addEqualityGroup( |
| DisplayData.Identifier.of(DisplayData.Path.absolute("a"), DisplayDataTest.class, "1"), |
| DisplayData.Identifier.of(DisplayData.Path.absolute("a"), DisplayDataTest.class, "1")) |
| .addEqualityGroup( |
| DisplayData.Identifier.of(DisplayData.Path.absolute("b"), DisplayDataTest.class, "1")) |
| .addEqualityGroup( |
| DisplayData.Identifier.of(DisplayData.Path.absolute("a"), Object.class, "1")) |
| .addEqualityGroup( |
| DisplayData.Identifier.of(DisplayData.Path.absolute("a"), DisplayDataTest.class, "2")) |
| .testEquals(); |
| } |
| |
| @Test |
| public void testItemEquality() { |
| new EqualsTester() |
| .addEqualityGroup(DisplayData.item("foo", "bar"), DisplayData.item("foo", "bar")) |
| .addEqualityGroup(DisplayData.item("foo", "barz")) |
| .testEquals(); |
| } |
| |
| @Test |
| public void testDisplayDataEquality() { |
| HasDisplayData component1 = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }; |
| HasDisplayData component2 = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }; |
| |
| DisplayData component1DisplayData1 = DisplayData.from(component1); |
| DisplayData component1DisplayData2 = DisplayData.from(component1); |
| DisplayData component2DisplayData = DisplayData.from(component2); |
| |
| new EqualsTester() |
| .addEqualityGroup(component1DisplayData1, component1DisplayData2) |
| .addEqualityGroup(component2DisplayData) |
| .testEquals(); |
| } |
| |
| @Test |
| public void testAcceptsKeysWithDifferentNamespaces() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder |
| .add(DisplayData.item("foo", "bar")) |
| .include( |
| "p", |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }); |
| } |
| }); |
| |
| assertThat(data.items(), hasSize(2)); |
| } |
| |
| @Test |
| public void testDuplicateKeyThrowsException() { |
| thrown.expectCause(isA(IllegalArgumentException.class)); |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")).add(DisplayData.item("foo", "baz")); |
| } |
| }); |
| } |
| |
| @Test |
| public void testDuplicateKeyWithNamespaceOverrideDoesntThrow() { |
| DisplayData displayData = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder |
| .add(DisplayData.item("foo", "bar")) |
| .add(DisplayData.item("foo", "baz").withNamespace(DisplayDataTest.class)); |
| } |
| }); |
| |
| assertThat(displayData.items(), hasSize(2)); |
| } |
| |
| @Test |
| public void testToString() { |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }; |
| |
| DisplayData data = DisplayData.from(component); |
| assertEquals(String.format("[]%s:foo=bar", component.getClass().getName()), data.toString()); |
| } |
| |
| @Test |
| public void testHandlesIncludeCycles() { |
| |
| final IncludeSubComponent componentA = |
| new IncludeSubComponent() { |
| @Override |
| String getId() { |
| return "componentA"; |
| } |
| }; |
| final IncludeSubComponent componentB = |
| new IncludeSubComponent() { |
| @Override |
| String getId() { |
| return "componentB"; |
| } |
| }; |
| |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.include("p", componentA); |
| } |
| }; |
| |
| componentA.subComponent = componentB; |
| componentB.subComponent = componentA; |
| |
| DisplayData data = DisplayData.from(component); |
| assertThat(data.items(), hasSize(2)); |
| } |
| |
| @Test |
| public void testHandlesIncludeCyclesDifferentInstances() { |
| HasDisplayData component = |
| new DelegatingDisplayData(new DelegatingDisplayData(new NoopDisplayData())); |
| |
| DisplayData data = DisplayData.from(component); |
| assertThat(data.items(), hasSize(2)); |
| } |
| |
| private static class DelegatingDisplayData implements HasDisplayData { |
| private final HasDisplayData subComponent; |
| |
| public DelegatingDisplayData(HasDisplayData subComponent) { |
| this.subComponent = subComponent; |
| } |
| |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder |
| .add(DisplayData.item("subComponent", subComponent.getClass())) |
| .include("p", subComponent); |
| } |
| } |
| |
| @Test |
| public void testIncludesSubcomponentsWithObjectEquality() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder |
| .include("p1", new EqualsEverything("foo1", "bar1")) |
| .include("p2", new EqualsEverything("foo2", "bar2")); |
| } |
| }); |
| |
| assertThat(data.items(), hasSize(2)); |
| } |
| |
| private static class EqualsEverything implements HasDisplayData { |
| private final String value; |
| private final String key; |
| |
| EqualsEverything(String key, String value) { |
| this.key = key; |
| this.value = value; |
| } |
| |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item(key, value)); |
| } |
| |
| @Override |
| public int hashCode() { |
| return 1; |
| } |
| |
| @Override |
| @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") |
| public boolean equals(Object obj) { |
| return true; |
| } |
| } |
| |
| @Test |
| public void testDelegate() { |
| final HasDisplayData subcomponent = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("subCompKey", "foo")); |
| } |
| }; |
| |
| final HasDisplayData wrapped = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("wrappedKey", "bar")).include("p", subcomponent); |
| } |
| }; |
| |
| HasDisplayData wrapper = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.delegate(wrapped); |
| } |
| }; |
| |
| DisplayData data = DisplayData.from(wrapper); |
| assertThat( |
| data, |
| hasDisplayItem( |
| allOf(hasKey("wrappedKey"), hasNamespace(wrapped.getClass()), hasPath(/* root */ )))); |
| assertThat( |
| data, |
| hasDisplayItem( |
| allOf(hasKey("subCompKey"), hasNamespace(subcomponent.getClass()), hasPath("p")))); |
| } |
| |
| abstract static class IncludeSubComponent implements HasDisplayData { |
| HasDisplayData subComponent; |
| |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("id", getId())).include(getId(), subComponent); |
| } |
| |
| abstract String getId(); |
| } |
| |
| @Test |
| public void testTypeMappings() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder |
| .add(DisplayData.item("string", "foobar")) |
| .add(DisplayData.item("integer", 123)) |
| .add(DisplayData.item("float", 2.34)) |
| .add(DisplayData.item("boolean", true)) |
| .add(DisplayData.item("java_class", DisplayDataTest.class)) |
| .add(DisplayData.item("timestamp", Instant.now())) |
| .add(DisplayData.item("duration", Duration.standardHours(1))); |
| } |
| }); |
| |
| Collection<Item> items = data.items(); |
| assertThat(items, hasItem(allOf(hasKey("string"), hasType(DisplayData.Type.STRING)))); |
| assertThat(items, hasItem(allOf(hasKey("integer"), hasType(DisplayData.Type.INTEGER)))); |
| assertThat(items, hasItem(allOf(hasKey("float"), hasType(DisplayData.Type.FLOAT)))); |
| assertThat(items, hasItem(allOf(hasKey("boolean"), hasType(DisplayData.Type.BOOLEAN)))); |
| assertThat(items, hasItem(allOf(hasKey("java_class"), hasType(DisplayData.Type.JAVA_CLASS)))); |
| assertThat(items, hasItem(allOf(hasKey("timestamp"), hasType(DisplayData.Type.TIMESTAMP)))); |
| assertThat(items, hasItem(allOf(hasKey("duration"), hasType(DisplayData.Type.DURATION)))); |
| } |
| |
| @Test |
| public void testExplicitItemType() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder |
| .add(DisplayData.item("integer", DisplayData.Type.INTEGER, 1234L)) |
| .add(DisplayData.item("string", DisplayData.Type.STRING, "foobar")); |
| } |
| }); |
| |
| assertThat(data, hasDisplayItem("integer", 1234L)); |
| assertThat(data, hasDisplayItem("string", "foobar")); |
| } |
| |
| @Test |
| public void testFormatIncompatibleTypes() { |
| Map<DisplayData.Type, Object> invalidPairs = |
| ImmutableMap.<DisplayData.Type, Object>builder() |
| .put(DisplayData.Type.STRING, 1234) |
| .put(DisplayData.Type.INTEGER, "string value") |
| .put(DisplayData.Type.FLOAT, "string value") |
| .put(DisplayData.Type.BOOLEAN, "string value") |
| .put(DisplayData.Type.TIMESTAMP, "string value") |
| .put(DisplayData.Type.DURATION, "string value") |
| .put(DisplayData.Type.JAVA_CLASS, "string value") |
| .build(); |
| |
| for (Map.Entry<DisplayData.Type, Object> pair : invalidPairs.entrySet()) { |
| try { |
| DisplayData.Type type = pair.getKey(); |
| Object invalidValue = pair.getValue(); |
| |
| type.format(invalidValue); |
| fail( |
| String.format( |
| "Expected exception not thrown for invalid %s value: %s", type, invalidValue)); |
| } catch (ClassCastException e) { |
| // Expected |
| } |
| } |
| } |
| |
| @Test |
| public void testFormatCompatibleTypes() { |
| Multimap<DisplayData.Type, Object> validPairs = |
| ImmutableMultimap.<DisplayData.Type, Object>builder() |
| .put(DisplayData.Type.INTEGER, 1234) |
| .put(DisplayData.Type.INTEGER, 1234L) |
| .put(DisplayData.Type.FLOAT, 123.4f) |
| .put(DisplayData.Type.FLOAT, 123.4) |
| .put(DisplayData.Type.FLOAT, 1234) |
| .put(DisplayData.Type.FLOAT, 1234L) |
| .build(); |
| |
| for (Map.Entry<DisplayData.Type, Object> pair : validPairs.entries()) { |
| DisplayData.Type type = pair.getKey(); |
| Object value = pair.getValue(); |
| |
| try { |
| type.format(value); |
| } catch (ClassCastException e) { |
| throw new AssertionError( |
| String.format( |
| "Failed to format %s for DisplayData.%s", value.getClass().getSimpleName(), type), |
| e); |
| } |
| } |
| } |
| |
| @Test |
| public void testInvalidExplicitItemType() { |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("integer", DisplayData.Type.INTEGER, "foobar")); |
| } |
| }; |
| |
| thrown.expectCause(isA(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); |
| |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder |
| .add(DisplayData.item("string", "foobar")) |
| .add(DisplayData.item("integer", 123)) |
| .add(DisplayData.item("float", 2.34)) |
| .add(DisplayData.item("boolean", true)) |
| .add(DisplayData.item("java_class", DisplayDataTest.class)) |
| .add(DisplayData.item("timestamp", now)) |
| .add(DisplayData.item("duration", oneHour)); |
| } |
| }; |
| DisplayData data = DisplayData.from(component); |
| |
| assertThat(data, hasDisplayItem("string", "foobar")); |
| assertThat(data, hasDisplayItem("integer", 123)); |
| assertThat(data, hasDisplayItem("float", 2.34)); |
| assertThat(data, hasDisplayItem("boolean", true)); |
| assertThat(data, hasDisplayItem("java_class", DisplayDataTest.class)); |
| assertThat(data, hasDisplayItem("timestamp", now)); |
| assertThat(data, hasDisplayItem("duration", oneHour)); |
| } |
| |
| @Test |
| public void testContextProperlyReset() { |
| final HasDisplayData subComponent = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }; |
| |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.include("p", subComponent).add(DisplayData.item("alpha", "bravo")); |
| } |
| }; |
| |
| DisplayData data = DisplayData.from(component); |
| assertThat(data.items(), hasItem(allOf(hasKey("alpha"), hasNamespace(component.getClass())))); |
| } |
| |
| @Test |
| public void testFromNull() { |
| thrown.expect(NullPointerException.class); |
| DisplayData.from(null); |
| } |
| |
| @Test |
| public void testIncludeNull() { |
| thrown.expectCause(isA(NullPointerException.class)); |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.include("p", null); |
| } |
| }); |
| } |
| |
| @Test |
| public void testIncludeNullPath() { |
| thrown.expectCause(isA(NullPointerException.class)); |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.include(null, new NoopDisplayData()); |
| } |
| }); |
| } |
| |
| @Test |
| public void testIncludeEmptyPath() { |
| thrown.expectCause(isA(IllegalArgumentException.class)); |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.include("", new NoopDisplayData()); |
| } |
| }); |
| } |
| |
| @Test |
| public void testNullKey() { |
| thrown.expectCause(isA(NullPointerException.class)); |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item(null, "foo")); |
| } |
| }); |
| } |
| |
| @Test |
| public void testRejectsNullValues() { |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| try { |
| builder.add(DisplayData.item("key", (String) null)); |
| throw new RuntimeException("Should throw on null string value"); |
| } catch (NullPointerException ex) { |
| // Expected |
| } |
| |
| try { |
| builder.add(DisplayData.item("key", (Class<?>) null)); |
| throw new RuntimeException("Should throw on null class value"); |
| } catch (NullPointerException ex) { |
| // Expected |
| } |
| |
| try { |
| builder.add(DisplayData.item("key", (Duration) null)); |
| throw new RuntimeException("Should throw on null duration value"); |
| } catch (NullPointerException ex) { |
| // Expected |
| } |
| |
| try { |
| builder.add(DisplayData.item("key", (Instant) null)); |
| throw new RuntimeException("Should throw on null instant value"); |
| } catch (NullPointerException ex) { |
| // Expected |
| } |
| } |
| }); |
| } |
| |
| @Test |
| public void testAcceptsNullOptionalValues() { |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("key", "value").withLabel(null).withLinkUrl(null)); |
| } |
| }); |
| |
| // Should not throw |
| } |
| |
| @Test |
| public void testJsonSerialization() throws IOException { |
| final String stringValue = "foobar"; |
| final int intValue = 1234; |
| final double floatValue = 123.4; |
| final boolean boolValue = true; |
| final int durationMillis = 1234; |
| |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder |
| .add(DisplayData.item("string", stringValue)) |
| .add(DisplayData.item("long", intValue)) |
| .add(DisplayData.item("double", floatValue)) |
| .add(DisplayData.item("boolean", boolValue)) |
| .add(DisplayData.item("instant", new Instant(0))) |
| .add(DisplayData.item("duration", Duration.millis(durationMillis))) |
| .add( |
| DisplayData.item("class", DisplayDataTest.class) |
| .withLinkUrl("http://abc") |
| .withLabel("baz")); |
| } |
| }; |
| DisplayData data = DisplayData.from(component); |
| |
| JsonNode json = MAPPER.readTree(MAPPER.writeValueAsBytes(data)); |
| assertThat(json, hasExpectedJson(component, "STRING", "string", quoted(stringValue))); |
| assertThat(json, hasExpectedJson(component, "INTEGER", "long", intValue)); |
| assertThat(json, hasExpectedJson(component, "FLOAT", "double", floatValue)); |
| assertThat(json, hasExpectedJson(component, "BOOLEAN", "boolean", boolValue)); |
| assertThat(json, hasExpectedJson(component, "DURATION", "duration", durationMillis)); |
| assertThat( |
| json, |
| hasExpectedJson(component, "TIMESTAMP", "instant", quoted("1970-01-01T00:00:00.000Z"))); |
| assertThat( |
| json, |
| hasExpectedJson( |
| component, |
| "JAVA_CLASS", |
| "class", |
| quoted(DisplayDataTest.class.getName()), |
| quoted("DisplayDataTest"), |
| "baz", |
| "http://abc")); |
| } |
| |
| @Test |
| public void testJsonSerializationAnonymousClassNamespace() throws IOException { |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }; |
| DisplayData data = DisplayData.from(component); |
| |
| JsonNode json = MAPPER.readTree(MAPPER.writeValueAsBytes(data)); |
| String namespace = json.elements().next().get("namespace").asText(); |
| final Pattern anonClassRegex = |
| Pattern.compile(Pattern.quote(DisplayDataTest.class.getName()) + "\\$\\d+$"); |
| assertThat( |
| namespace, |
| new CustomTypeSafeMatcher<String>("anonymous class regex: " + anonClassRegex) { |
| @Override |
| protected boolean matchesSafely(String item) { |
| java.util.regex.Matcher m = anonClassRegex.matcher(item); |
| return m.matches(); |
| } |
| }); |
| } |
| |
| @Test |
| public void testCanSerializeItemSpecReference() { |
| DisplayData.ItemSpec<?> spec = DisplayData.item("clazz", DisplayDataTest.class); |
| SerializableUtils.ensureSerializable(new HoldsItemSpecReference(spec)); |
| } |
| |
| private static class HoldsItemSpecReference implements Serializable { |
| private final DisplayData.ItemSpec<?> spec; |
| |
| public HoldsItemSpecReference(DisplayData.ItemSpec<?> spec) { |
| this.spec = spec; |
| } |
| } |
| |
| @Test |
| public void testSerializable() { |
| DisplayData data = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(DisplayData.Builder builder) { |
| builder.add(DisplayData.item("foo", "bar")); |
| } |
| }); |
| |
| DisplayData serData = SerializableUtils.clone(data); |
| assertEquals(data, serData); |
| } |
| |
| /** |
| * Verify that {@link DisplayData.Builder} can recover from exceptions thrown in user code. This |
| * is not used within the Beam SDK since we want all code to produce valid DisplayData. This test |
| * just ensures it is possible to write custom code that does recover. |
| */ |
| @Test |
| public void testCanRecoverFromBuildException() { |
| final HasDisplayData safeComponent = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("a", "a")); |
| } |
| }; |
| |
| final HasDisplayData failingComponent = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| throw new RuntimeException("oh noes!"); |
| } |
| }; |
| |
| DisplayData displayData = |
| DisplayData.from( |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("b", "b")).add(DisplayData.item("c", "c")); |
| |
| try { |
| builder.include("p", failingComponent); |
| fail("Expected exception not thrown"); |
| } catch (RuntimeException e) { |
| // Expected |
| } |
| |
| builder.include("p", safeComponent).add(DisplayData.item("d", "d")); |
| } |
| }); |
| |
| assertThat(displayData, hasDisplayItem("a")); |
| assertThat(displayData, hasDisplayItem("b")); |
| assertThat(displayData, hasDisplayItem("c")); |
| assertThat(displayData, hasDisplayItem("d")); |
| } |
| |
| @Test |
| public void testExceptionMessage() { |
| final RuntimeException cause = new RuntimeException("oh noes!"); |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| throw cause; |
| } |
| }; |
| |
| thrown.expectMessage(component.getClass().getName()); |
| thrown.expectCause(is(cause)); |
| |
| DisplayData.from(component); |
| } |
| |
| @Test |
| public void testExceptionsNotWrappedRecursively() { |
| final RuntimeException cause = new RuntimeException("oh noes!"); |
| HasDisplayData component = |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.include( |
| "p", |
| new HasDisplayData() { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| throw cause; |
| } |
| }); |
| } |
| }; |
| |
| thrown.expectCause(is(cause)); |
| DisplayData.from(component); |
| } |
| |
| @AutoValue |
| abstract static class Foo implements HasDisplayData { |
| @Override |
| public void populateDisplayData(Builder builder) { |
| builder.add(DisplayData.item("someKey", "someValue")); |
| } |
| } |
| |
| @Test |
| public void testAutoValue() { |
| DisplayData data = DisplayData.from(new AutoValue_DisplayDataTest_Foo()); |
| Item item = Iterables.getOnlyElement(data.asMap().values()); |
| assertEquals(Foo.class, item.getNamespace()); |
| } |
| |
| private String quoted(Object obj) { |
| return String.format("\"%s\"", obj); |
| } |
| |
| private Matcher<Iterable<? super JsonNode>> hasExpectedJson( |
| HasDisplayData component, String type, String key, Object value) throws IOException { |
| return hasExpectedJson(component, type, key, value, null, null, null); |
| } |
| |
| private Matcher<Iterable<? super JsonNode>> hasExpectedJson( |
| HasDisplayData component, |
| String type, |
| String key, |
| Object value, |
| Object shortValue, |
| String label, |
| String linkUrl) |
| throws IOException { |
| Class<?> nsClass = component.getClass(); |
| |
| StringBuilder builder = new StringBuilder(); |
| builder.append("{"); |
| builder.append(String.format("\"namespace\":\"%s\",", nsClass.getName())); |
| builder.append(String.format("\"type\":\"%s\",", type)); |
| builder.append(String.format("\"key\":\"%s\",", key)); |
| builder.append(String.format("\"value\":%s", value)); |
| |
| if (shortValue != null) { |
| builder.append(String.format(",\"shortValue\":%s", shortValue)); |
| } |
| if (label != null) { |
| builder.append(String.format(",\"label\":\"%s\"", label)); |
| } |
| if (linkUrl != null) { |
| builder.append(String.format(",\"linkUrl\":\"%s\"", linkUrl)); |
| } |
| |
| builder.append("}"); |
| |
| JsonNode jsonNode = MAPPER.readTree(builder.toString()); |
| return hasItem(jsonNode); |
| } |
| |
| private static class NoopDisplayData implements HasDisplayData { |
| @Override |
| public void populateDisplayData(Builder builder) {} |
| } |
| |
| private static Matcher<DisplayData.Item> hasUrl(Matcher<String> urlMatcher) { |
| return new FeatureMatcher<DisplayData.Item, String>( |
| urlMatcher, "display item with url", "URL") { |
| @Override |
| protected String featureValueOf(DisplayData.Item actual) { |
| return actual.getLinkUrl(); |
| } |
| }; |
| } |
| |
| private static <T> Matcher<DisplayData.Item> hasShortValue(Matcher<T> valueStringMatcher) { |
| return new FeatureMatcher<DisplayData.Item, T>( |
| valueStringMatcher, "display item with short value", "short value") { |
| @Override |
| protected T featureValueOf(DisplayData.Item actual) { |
| @SuppressWarnings("unchecked") |
| T shortValue = (T) actual.getShortValue(); |
| return shortValue; |
| } |
| }; |
| } |
| } |