static void checkGeoRRS(ResourceRecordSet<?> geoRRS) {
    checkNotNull(geoRRS.geo(), "Geo absent: " + geoRRS);
    checkNotNull(geoRRS.qualifier(), "Qualifier: ResourceRecordSet %s", geoRRS);

    Geo geo = geoRRS.geo();
    assertTrue(!geo.regions().isEmpty(), "Regions empty on Geo: " + geoRRS);
    checkNotNull(geoRRS.name(), "Name: ResourceRecordSet %s", geoRRS);
    checkNotNull(geoRRS.type(), "Type: ResourceRecordSet %s", geoRRS);
    checkNotNull(geoRRS.ttl(), "TTL: ResourceRecordSet %s", geoRRS);
    assertFalse(geoRRS.records().isEmpty(), "Values absent on ResourceRecordSet: " + geoRRS);
  }
@Test
public class ResourceRecordSetsTest {

  ResourceRecordSet<AData> aRRS =
      ResourceRecordSet.<AData>builder()
          .name("www.denominator.io.")
          .type("A")
          .ttl(3600)
          .add(AData.create("192.0.2.1"))
          .build();

  public void nameEqualToReturnsFalseOnNull() {
    assertFalse(nameEqualTo(aRRS.name()).apply(null));
  }

  public void nameEqualToReturnsFalseOnDifferentName() {
    assertFalse(nameEqualTo("www.foo.com").apply(aRRS));
  }

  public void nameEqualToReturnsTrueOnSameName() {
    assertTrue(nameEqualTo(aRRS.name()).apply(aRRS));
  }

  public void typeEqualToReturnsFalseOnNull() {
    assertFalse(nameAndTypeEqualTo(aRRS.name(), aRRS.type()).apply(null));
  }

  public void typeEqualToReturnsFalseOnDifferentType() {
    assertFalse(nameAndTypeEqualTo(aRRS.name(), "TXT").apply(aRRS));
  }

  public void typeEqualToReturnsTrueOnSameType() {
    assertTrue(nameAndTypeEqualTo(aRRS.name(), aRRS.type()).apply(aRRS));
  }

  public void containsRecordReturnsFalseOnNull() {
    assertFalse(ResourceRecordSets.containsRecord(aRRS.records().get(0)).apply(null));
  }

  public void containsRecordReturnsFalseWhenRDataDifferent() {
    assertFalse(ResourceRecordSets.containsRecord(AData.create("198.51.100.1")).apply(aRRS));
  }

  public void containsRecordReturnsTrueWhenRDataEqual() {
    assertTrue(ResourceRecordSets.containsRecord(AData.create("192.0.2.1")).apply(aRRS));
  }

  public void containsRecordReturnsTrueWhenRDataEqualButDifferentType() {
    assertTrue(
        ResourceRecordSets.containsRecord(ImmutableMap.of("address", "192.0.2.1")).apply(aRRS));
  }

  Geo geo = Geo.create(ImmutableMultimap.of("US", "US-VA").asMap());

  ResourceRecordSet<AData> geoRRS =
      ResourceRecordSet.<AData>builder()
          .name("www.denominator.io.")
          .type("A")
          .qualifier("US-East")
          .ttl(3600)
          .add(AData.create("1.1.1.1"))
          .geo(geo)
          .build();

  public void qualifierEqualToReturnsFalseOnNull() {
    assertFalse(
        nameTypeAndQualifierEqualTo(geoRRS.name(), geoRRS.type(), geoRRS.qualifier()).apply(null));
  }

  public void qualifierEqualToReturnsFalseOnDifferentQualifier() {
    assertFalse(nameTypeAndQualifierEqualTo(geoRRS.name(), geoRRS.type(), "TXT").apply(geoRRS));
  }

  public void qualifierEqualToReturnsFalseOnAbsentQualifier() {
    assertFalse(nameTypeAndQualifierEqualTo(geoRRS.name(), geoRRS.type(), "TXT").apply(aRRS));
  }

  public void qualifierEqualToReturnsTrueOnSameQualifier() {
    assertTrue(
        nameTypeAndQualifierEqualTo(geoRRS.name(), geoRRS.type(), geoRRS.qualifier())
            .apply(geoRRS));
  }

