+++ /dev/null
-package net.pterodactylus.fcp.plugin;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Interface for parsers for WebOfTrustās on-wire identity formats.
- */
-interface IdentityParser {
-
- /**
- * Whether this parser can parse an identity from the given fields.
- *
- * @param fields The fields to parse the identity from
- * @param prefix A prefix used for all accessed fields, may be an empty String
- * @return {@code true} if this parse can parse identities from the given
- * fields, {@code false} otherwise
- */
- boolean canParse(Map<String, String> fields, String prefix);
-
- /**
- * Parses an identity from the given fields.
- *
- * @param fields The fields to parse the identity from
- * @param prefix A prefix used for all access fields, may be an empty String
- * @param identityGenerator An identity generator
- * @param <I> The type of the identity to parse
- * @return The generated identity
- */
- <I extends Identity> I parseSingleIdentity(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator);
-
- /**
- * Parses multiple identities from the given fields.
- *
- * @param fields The fields to parse the identities from
- * @param prefix A prefix used for all identities, may be an empty String
- * @param identityGenerator An identity generator
- * @param <I> The type of the identity to parse
- * @return The generated identities
- */
- <I extends Identity> Set<I> parseMultipleIdentities(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator);
-
-}
+++ /dev/null
-package net.pterodactylus.fcp.plugin;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-
-/**
- * {@link IdentityParser} for the first WebOfTrust identity format. This
- * format is now deprecated for most of the transferred identities, but not
- * all of them; e.g. {@code GetOwnIdentities} still uses this format.
- */
-class IdentityParserV1 implements IdentityParser {
-
- @Override
- public boolean canParse(Map<String, String> fields, String prefix) {
- return fields.containsKey(prefix + "Identity0");
- }
-
- @Override
- public <I extends Identity> I parseSingleIdentity(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator) {
- return parseIdentity(fields, field -> prefix + field, identityGenerator);
- }
-
- @Override
- public <I extends Identity> Set<I> parseMultipleIdentities(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator) {
- Set<I> identities = new HashSet<>();
- for (int identityIndex = 0; fields.containsKey(prefix + "Identity" + identityIndex); identityIndex++) {
- int index = identityIndex;
- identities.add(parseIdentity(fields, field -> prefix + field + index, identityGenerator));
- }
- return identities;
- }
-
- private static <I extends Identity> I parseIdentity(Map<String, String> fields, Function<String, String> fieldPackager, IdentityGenerator<I> identityGenerator) {
- String id = fields.get(fieldPackager.apply("Identity"));
- /* sometimes WoT doesnāt send an Identity field, but an ID field. */
- if (id == null) {
- id = fields.get(fieldPackager.apply("ID"));
- }
- String name = fields.get(fieldPackager.apply("Nickname"));
- String requestUri = fields.get(fieldPackager.apply("RequestURI"));
- String insertUri = fields.get(fieldPackager.apply("InsertURI"));
- Set<String> contexts = new HashSet<>();
- for (int contextIndex = 0; fields.containsKey(fieldPackager.apply("Contexts") + ".Context" + contextIndex); contextIndex++) {
- contexts.add(fields.get(fieldPackager.apply("Contexts") + ".Context" + contextIndex));
- }
- Map<String, String> properties = new HashMap<>();
- for (int propertyIndex = 0; fields.containsKey(fieldPackager.apply("Properties") + ".Property" + propertyIndex + ".Name"); propertyIndex++) {
- String key = fields.get(fieldPackager.apply("Properties") + ".Property" + propertyIndex + ".Name");
- String value = fields.get(fieldPackager.apply("Properties") + ".Property" + propertyIndex + ".Value");
- properties.put(key, value);
- }
- return identityGenerator.createIdentity(id, name, requestUri, insertUri, contexts, properties);
- }
-
-}
+++ /dev/null
-package net.pterodactylus.fcp.plugin;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-
-/**
- * {@link IdentityParser} for the second (and current) WebOfTrust identity
- * format. This format is more consequent in how the data is structured, but
- * it is not yet used for all WebOfTrust messages.
- */
-class IdentityParserV2 implements IdentityParser {
-
- @Override
- public boolean canParse(Map<String, String> fields, String prefix) {
- return fields.containsKey(prefix + "Identities.0.ID");
- }
-
- @Override
- public <I extends Identity> I parseSingleIdentity(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator) {
- return parseIdentity(fields, field -> prefix + field, identityGenerator);
- }
-
- @Override
- public <I extends Identity> Set<I> parseMultipleIdentities(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator) {
- Set<I> identities = new HashSet<>();
- for (int identityIndex = 0; fields.containsKey(prefix + "Identities." + identityIndex + ".ID"); identityIndex++) {
- int index = identityIndex;
- identities.add(parseIdentity(fields, field -> prefix + "Identities." + index + "." + field, identityGenerator));
- }
- return identities;
- }
-
- private <I extends Identity> I parseIdentity(Map<String, String> fields, Function<String, String> fieldPackager, IdentityGenerator<I> identityGenerator) {
- String id = fields.get(fieldPackager.apply("ID"));
- String name = fields.get(fieldPackager.apply("Nickname"));
- String requestUri = fields.get(fieldPackager.apply("RequestURI"));
- String insertUri = fields.get(fieldPackager.apply("InsertURI"));
- Set<String> contexts = new HashSet<>();
- for (int contextIndex = 0; fields.containsKey(fieldPackager.apply("Contexts.") + contextIndex + ".Name"); contextIndex++) {
- contexts.add(fields.get(fieldPackager.apply("Contexts.") + contextIndex + ".Name"));
- }
- Map<String, String> properties = new HashMap<>();
- for (int propertyIndex = 0; fields.containsKey(fieldPackager.apply("Properties.") + propertyIndex + ".Name"); propertyIndex++) {
- String key = fields.get(fieldPackager.apply("Properties.") + propertyIndex + ".Name");
- String value = fields.get(fieldPackager.apply("Properties.") + propertyIndex + ".Value");
- properties.put(key, value);
- }
- return identityGenerator.createIdentity(id, name, requestUri, insertUri, contexts, properties);
- }
-
-}
--- /dev/null
+package net.pterodactylus.fcp.plugin;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.Arrays.stream;
+
+/**
+ * Parser for Web of Trust identities, recognizing all formats the Web of
+ * Trust plugin writes (and trying them in order of least-deprecated) and
+ * allowing retrieval of per-identity extraneous values.
+ *
+ * <p>
+ * This parser is called V3 because it succeeds two other, less successful
+ * parsers.
+ * </p>
+ *
+ * <h2>Usage</h2>
+ * <p>
+ * This identity parser knows about all formats that the Web of Trust plugin
+ * writes identities (including contexts and properties) in; additional
+ * fields (such as the trust- and score-related ones) can be added by
+ * specifying {@link FieldNamer}s.
+ * </p>
+ * <pre>
+ * var identityParser = new IdentityParserV3<Identity>(fields, identityGenerator,
+ * new FieldNamer("Trust", "Identities.{0}.Trust.{1}.Value", "Trust{0}.Value", "Trust")
+ * );
+ * for (var iterator = identityParser.iterate(); iterator.hasNext();) {
+ * Identity identity = iterator.next();
+ * String trust = iterator.getExtraFieldValue("Trust");
+ * identityTrusts.put(identity, trust);
+ * }
+ * </pre>
+ *
+ * @param <I> The type of identity to parse
+ */
+class IdentityParserV3<I extends Identity> implements Iterable<I> {
+
+ public IdentityParserV3(Map<String, String> fields, IdentityGenerator<I> identityGenerator, FieldNamer... extraFields) {
+ this.fields = fields;
+ this.identityGenerator = identityGenerator;
+ this.extraFields = extraFields;
+ }
+
+ public IdentityIterator iterator() {
+ return new IdentityIterator();
+ }
+
+ /**
+ * Specialized {@link Iterator} over parsed identities which can provide
+ * the values of additional fields. The extra fields are parsed together
+ * with the next identity, so they need to be accessed either before
+ * {@link #next()} is called, or after {@link #next()} but before the
+ * next {@link #next()} or {@link #hasNext()}.
+ */
+ class IdentityIterator implements Iterator<I> {
+
+ @Override
+ public boolean hasNext() {
+ getNext();
+ return nextIdentity != null;
+ }
+
+ @Override
+ public I next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ try {
+ return nextIdentity;
+ } finally {
+ nextIdentity = null;
+ }
+ }
+
+ public String getExtraField(String field) {
+ return extraFieldValues.get(field);
+ }
+
+ private void getNext() {
+ if (nextIdentity == null) {
+ extraFieldValues.clear();
+ nextIdentity = parseIdentity(identityIndex++, extraFieldValues);
+ }
+ }
+
+ private int identityIndex = 0;
+ private I nextIdentity;
+ private final Map<String, String> extraFieldValues = new HashMap<>();
+
+ }
+
+ /**
+ * Defines where a field is located in a Web of Trust response.
+ */
+ static class FieldNamer {
+
+ /**
+ * Creates a new field namer. The additional field names can contain
+ * placeholders for indices.
+ * {@link MessageFormat#format(String, Object...)} is used to combine
+ * the field names and the indices; e.g. an additional field name of
+ * {@code Foo.{0}.Value{1}} together with the indices 3 and 5 will
+ * result in a field name of {@code Foo.3.Value5}. When multiple
+ * field names are specified, the first field name that is contained
+ * in the Web of Trust responses will be used. The name of the
+ * field is the name that can be used with
+ * {@link IdentityIterator#getExtraField(String)} to retrieve the
+ * value of the field.
+ *
+ * @param name The name of the field
+ * @param additionalFieldNames Field names using
+ * {@link MessageFormat}-style placeholders for indices
+ */
+ public FieldNamer(String name, String... additionalFieldNames) {
+ this.name = name;
+ this.fieldNames = additionalFieldNames;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getFieldValue(Map<String, String> fields, Integer... indices) {
+ for (String fieldName : getFieldNames(indices)) {
+ if (fields.containsKey(fieldName)) {
+ return fields.get(fieldName);
+ }
+ }
+ return null;
+ }
+
+ private List<String> getFieldNames(Integer... indices) {
+ return Stream.of(fieldNames)
+ .map(fieldName -> MessageFormat.format(fieldName, (Object[]) indices))
+ .collect(Collectors.toList());
+ }
+
+ private final String name;
+ private final String[] fieldNames;
+
+ }
+
+ private I parseIdentity(int identityIndex, Map<String, String> extraFieldValues) {
+ I result = parseIdentity(identityIndex);
+ if (result == null) {
+ return null;
+ }
+ stream(extraFields).forEach(fieldNamer -> {
+ for (String fieldName : fieldNamer.getFieldNames(identityIndex)) {
+ extraFieldValues.putIfAbsent(fieldNamer.getName(), fields.get(fieldName));
+ }
+ });
+ return result;
+ }
+
+ private I parseIdentity(int identityIndex) {
+ String insertUri = insertUriNamer.getFieldValue(fields, identityIndex);
+ String requestUri = requestUriNamer.getFieldValue(fields, identityIndex);
+ String nickname = nicknameNamer.getFieldValue(fields, identityIndex);
+ String id = identityNamer.getFieldValue(fields, identityIndex);
+ if (id == null) {
+ return null;
+ }
+ Set<String> contexts = new HashSet<>();
+ int contextIndex = 0;
+ while (true) {
+ String context = contextNamer.getFieldValue(fields, identityIndex, contextIndex);
+ if (context == null) {
+ break;
+ }
+ contexts.add(context);
+ contextIndex++;
+ }
+ Map<String, String> properties = new HashMap<>();
+ int propertyIndex = 0;
+ while (true) {
+ String name = propertyNameNamer.getFieldValue(fields, identityIndex, propertyIndex);
+ if (name == null) {
+ break;
+ }
+ String value = propertyValueNamer.getFieldValue(fields, identityIndex, propertyIndex);
+ properties.put(name, value);
+ propertyIndex++;
+ }
+
+ return identityGenerator.createIdentity(id, nickname, requestUri, insertUri, contexts, properties);
+ }
+
+ private final Map<String, String> fields;
+ private final IdentityGenerator<I> identityGenerator;
+ private final FieldNamer[] extraFields;
+
+ private final FieldNamer insertUriNamer = new FieldNamer("InsertURI", "Identities.{0}.InsertURI", "InsertURI{0}", "InsertURI");
+ private final FieldNamer requestUriNamer = new FieldNamer("RequestURI", "Identities.{0}.RequestURI", "RequestURI{0}", "RequestURI");
+ private final FieldNamer nicknameNamer = new FieldNamer("Nickname", "Identities.{0}.Nickname", "Nickname{0}", "Nickname");
+ private final FieldNamer identityNamer = new FieldNamer("Identity", "Identities.{0}.ID", "Identities.{0}.Identity", "ID{0}", "Identity{0}", "ID", "Identity");
+ private final FieldNamer contextNamer = new FieldNamer("Context", "Identities.{0}.Contexts.{1}.Name", "Contexts{0}.Context{1}", "Context{1}");
+ private final FieldNamer propertyNameNamer = new FieldNamer("Name", "Identities.{0}.Properties.{1}.Name", "Properties{0}.Property{1}.Name", "Property{1}.Name");
+ private final FieldNamer propertyValueNamer = new FieldNamer("Value", "Identities.{0}.Properties.{1}.Value", "Properties{0}.Property{1}.Value", "Property{1}.Value");
+
+}
package net.pterodactylus.fcp.plugin;
+import java.util.HashSet;
import net.pterodactylus.fcp.highlevel.FcpClient;
import net.pterodactylus.fcp.highlevel.FcpException;
throw new FcpException("WebOfTrust Plugin did not reply with āIdentitiesā message!");
}
Map<Identity, IdentityTrust> identityTrusts = new HashMap<>();
- for (int identityIndex = 0; replies.containsKey("Identity" + identityIndex); identityIndex++) {
- String identifier = replies.get("Identity" + identityIndex);
- String nickname = replies.get("Nickname" + identityIndex);
- String requestUri = replies.get("RequestURI" + identityIndex);
- byte trust = Byte.parseByte(replies.get("Value" + identityIndex));
- String comment = replies.get("Comment" + identityIndex);
- identityTrusts.put(new Identity(identifier, nickname, requestUri, emptySet(), emptyMap()), new IdentityTrust(trust, comment));
+ IdentityParserV3<Identity> parser = new IdentityParserV3<>(replies, WebOfTrustPlugin::createIdentity,
+ new IdentityParserV3.FieldNamer("Value", "Value{0}"),
+ new IdentityParserV3.FieldNamer("Comment", "Comment{0}")
+ );
+ for (IdentityParserV3<Identity>.IdentityIterator parsedIdentity = parser.iterator(); parsedIdentity.hasNext();) {
+ byte trust = Byte.parseByte(parsedIdentity.getExtraField("Value"));
+ String comment = parsedIdentity.getExtraField("Comment");
+ identityTrusts.put(parsedIdentity.next(), new IdentityTrust(trust, comment));
}
return identityTrusts;
}
}
private static <I extends Identity> I parseIdentity(Map<String, String> replies, IdentityGenerator<I> identityGenerator) {
- IdentityParser parser = v2IdentityParser.canParse(replies, "") ? v2IdentityParser : v1IdentityParser;
- return parser.parseSingleIdentity(replies, "", identityGenerator);
+ return new IdentityParserV3<>(replies, identityGenerator).iterator().next();
}
private static <I extends Identity> Set<I> parseIdentities(Map<String, String> replies, IdentityGenerator<I> identityGenerator) {
- IdentityParser parser = v2IdentityParser.canParse(replies, "") ? v2IdentityParser : v1IdentityParser;
- return parser.parseMultipleIdentities(replies, "", identityGenerator);
+ Set<I> identities = new HashSet<>();
+ new IdentityParserV3<>(replies, identityGenerator).iterator().forEachRemaining(identities::add);
+ return identities;
}
- private static final IdentityParser v1IdentityParser = new IdentityParserV1();
- private static final IdentityParser v2IdentityParser = new IdentityParserV2();
-
private static Identity createIdentity(String identity, String nickname, String requestUri, @SuppressWarnings("unused") String insertUri, Set<String> contexts, Map<String, String> properties) {
return new Identity(identity, nickname, requestUri, contexts, properties);
}
--- /dev/null
+package net.pterodactylus.fcp.plugin;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import net.pterodactylus.fcp.plugin.IdentityParserV3.FieldNamer;
+import org.hamcrest.Matcher;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+import static net.pterodactylus.fcp.test.IdentityMatchers.hasContexts;
+import static net.pterodactylus.fcp.test.IdentityMatchers.hasId;
+import static net.pterodactylus.fcp.test.IdentityMatchers.hasInsertUri;
+import static net.pterodactylus.fcp.test.IdentityMatchers.hasNickname;
+import static net.pterodactylus.fcp.test.IdentityMatchers.hasProperties;
+import static net.pterodactylus.fcp.test.IdentityMatchers.hasRequestUri;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.junit.Assert.assertThrows;
+
+public class IdentityParserV3Test {
+
+ @Test
+ public void identityParserCanParseFirstDeprecatedFormat() {
+ addFieldsWithIdentityAndWithoutSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ assertThat(iterator.next(), matches("first", 0));
+ });
+ }
+
+ @Test
+ public void identityParserCanParseFirstDeprecatedFormatWithID() {
+ addFieldsWithIDAndWithoutSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ assertThat(iterator.next(), matches("first", 0));
+ });
+ }
+
+ @Test
+ public void identityParserCanParseSecondAlsoDeprecatedFormat() {
+ addFieldsWithIdentityAndWithNumericSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ assertThat(iterator.next(), matches("second", 0));
+ });
+ }
+
+ @Test
+ public void identityParserCanParseSecondAlsoDeprecatedFormatWithID() {
+ addFieldsWithIDAndWithNumericSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ assertThat(iterator.next(), matches("second", 0));
+ });
+ }
+
+ @Test
+ public void identityParserCanParseThirdNotDeprecatedFormat() {
+ addFieldsWithIdentityAndWithDotNumericSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ assertThat(iterator.next(), matches("third", 0));
+ });
+ }
+
+ @Test
+ public void identityParserCanParseThirdNotDeprecatedFormatWithID() {
+ addFieldsWithIDAndWithDotNumericSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ assertThat(iterator.next(), matches("third", 0));
+ });
+ }
+
+ @Test
+ public void identityParserPrefersSecondOverFirstFormat() {
+ addFieldsWithIdentityAndWithoutSuffix();
+ addFieldsWithIdentityAndWithNumericSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ assertThat(iterator.next(), matches("second", 0));
+ });
+ }
+
+ @Test
+ public void identityParserPrefersThirdOverFirstFormat() {
+ addFieldsWithIdentityAndWithoutSuffix();
+ addFieldsWithIdentityAndWithDotNumericSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ assertThat(iterator.next(), matches("third", 0));
+ });
+ }
+
+ @Test
+ public void identityParserPrefersThirdOverSecondFormat() {
+ addFieldsWithIdentityAndWithNumericSuffix();
+ addFieldsWithIdentityAndWithDotNumericSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ assertThat(iterator.next(), matches("third", 0));
+ });
+ }
+
+ @Test
+ public void identityParserParsesExactlyOneIdentity() {
+ addFieldsWithIdentityAndWithDotNumericSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ iterator.next();
+ assertThat(iterator.hasNext(), equalTo(false));
+ });
+ }
+
+ @Test
+ public void identityParserThrowsExceptionWhenIteratingPastTheEndOfTheIterator() {
+ addFieldsWithIdentityAndWithDotNumericSuffix();
+ createParserAndVerifyIterator(iterator -> {
+ iterator.next();
+ assertThrows(NoSuchElementException.class, () -> iterator.next());
+ });
+ }
+
+ @Test
+ public void identityParserParsesAllIdentitiesFromFields() {
+ addFieldsWithIdentityAndWithDotNumericSuffix("Identity", 5);
+ createParserAndVerify(parser -> {
+ assertThat(parser, containsInAnyOrder(
+ matches("third", 0),
+ matches("third", 1),
+ matches("third", 2),
+ matches("third", 3),
+ matches("third", 4)
+ ));
+ });
+ }
+
+ @Test
+ public void identityParserParsesExtraFieldsCorrectlyWithFirstFormat() {
+ addFieldsWithIDAndWithoutSuffix();
+ fields.put("Extra", "extra");
+ fields.put("Field", "field");
+ IdentityParserV3<OwnIdentity> parser = new IdentityParserV3<>(fields, ownIdentityGenerator,
+ new FieldNamer("Extra", "Extras.{0}.Extra", "Extra{0}", "Extra"),
+ new FieldNamer("Field", "Extras.{0}.Field", "Field{0}", "Field")
+ );
+ IdentityParserV3<?>.IdentityIterator identityIterator = parser.iterator();
+ identityIterator.next();
+ assertThat(identityIterator.getExtraField("Extra"), equalTo("extra"));
+ assertThat(identityIterator.getExtraField("Field"), equalTo("field"));
+ }
+
+ @Test
+ public void identityParserParsesExtraFieldsCorrectlyWithSecondFormat() {
+ addFieldsWithIDAndWithoutSuffix();
+ fields.put("Extra0", "extra");
+ fields.put("Field0", "field");
+ IdentityParserV3<OwnIdentity> parser = new IdentityParserV3<>(fields, ownIdentityGenerator,
+ new FieldNamer("Extra", "Extras.{0}.Extra", "Extra{0}", "Extra"),
+ new FieldNamer("Field", "Extras.{0}.Field", "Field{0}", "Field")
+ );
+ IdentityParserV3<?>.IdentityIterator identityIterator = parser.iterator();
+ identityIterator.next();
+ assertThat(identityIterator.getExtraField("Extra"), equalTo("extra"));
+ assertThat(identityIterator.getExtraField("Field"), equalTo("field"));
+ }
+
+ @Test
+ public void identityParserParsesExtraFieldsCorrectlyWithThirdFormat() {
+ addFieldsWithIDAndWithoutSuffix();
+ fields.put("Extras.0.Extra", "extra");
+ fields.put("Extras.0.Field", "field");
+ IdentityParserV3<OwnIdentity> parser = new IdentityParserV3<>(fields, ownIdentityGenerator,
+ new FieldNamer("Extra", "Extras.{0}.Extra", "Extra{0}", "Extra"),
+ new FieldNamer("Field", "Extras.{0}.Field", "Field{0}", "Field")
+ );
+ IdentityParserV3<?>.IdentityIterator identityIterator = parser.iterator();
+ identityIterator.next();
+ MatcherAssert.assertThat(identityIterator.getExtraField("Extra"), equalTo("extra"));
+ MatcherAssert.assertThat(identityIterator.getExtraField("Field"), equalTo("field"));
+ }
+
+ private Matcher<OwnIdentity> matches(String prefix, int index) {
+ return allOf(
+ hasId(prefix + "-id" + index),
+ hasNickname(prefix + "-nick" + index),
+ hasRequestUri(prefix + "-r" + index),
+ hasInsertUri(prefix + "-i" + index),
+ hasContexts(containsInAnyOrder(prefix + "-context-" + index, prefix + "-context-" + (index + 1))),
+ hasProperties(allOf(hasEntry(prefix + "-name-" + index, prefix + "-value-" + index), hasEntry(prefix + "-name-" + (index + 1), prefix + "-value-" + (index + 1))))
+ );
+ }
+
+ private void addFieldsWithIdentityAndWithoutSuffix() {
+ fields.put("Identity", "first-id0");
+ addFieldsWithoutSuffix();
+ }
+
+ private void addFieldsWithIDAndWithoutSuffix() {
+ fields.put("ID", "first-id0");
+ addFieldsWithoutSuffix();
+ }
+
+ private void addFieldsWithoutSuffix() {
+ fields.put("Nickname", "first-nick0");
+ fields.put("RequestURI", "first-r0");
+ fields.put("InsertURI", "first-i0");
+ fields.put("Context0", "first-context-0");
+ fields.put("Context1", "first-context-1");
+ fields.put("Property0.Name", "first-name-0");
+ fields.put("Property0.Value", "first-value-0");
+ fields.put("Property1.Name", "first-name-1");
+ fields.put("Property1.Value", "first-value-1");
+ }
+
+ private void addFieldsWithIdentityAndWithNumericSuffix() {
+ fields.put("Identity0", "second-id0");
+ addFieldsWithNumericSuffix();
+ }
+
+ private void addFieldsWithIDAndWithNumericSuffix() {
+ fields.put("ID0", "second-id0");
+ addFieldsWithNumericSuffix();
+ }
+
+ private void addFieldsWithNumericSuffix() {
+ fields.put("Nickname0", "second-nick0");
+ fields.put("RequestURI0", "second-r0");
+ fields.put("InsertURI0", "second-i0");
+ fields.put("Contexts0.Context0", "second-context-0");
+ fields.put("Contexts0.Context1", "second-context-1");
+ fields.put("Properties0.Property0.Name", "second-name-0");
+ fields.put("Properties0.Property0.Value", "second-value-0");
+ fields.put("Properties0.Property1.Name", "second-name-1");
+ fields.put("Properties0.Property1.Value", "second-value-1");
+ }
+
+ private void addFieldsWithIdentityAndWithDotNumericSuffix() {
+ addFieldsWithIdentityAndWithDotNumericSuffix("Identity", 1);
+ }
+
+ private void addFieldsWithIDAndWithDotNumericSuffix() {
+ addFieldsWithIdentityAndWithDotNumericSuffix("ID", 1);
+ }
+
+ private void addFieldsWithIdentityAndWithDotNumericSuffix(String idField, int count) {
+ Stream.iterate(0, index -> index + 1)
+ .limit(count)
+ .forEach(index -> {
+ fields.put("Identities." + index + "." + idField, "third-id" + index);
+ fields.put("Identities." + index + ".Nickname", "third-nick" + index);
+ fields.put("Identities." + index + ".RequestURI", "third-r" + index);
+ fields.put("Identities." + index + ".InsertURI", "third-i" + index);
+ fields.put("Identities." + index + ".Contexts.0.Name", "third-context-" + index);
+ fields.put("Identities." + index + ".Contexts.1.Name", "third-context-" + (index + 1));
+ fields.put("Identities." + index + ".Properties.0.Name", "third-name-" + index);
+ fields.put("Identities." + index + ".Properties.0.Value", "third-value-" + index);
+ fields.put("Identities." + index + ".Properties.1.Name", "third-name-" + (index + 1));
+ fields.put("Identities." + index + ".Properties.1.Value", "third-value-" + (index + 1));
+ });
+ }
+
+ private void createParserAndVerify(Consumer<IdentityParserV3<OwnIdentity>> iteratorConsumer, FieldNamer... extraFields) {
+ IdentityParserV3<OwnIdentity> parser = new IdentityParserV3<>(fields, ownIdentityGenerator, extraFields);
+ iteratorConsumer.accept(parser);
+ }
+
+ private void createParserAndVerifyIterator(Consumer<Iterator<OwnIdentity>> iteratorConsumer) {
+ createParserAndVerify(parser -> {
+ Iterator<OwnIdentity> iterator = parser.iterator();
+ iteratorConsumer.accept(iterator);
+ });
+ }
+
+ private OwnIdentity createOwnIdentity(String identity, String nickname, String requestUri, String insertUri, Set<String> contexts, Map<String, String> properties) {
+ return new OwnIdentity(identity, nickname, requestUri, insertUri, contexts, properties);
+ }
+
+ private final Map<String, String> fields = new HashMap<>();
+ private final IdentityGenerator<OwnIdentity> ownIdentityGenerator = this::createOwnIdentity;
+
+}