  @DataProvider(name = "a")
  public Object[][] createData() {
    Object[][] data = new Object[37][3];
    data[0][0] = a("www.denominator.io.", "192.0.2.1");
    data[0][1] =
        ResourceRecordSet.<AData>builder()
            .name("www.denominator.io.")
            .type("A")
            .add(AData.create("192.0.2.1"))
            .build();
    data[0][2] =
        "{\"name\":\"www.denominator.io.\",\"type\":\"A\",\"records\":[{\"address\":\"192.0.2.1\"}]}";
    data[1][0] = a("www.denominator.io.", 3600, "192.0.2.1");
    data[1][1] =
        ResourceRecordSet.<AData>builder()
            .name("www.denominator.io.")
            .type("A")
            .ttl(3600)
            .add(AData.create("192.0.2.1"))
            .build();
    data[1][2] =
        "{\"name\":\"www.denominator.io.\",\"type\":\"A\",\"ttl\":3600,\"records\":[{\"address\":\"192.0.2.1\"}]}";
    data[2][0] = a("www.denominator.io.", ImmutableSet.of("192.0.2.1"));
    data[2][1] = data[0][1];
    data[2][2] = data[0][2];
    data[3][0] = a("www.denominator.io.", 3600, ImmutableSet.of("192.0.2.1"));
    data[3][1] = data[1][1];
    data[3][2] = data[1][2];
    data[4][0] = aaaa("www.denominator.io.", "1234:ab00:ff00::6b14:abcd");
    data[4][1] =
        ResourceRecordSet.<AAAAData>builder()
            .name("www.denominator.io.")
            .type("AAAA")
            .add(AAAAData.create("1234:ab00:ff00::6b14:abcd"))
            .build();
    data[4][2] =
        "{\"name\":\"www.denominator.io.\",\"type\":\"AAAA\",\"records\":[{\"address\":\"1234:ab00:ff00::6b14:abcd\"}]}";
    data[5][0] = aaaa("www.denominator.io.", 3600, "1234:ab00:ff00::6b14:abcd");
    data[5][1] =
        ResourceRecordSet.<AAAAData>builder()
            .name("www.denominator.io.")
            .type("AAAA")
            .ttl(3600)
            .add(AAAAData.create("1234:ab00:ff00::6b14:abcd"))
            .build();
    data[5][2] =
        "{\"name\":\"www.denominator.io.\",\"type\":\"AAAA\",\"ttl\":3600,\"records\":[{\"address\":\"1234:ab00:ff00::6b14:abcd\"}]}";
    data[6][0] = aaaa("www.denominator.io.", ImmutableSet.of("1234:ab00:ff00::6b14:abcd"));
    data[6][1] = data[4][1];
    data[6][2] = data[4][2];
    data[7][0] = aaaa("www.denominator.io.", 3600, ImmutableSet.of("1234:ab00:ff00::6b14:abcd"));
    data[7][1] = data[5][1];
    data[7][2] = data[5][2];
    data[8][0] = cname("www.denominator.io.", "www1.denominator.io.");
    data[8][1] =
        ResourceRecordSet.<CNAMEData>builder()
            .name("www.denominator.io.")
            .type("CNAME")
            .add(CNAMEData.create("www1.denominator.io."))
            .build();
    data[8][2] =
        "{\"name\":\"www.denominator.io.\",\"type\":\"CNAME\",\"records\":[{\"cname\":\"www1.denominator.io.\"}]}";
    data[9][0] = cname("www.denominator.io.", 3600, "www1.denominator.io.");
    data[9][1] =
        ResourceRecordSet.<CNAMEData>builder()
            .name("www.denominator.io.")
            .type("CNAME")
            .ttl(3600)
            .add(CNAMEData.create("www1.denominator.io."))
            .build();
    data[9][2] =
        "{\"name\":\"www.denominator.io.\",\"type\":\"CNAME\",\"ttl\":3600,\"records\":[{\"cname\":\"www1.denominator.io.\"}]}";
    data[10][0] = cname("www.denominator.io.", ImmutableSet.of("www1.denominator.io."));
    data[10][1] = data[8][1];
    data[10][2] = data[8][2];
    data[11][0] = cname("www.denominator.io.", 3600, ImmutableSet.of("www1.denominator.io."));
    data[11][1] = data[9][1];
    data[11][2] = data[9][2];
    data[12][0] = ns("denominator.io.", "ns.denominator.io.");
    data[12][1] =
        ResourceRecordSet.<NSData>builder()
            .name("denominator.io.")
            .type("NS")
            .add(NSData.create("ns.denominator.io."))
            .build();
    data[12][2] =
        "{\"name\":\"denominator.io.\",\"type\":\"NS\",\"records\":[{\"nsdname\":\"ns.denominator.io.\"}]}";
    data[13][0] = ns("denominator.io.", 3600, "ns.denominator.io.");
    data[13][1] =
        ResourceRecordSet.<NSData>builder()
            .name("denominator.io.")
            .type("NS")
            .ttl(3600)
            .add(NSData.create("ns.denominator.io."))
            .build();
    data[13][2] =
        "{\"name\":\"denominator.io.\",\"type\":\"NS\",\"ttl\":3600,\"records\":[{\"nsdname\":\"ns.denominator.io.\"}]}";
    data[14][0] = ns("denominator.io.", ImmutableSet.of("ns.denominator.io."));
    data[14][1] = data[12][1];
    data[14][2] = data[12][2];
    data[15][0] = ns("denominator.io.", 3600, ImmutableSet.of("ns.denominator.io."));
    data[15][1] = data[13][1];
    data[15][2] = data[13][2];
    data[16][0] = ptr("denominator.io.", "ptr.denominator.io.");
    data[16][1] =
        ResourceRecordSet.<PTRData>builder()
            .name("denominator.io.")
            .type("PTR")
            .add(PTRData.create("ptr.denominator.io."))
            .build();
    data[16][2] =
        "{\"name\":\"denominator.io.\",\"type\":\"PTR\",\"records\":[{\"ptrdname\":\"ptr.denominator.io.\"}]}";
    data[17][0] = ptr("denominator.io.", 3600, "ptr.denominator.io.");
    data[17][1] =
        ResourceRecordSet.<PTRData>builder()
            .name("denominator.io.")
            .type("PTR")
            .ttl(3600)
            .add(PTRData.create("ptr.denominator.io."))
            .build();
    data[17][2] =
        "{\"name\":\"denominator.io.\",\"type\":\"PTR\",\"ttl\":3600,\"records\":[{\"ptrdname\":\"ptr.denominator.io.\"}]}";
    data[18][0] = ptr("denominator.io.", ImmutableSet.of("ptr.denominator.io."));
    data[18][1] = data[16][1];
    data[18][2] = data[16][2];
    data[19][0] = ptr("denominator.io.", 3600, ImmutableSet.of("ptr.denominator.io."));
    data[19][1] = data[17][1];
    data[19][2] = data[17][2];
    data[20][0] = txt("denominator.io.", "\"made in sweden\"");
    data[20][1] =
        ResourceRecordSet.<TXTData>builder()
            .name("denominator.io.")
            .type("TXT")
            .add(TXTData.create("\"made in sweden\""))
            .build();
    data[20][2] =
        "{\"name\":\"denominator.io.\",\"type\":\"TXT\",\"records\":[{\"txtdata\":\"\\\"made in sweden\\\"\"}]}";
    data[21][0] = txt("denominator.io.", 3600, "\"made in sweden\"");
    data[21][1] =
        ResourceRecordSet.<TXTData>builder()
            .name("denominator.io.")
            .type("TXT")
            .ttl(3600)
            .add(TXTData.create("\"made in sweden\""))
            .build();
    data[21][2] =
        "{\"name\":\"denominator.io.\",\"type\":\"TXT\",\"ttl\":3600,\"records\":[{\"txtdata\":\"\\\"made in sweden\\\"\"}]}";
    data[22][0] = txt("denominator.io.", ImmutableSet.of("\"made in sweden\""));
    data[22][1] = data[20][1];
    data[22][2] = data[20][2];
    data[23][0] = txt("denominator.io.", 3600, ImmutableSet.of("\"made in sweden\""));
    data[23][1] = data[21][1];
    data[23][2] = data[21][2];
    data[24][0] = spf("denominator.io.", "\"v=spf1 a mx -all\"");
    data[24][1] =
        ResourceRecordSet.<SPFData>builder()
            .name("denominator.io.")
            .type("SPF")
            .add(SPFData.create("\"v=spf1 a mx -all\""))
            .build();
    data[24][2] =
        "{\"name\":\"denominator.io.\",\"type\":\"SPF\",\"records\":[{\"txtdata\":\"\\\"v=spf1 a mx -all\\\"\"}]}";
    data[25][0] = spf("denominator.io.", 3600, "\"v=spf1 a mx -all\"");
    data[25][1] =
        ResourceRecordSet.<SPFData>builder()
            .name("denominator.io.")
            .type("SPF")
            .ttl(3600)
            .add(SPFData.create("\"v=spf1 a mx -all\""))
            .build();
    data[25][2] =
        "{\"name\":\"denominator.io.\",\"type\":\"SPF\",\"ttl\":3600,\"records\":[{\"txtdata\":\"\\\"v=spf1 a mx -all\\\"\"}]}";
    data[26][0] = spf("denominator.io.", ImmutableSet.of("\"v=spf1 a mx -all\""));
    data[26][1] = data[24][1];
    data[26][2] = data[24][2];
    data[27][0] = spf("denominator.io.", 3600, ImmutableSet.of("\"v=spf1 a mx -all\""));
    data[27][1] = data[25][1];
    data[27][2] = data[25][2];
    data[28][0] = mx("denominator.io.", 3600, ImmutableSet.of("1 mx1.denominator.io."));
    data[28][1] =
        ResourceRecordSet.<MXData>builder()
            .name("denominator.io.")
            .type("MX")
            .ttl(3600)
            .add(MXData.create(1, "mx1.denominator.io."))
            .build();
    data[28][2] =
        "{\"name\":\"denominator.io.\",\"type\":\"MX\",\"ttl\":3600,\"records\":[{\"preference\":1,\"exchange\":\"mx1.denominator.io.\"}]}";
    data[29][0] = srv("denominator.io.", 3600, ImmutableSet.of("0 1 80 srv.denominator.io."));
    data[29][1] =
        ResourceRecordSet.<SRVData>builder()
            .name("denominator.io.")
            .type("SRV")
            .ttl(3600)
            .add(
                SRVData.builder()
                    .priority(0)
                    .weight(1)
                    .port(80)
                    .target("srv.denominator.io.")
                    .build())
            .build();
    data[29][2] =
        "{\"name\":\"denominator.io.\",\"type\":\"SRV\",\"ttl\":3600,\"records\":[{\"priority\":0,\"weight\":1,\"port\":80,\"target\":\"srv.denominator.io.\"}]}";
    data[30][0] = ds("ds.denominator.io.", 3600, ImmutableSet.of("12345 1 1 B33F"));
    data[30][1] =
        ResourceRecordSet.<DSData>builder()
            .name("ds.denominator.io.")
            .type("DS")
            .ttl(3600)
            .add(DSData.builder().keyTag(12345).algorithmId(1).digestId(1).digest("B33F").build())
            .build();
    data[30][2] =
        "{\"name\":\"ds.denominator.io.\",\"type\":\"DS\",\"ttl\":3600,\"records\":[{\"keyTag\":12345,\"algorithmId\":1,\"digestId\":1,\"digest\":\"B33F\"}]}";
    data[31][0] = cert("www.denominator.io.", 3600, ImmutableSet.of("12345 1 1 B33F"));
    data[31][1] =
        ResourceRecordSet.<CERTData>builder()
            .name("www.denominator.io.")
            .type("CERT")
            .ttl(3600)
            .add(CERTData.builder().certType(12345).keyTag(1).algorithm(1).cert("B33F").build())
            .build();
    data[31][2] =
        "{\"name\":\"www.denominator.io.\",\"type\":\"CERT\",\"ttl\":3600,\"records\":[{\"certType\":12345,\"keyTag\":1,\"algorithm\":1,\"cert\":\"B33F\"}]}";
    data[32][0] =
        naptr(
            "phone.denominator.io.",
            3600,
            ImmutableSet.of("1 1 U E2U+sip !^.*$!sip:[email protected]! ."));
    data[32][1] =
        ResourceRecordSet.<NAPTRData>builder()
            .name("phone.denominator.io.")
            .type("NAPTR")
            .ttl(3600)
            .add(
                NAPTRData.builder()
                    .order(1)
                    .preference(1)
                    .flags("U")
                    .services("E2U+sip")
                    .regexp("!^.*$!sip:[email protected]!")
                    .replacement(".")
                    .build())
            .build();
    data[32][2] =
        "{\"name\":\"phone.denominator.io.\",\"type\":\"NAPTR\",\"ttl\":3600,\"records\":[{\"order\":1,\"preference\":1,\"flags\":\"U\",\"services\":\"E2U+sip\",\"regexp\":\"!^.*$!sip:[email protected]!\",\"replacement\":\".\"}]}";
    data[33][0] =
        sshfp(
            "server1.denominator.io.",
            3600,
            ImmutableSet.of("2 1 123456789abcdef67890123456789abcdef67890"));
    data[33][1] =
        ResourceRecordSet.<SSHFPData>builder()
            .name("server1.denominator.io.")
            .type("SSHFP")
            .ttl(3600)
            .add(SSHFPData.createDSA("123456789abcdef67890123456789abcdef67890"))
            .build();
    data[33][2] =
        "{\"name\":\"server1.denominator.io.\",\"type\":\"SSHFP\",\"ttl\":3600,\"records\":[{\"algorithm\":2,\"fptype\":1,\"fingerprint\":\"123456789abcdef67890123456789abcdef67890\"}]}";
    data[34][0] =
        loc(
            "server1.denominator.io.",
            3600,
            ImmutableSet.of("37 48 48.892 S 144 57 57.502 E 26m 10m 100m 10m"));
    data[34][1] =
        ResourceRecordSet.<LOCData>builder()
            .name("server1.denominator.io.")
            .type("LOC")
            .ttl(3600)
            .add(
                LOCData.builder()
                    .latitude("37 48 48.892 S")
                    .longitude("144 57 57.502 E")
                    .altitude("26m")
                    .diameter("10m")
                    .hprecision("100m")
                    .vprecision("10m")
                    .build())
            .build();
    data[34][2] =
        "{\"name\":\"server1.denominator.io.\",\"type\":\"LOC\",\"ttl\":3600,\"records\":[{\"latitude\":\"37 48 48.892 S\",\"longitude\":\"144 57 57.502 E\",\"altitude\":\"26m\",\"diameter\":\"10m\",\"hprecision\":\"100m\",\"vprecision\":\"10m\"}]}";
    data[35][0] = tlsa("server1.denominator.io.", 3600, ImmutableSet.of("1 1 1 B33F"));
    data[35][1] =
        ResourceRecordSet.<TLSAData>builder()
            .name("server1.denominator.io.")
            .type("TLSA")
            .ttl(3600)
            .add(
                TLSAData.builder()
                    .usage(1)
                    .selector(1)
                    .matchingType(1)
                    .certificateAssociationData("B33F")
                    .build())
            .build();
    data[35][2] =
        "{\"name\":\"server1.denominator.io.\",\"type\":\"TLSA\",\"ttl\":3600,\"records\":[{\"usage\":1,\"selector\":1,\"matchingType\":1,\"certificateAssociationData\":\"B33F\"}]}";
    data[36][0] =
        loc("server1.denominator.io.", 3600, ImmutableSet.of("37 48 48.892 S 144 57 57.502 E"));
    data[36][1] =
        ResourceRecordSet.<LOCData>builder()
            .name("server1.denominator.io.")
            .type("LOC")
            .ttl(3600)
            .add(LOCData.builder().latitude("37 48 48.892 S").longitude("144 57 57.502 E").build())
            .build();
    data[36][2] =
        "{\"name\":\"server1.denominator.io.\",\"type\":\"LOC\",\"ttl\":3600,\"records\":[{\"latitude\":\"37 48 48.892 S\",\"longitude\":\"144 57 57.502 E\"}]}";
    return data;
  }

  @Test(dataProvider = "a")
  public void shortFormEqualsLongForm(
      ResourceRecordSet<?> shortForm, ResourceRecordSet<?> longForm, String json)
      throws IOException {
    assertEquals(shortForm, longForm);
    assertEquals(shortForm.name(), longForm.name());
    assertEquals(shortForm.type(), longForm.type());
    assertEquals(shortForm.ttl(), longForm.ttl());
    assertEquals(
        ImmutableList.copyOf(shortForm.records()), ImmutableList.copyOf(longForm.records()));
    assertEquals(gson.toJson(shortForm), json);
    assertEquals(gson.fromJson(json, RRSET_STRING_OBJECT.getType()), shortForm);
    assertEquals(gson.fromJson(json, ResourceRecordSet.class), shortForm);
  }

  private static final TypeToken<Map<String, Object>> MAP_STRING_OBJECT =
      new TypeToken<Map<String, Object>>() {};

  private static final TypeToken<ResourceRecordSet<Map<String, Object>>> RRSET_STRING_OBJECT =
      new TypeToken<ResourceRecordSet<Map<String, Object>>>() {};

  private static final TypeAdapter<Map<String, Object>> doubleToInt =
      new TypeAdapter<Map<String, Object>>() {
        TypeAdapter<Map<String, Object>> delegate =
            new MapTypeAdapterFactory(
                    new ConstructorConstructor(Collections.<Type, InstanceCreator<?>>emptyMap()),
                    false)
                .create(new Gson(), MAP_STRING_OBJECT);

        @Override
        public void write(JsonWriter out, Map<String, Object> value) throws IOException {
          delegate.write(out, value);
        }

        @Override
        public Map<String, Object> read(JsonReader in) throws IOException {
          Map<String, Object> map = delegate.read(in);
          for (Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() instanceof Double) {
              entry.setValue(Double.class.cast(entry.getValue()).intValue());
            }
          }
          return map;
        }
      }.nullSafe();

  // deals with scenario where gson Object type treats all numbers as doubles.
  public static final Gson gson =
      new GsonBuilder() //
          .disableHtmlEscaping() //
          .registerTypeAdapter(Map.class, doubleToInt) //
          .registerTypeAdapter(MAP_STRING_OBJECT.getType(), doubleToInt)
          .create();
}