Loading plugins/org.etsi.mts.tdl.execution.java.codegen/src/org/etsi/mts/tdl/execution/java/codegen/JUnitTestGenerator.java +6 −0 Original line number Diff line number Diff line Loading @@ -2199,6 +2199,8 @@ public class JUnitTestGenerator extends Renderer { if (isUnmapped()) { line(unmappedDataInitializer); // The TDL identity of the instance; the mapping is for the adapter writeSetName(dataInstance); append(".setMapping("); writeMapping(m, ref + "_mapping", true); line(")"); Loading Loading @@ -2498,6 +2500,10 @@ public class JUnitTestGenerator extends Renderer { UnassignedMemberTreatment t = getUnassignedMember(dataInstance, tdlPackage.eINSTANCE.getStructuredDataInstance_UnassignedMember()); return isUndefined(t) ? null : t; } if (dataInstance instanceof CollectionDataInstance) { UnassignedMemberTreatment t = getUnassignedMember(dataInstance, tdlPackage.eINSTANCE.getCollectionDataInstance_UnassignedMember()); return isUndefined(t) ? null : t; } return null; } Loading plugins/org.etsi.mts.tdl.execution.java.runtime/src/org/etsi/mts/tdl/execution/java/adapters/AbstractTemplateValidator.java 0 → 100644 +143 −0 Original line number Diff line number Diff line package org.etsi.mts.tdl.execution.java.adapters; import org.etsi.mts.tdl.execution.java.tri.Data; import org.etsi.mts.tdl.execution.java.tri.Reporter; import org.etsi.mts.tdl.execution.java.tri.SpecialValue; import org.etsi.mts.tdl.execution.java.tri.Template; import org.etsi.mts.tdl.execution.java.tri.Validator; import org.etsi.mts.tdl.execution.java.tri.Verdict; /** * Common part of the default validators: the interpretation of the expected * data template (see {@link Data#getTemplate()} and {@link Template}). * Subclasses supply the navigation of a particular data representation and * apply, for each part of the expected data: * <ol> * <li>a special value marking of the template node, regardless of the expected * value at that position: <i>AnyValue</i> requires a present value, * <i>AnyValueOrOmit</i> matches anything, <i>OmitValue</i> requires an absent * value (see {@link #matchesSpecial(boolean, Template, String)});</li> * <li>otherwise a concrete comparison if the expected value is present;</li> * <li>otherwise the treatment of unassigned members in effect for the * containing node, or {@link #setDefaultUnassignedMemberTreatment(SpecialValue) * the default treatment} (<i>AnyValueOrOmit</i> unless configured otherwise) if * none is defined (see * {@link #matchesUnassigned(boolean, Template, String)}).</li> * </ol> * The first mismatch found is reported through * {@link #reportMismatch(String, String)}. Verdict handling is left to * subclasses; {@link #setVerdict(Verdict)} only logs. */ public abstract class AbstractTemplateValidator implements Validator { /** * Treatment applied to structure members and collection items that are neither * assigned in the expected value nor marked in its template (i.e. unassigned at * TDL level with the <i>unassignedMember</i> property undefined). */ private SpecialValue defaultUnassignedMemberTreatment = SpecialValue.AnyValueOrOmit; public void setDefaultUnassignedMemberTreatment(SpecialValue treatment) { if (treatment == SpecialValue.OmitValue) throw new IllegalArgumentException("OmitValue is not a valid unassigned member treatment"); this.defaultUnassignedMemberTreatment = treatment; } public SpecialValue getDefaultUnassignedMemberTreatment() { return defaultUnassignedMemberTreatment; } private Reporter reporter; /** * Set the reporter to log mismatches through (see * {@link #reportMismatch(String, String)}). */ public void setReporter(Reporter reporter) { this.reporter = reporter; } /** * Reports the part of the expected data at which matching failed. Matching * stops at the first mismatch, so this is called at most once per * {@link #matches(Data, Data)} call. The default implementation logs at * {@link LogLevel#Debug} through the {@link #setReporter(Reporter) reporter} if * one is set (reporting as "Validator", the tester component not being known * here), and prints to the console otherwise. * * @param path Path of the part within the expected data: member names * separated by dots, item indexes in brackets; empty for the * root. * @param reason What was expected and what was found. */ protected void reportMismatch(String path, String reason) { String message = "Mismatch at '" + (path.isEmpty() ? "<root>" : path) + "': " + reason; if (reporter != null) reporter.log(null, "Validator | " + message); else System.out.println(message); } /** * Reports a mismatch and returns <b>false</b>, for use in match expressions. */ protected final boolean mismatch(String path, String reason) { reportMismatch(path, reason); return false; } protected static String memberPath(String path, String name) { return path.isEmpty() ? name : path + "." + name; } protected static String itemPath(String path, int index) { return path + "[" + index + "]"; } /** * Match a part marked as a special value in the template against the presence * of the actual part. Only call if {@link Template#getSpecialValue()} of the * node is non-null. * * @param actualPresent Whether the actual part is present (non-null). * @param template The template node of the part. * @param path Path of the part, for mismatch reporting. */ protected boolean matchesSpecial(boolean actualPresent, Template template, String path) { SpecialValue marking = template.getSpecialValue(); if (marking == SpecialValue.AnyValue) return actualPresent || mismatch(path, "expected: any value, actual: omit"); if (marking == SpecialValue.AnyValueOrOmit) return true; // OmitValue return !actualPresent || mismatch(path, "expected: omit, actual: present"); } /** * Match a member/item that is unassigned at TDL level (absent expected value, * no special value marking), using the treatment of unassigned members in * effect for the containing node or the default treatment. * * @param actualPresent Whether the actual part is present (non-null). * @param container The template node of the containing structure or * collection. * @param path Path of the part, for mismatch reporting. */ protected boolean matchesUnassigned(boolean actualPresent, Template container, String path) { SpecialValue treatment = container.getUnassignedMemberTreatment(); if (treatment == null) treatment = defaultUnassignedMemberTreatment; if (treatment == SpecialValue.AnyValue) return actualPresent || mismatch(path, "unassigned member treated as any value, actual omit"); return true; } @Override public void setVerdict(Verdict verdict) { if (reporter != null) reporter.log(null, "Validator | " + "Set verdict to '" + verdict.getName() + "'"); else System.out.println("Set verdict to " + verdict.getName()); } } plugins/org.etsi.mts.tdl.execution.java.runtime/src/org/etsi/mts/tdl/execution/java/adapters/DefaultAdapter.java +43 −4 Original line number Diff line number Diff line Loading @@ -5,8 +5,43 @@ import org.etsi.mts.tdl.execution.java.tri.Reporter; import org.etsi.mts.tdl.execution.java.tri.Validator; import org.etsi.mts.tdl.execution.java.tri.Verdict; /** * Default all-in-one adapter: a {@link Reporter} that logs to the console and * a {@link Validator} that delegates matching to a configurable validator — * {@link PojoValidator} for POJO mapped data (the default) or * {@link ValueValidator} for unmapped * {@link org.etsi.mts.tdl.execution.java.tri.Value Value} data. */ public class DefaultAdapter implements Validator, Reporter { private Validator validator; /** * Uses {@link PojoValidator}. */ public DefaultAdapter() { this(new PojoValidator()); } /** * @param validator The validator to delegate matching to. */ public DefaultAdapter(Validator validator) { setValidator(validator); } public Validator getValidator() { return validator; } public DefaultAdapter setValidator(Validator validator) { this.validator = validator; if (validator instanceof AbstractTemplateValidator) // Route mismatch reports through this reporter ((AbstractTemplateValidator) validator).setReporter(this); return this; } public boolean equals(Object o0, Object o1) { // TODO Auto-generated method stub return false; Loading @@ -23,6 +58,12 @@ public class DefaultAdapter implements Validator, Reporter { System.out.println("[" + tester + "] " + body); } @Override public void log(String tester, String message) { String line = "[" + (tester != null ? tester : "-") + "] " + message; System.out.println(line); } @Override public void testObjectiveReached(String tester, String uri, String description) { // TODO Auto-generated method stub Loading @@ -49,14 +90,12 @@ public class DefaultAdapter implements Validator, Reporter { @Override public boolean matches(Data expected, Data actual) { // TODO Auto-generated method stub return false; return validator.matches(expected, actual); } @Override public void setVerdict(Verdict verdict) { // TODO Auto-generated method stub System.out.println("Set verdict to " + verdict.getName()); validator.setVerdict(verdict); } } plugins/org.etsi.mts.tdl.execution.java.runtime/src/org/etsi/mts/tdl/execution/java/adapters/PojoRenderer.java +3 −9 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ package org.etsi.mts.tdl.execution.java.adapters; import java.util.Iterator; import java.util.List; import java.util.Map; import org.etsi.mts.tdl.execution.java.rt.core.TemplateImpl; import org.etsi.mts.tdl.execution.java.tri.Data; Loading Loading @@ -56,7 +55,7 @@ public final class PojoRenderer { out.append('"').append(value).append('"'); return; } if (PojoReflection.isLeaf(value) && !(value instanceof List) && !(value instanceof Map)) { if (PojoReflection.isLeaf(value) && !(value instanceof List)) { out.append(value); return; } Loading @@ -73,13 +72,8 @@ public final class PojoRenderer { } out.append('{'); boolean first = true; if (value instanceof Map) { for (Map.Entry<?, ?> e : ((Map<?, ?>) value).entrySet()) first = renderNamed(String.valueOf(e.getKey()), e.getValue(), template, first, out); } else { for (PojoReflection.Member m : PojoReflection.members(value.getClass())) first = renderNamed(m.getName(), m.read(value), template, first, out); } out.append('}'); } Loading plugins/org.etsi.mts.tdl.execution.java.runtime/src/org/etsi/mts/tdl/execution/java/adapters/PojoValidator.java 0 → 100644 +97 −0 Original line number Diff line number Diff line package org.etsi.mts.tdl.execution.java.adapters; import java.util.Iterator; import java.util.List; import org.etsi.mts.tdl.execution.java.tri.Data; import org.etsi.mts.tdl.execution.java.tri.Template; import org.etsi.mts.tdl.execution.java.tri.Validator; /** * Validator for mapped data. Compares the expected and actual object graphs * structurally by reflection: JDK types and enums by <code>equals()</code>, * lists item by item with equal size, maps by the expected keys, other objects * field by field including superclasses (read through the JavaBeans getter of a * field if the class provides one, directly otherwise). Template markings and * unassigned member treatment are applied as described in * {@link AbstractTemplateValidator}; template members are navigated by Java * field/property name. */ public class PojoValidator extends AbstractTemplateValidator { @Override public boolean matches(Data expected, Data actual) { if (expected == null) return true; Object actualValue = actual != null ? actual.getValue() : null; return matches(expected.getValue(), actualValue, expected.getTemplate(), ""); } private boolean matches(Object expected, Object actual, Template template, String path) { if (template.getSpecialValue() != null) return matchesSpecial(actual != null, template, path); if (expected == null) return actual == null || mismatch(path, "expected: omit, actual: " + describe(actual)); if (actual == null) return mismatch(path, "expected: " + describe(expected) + ", actual: omit"); if (expected instanceof List) return matchesItems((List<?>) expected, actual, template, path); if (PojoReflection.isLeaf(expected)) return expected.equals(actual) || mismatch(path, "expected: " + describe(expected) + ", actual: " + describe(actual)); return matchesFields(expected, actual, template, path); } private boolean matchesItems(List<?> expected, Object actual, Template template, String path) { if (!(actual instanceof List)) return mismatch(path, "expected: a list, actual: " + actual.getClass().getName()); List<?> actualItems = (List<?>) actual; // TODO temporarily disabled size matching of collections // if (expected.size() != actualItems.size()) // return mismatch(path, "expected: " + expected.size() + " items, actual: " + actualItems.size()); Iterator<?> e = expected.iterator(); Iterator<?> a = actualItems.iterator(); for (int i = 0; e.hasNext(); i++) { Object expectedItem = e.next(); // A missing actual item counts as absent Object actualItem = a.hasNext() ? a.next() : null; Template itemTemplate = template.getItem(i); String itemPath = itemPath(path, i); if (expectedItem == null && itemTemplate.getSpecialValue() == null) { // Unassigned at TDL level if (!matchesUnassigned(actualItem != null, template, itemPath)) return false; } else if (!matches(expectedItem, actualItem, itemTemplate, itemPath)) return false; } return true; } private boolean matchesFields(Object expected, Object actual, Template template, String path) { if (!expected.getClass().isAssignableFrom(actual.getClass())) return mismatch(path, "expected: " + expected.getClass().getName() + ", actual: " + actual.getClass().getName()); for (PojoReflection.Member m : PojoReflection.members(expected.getClass())) if (!matchesMember(m.getName(), m.read(expected), m.read(actual), template, path)) return false; return true; } private boolean matchesMember(String name, Object expectedMember, Object actualMember, Template template, String path) { Template memberTemplate = template.getMember(name); String memberPath = memberPath(path, name); if (expectedMember == null && memberTemplate.getSpecialValue() == null) // Unassigned at TDL level return matchesUnassigned(actualMember != null, template, memberPath); return matches(expectedMember, actualMember, memberTemplate, memberPath); } private String describe(Object value) { return PojoRenderer.render(value, null); } } Loading
plugins/org.etsi.mts.tdl.execution.java.codegen/src/org/etsi/mts/tdl/execution/java/codegen/JUnitTestGenerator.java +6 −0 Original line number Diff line number Diff line Loading @@ -2199,6 +2199,8 @@ public class JUnitTestGenerator extends Renderer { if (isUnmapped()) { line(unmappedDataInitializer); // The TDL identity of the instance; the mapping is for the adapter writeSetName(dataInstance); append(".setMapping("); writeMapping(m, ref + "_mapping", true); line(")"); Loading Loading @@ -2498,6 +2500,10 @@ public class JUnitTestGenerator extends Renderer { UnassignedMemberTreatment t = getUnassignedMember(dataInstance, tdlPackage.eINSTANCE.getStructuredDataInstance_UnassignedMember()); return isUndefined(t) ? null : t; } if (dataInstance instanceof CollectionDataInstance) { UnassignedMemberTreatment t = getUnassignedMember(dataInstance, tdlPackage.eINSTANCE.getCollectionDataInstance_UnassignedMember()); return isUndefined(t) ? null : t; } return null; } Loading
plugins/org.etsi.mts.tdl.execution.java.runtime/src/org/etsi/mts/tdl/execution/java/adapters/AbstractTemplateValidator.java 0 → 100644 +143 −0 Original line number Diff line number Diff line package org.etsi.mts.tdl.execution.java.adapters; import org.etsi.mts.tdl.execution.java.tri.Data; import org.etsi.mts.tdl.execution.java.tri.Reporter; import org.etsi.mts.tdl.execution.java.tri.SpecialValue; import org.etsi.mts.tdl.execution.java.tri.Template; import org.etsi.mts.tdl.execution.java.tri.Validator; import org.etsi.mts.tdl.execution.java.tri.Verdict; /** * Common part of the default validators: the interpretation of the expected * data template (see {@link Data#getTemplate()} and {@link Template}). * Subclasses supply the navigation of a particular data representation and * apply, for each part of the expected data: * <ol> * <li>a special value marking of the template node, regardless of the expected * value at that position: <i>AnyValue</i> requires a present value, * <i>AnyValueOrOmit</i> matches anything, <i>OmitValue</i> requires an absent * value (see {@link #matchesSpecial(boolean, Template, String)});</li> * <li>otherwise a concrete comparison if the expected value is present;</li> * <li>otherwise the treatment of unassigned members in effect for the * containing node, or {@link #setDefaultUnassignedMemberTreatment(SpecialValue) * the default treatment} (<i>AnyValueOrOmit</i> unless configured otherwise) if * none is defined (see * {@link #matchesUnassigned(boolean, Template, String)}).</li> * </ol> * The first mismatch found is reported through * {@link #reportMismatch(String, String)}. Verdict handling is left to * subclasses; {@link #setVerdict(Verdict)} only logs. */ public abstract class AbstractTemplateValidator implements Validator { /** * Treatment applied to structure members and collection items that are neither * assigned in the expected value nor marked in its template (i.e. unassigned at * TDL level with the <i>unassignedMember</i> property undefined). */ private SpecialValue defaultUnassignedMemberTreatment = SpecialValue.AnyValueOrOmit; public void setDefaultUnassignedMemberTreatment(SpecialValue treatment) { if (treatment == SpecialValue.OmitValue) throw new IllegalArgumentException("OmitValue is not a valid unassigned member treatment"); this.defaultUnassignedMemberTreatment = treatment; } public SpecialValue getDefaultUnassignedMemberTreatment() { return defaultUnassignedMemberTreatment; } private Reporter reporter; /** * Set the reporter to log mismatches through (see * {@link #reportMismatch(String, String)}). */ public void setReporter(Reporter reporter) { this.reporter = reporter; } /** * Reports the part of the expected data at which matching failed. Matching * stops at the first mismatch, so this is called at most once per * {@link #matches(Data, Data)} call. The default implementation logs at * {@link LogLevel#Debug} through the {@link #setReporter(Reporter) reporter} if * one is set (reporting as "Validator", the tester component not being known * here), and prints to the console otherwise. * * @param path Path of the part within the expected data: member names * separated by dots, item indexes in brackets; empty for the * root. * @param reason What was expected and what was found. */ protected void reportMismatch(String path, String reason) { String message = "Mismatch at '" + (path.isEmpty() ? "<root>" : path) + "': " + reason; if (reporter != null) reporter.log(null, "Validator | " + message); else System.out.println(message); } /** * Reports a mismatch and returns <b>false</b>, for use in match expressions. */ protected final boolean mismatch(String path, String reason) { reportMismatch(path, reason); return false; } protected static String memberPath(String path, String name) { return path.isEmpty() ? name : path + "." + name; } protected static String itemPath(String path, int index) { return path + "[" + index + "]"; } /** * Match a part marked as a special value in the template against the presence * of the actual part. Only call if {@link Template#getSpecialValue()} of the * node is non-null. * * @param actualPresent Whether the actual part is present (non-null). * @param template The template node of the part. * @param path Path of the part, for mismatch reporting. */ protected boolean matchesSpecial(boolean actualPresent, Template template, String path) { SpecialValue marking = template.getSpecialValue(); if (marking == SpecialValue.AnyValue) return actualPresent || mismatch(path, "expected: any value, actual: omit"); if (marking == SpecialValue.AnyValueOrOmit) return true; // OmitValue return !actualPresent || mismatch(path, "expected: omit, actual: present"); } /** * Match a member/item that is unassigned at TDL level (absent expected value, * no special value marking), using the treatment of unassigned members in * effect for the containing node or the default treatment. * * @param actualPresent Whether the actual part is present (non-null). * @param container The template node of the containing structure or * collection. * @param path Path of the part, for mismatch reporting. */ protected boolean matchesUnassigned(boolean actualPresent, Template container, String path) { SpecialValue treatment = container.getUnassignedMemberTreatment(); if (treatment == null) treatment = defaultUnassignedMemberTreatment; if (treatment == SpecialValue.AnyValue) return actualPresent || mismatch(path, "unassigned member treated as any value, actual omit"); return true; } @Override public void setVerdict(Verdict verdict) { if (reporter != null) reporter.log(null, "Validator | " + "Set verdict to '" + verdict.getName() + "'"); else System.out.println("Set verdict to " + verdict.getName()); } }
plugins/org.etsi.mts.tdl.execution.java.runtime/src/org/etsi/mts/tdl/execution/java/adapters/DefaultAdapter.java +43 −4 Original line number Diff line number Diff line Loading @@ -5,8 +5,43 @@ import org.etsi.mts.tdl.execution.java.tri.Reporter; import org.etsi.mts.tdl.execution.java.tri.Validator; import org.etsi.mts.tdl.execution.java.tri.Verdict; /** * Default all-in-one adapter: a {@link Reporter} that logs to the console and * a {@link Validator} that delegates matching to a configurable validator — * {@link PojoValidator} for POJO mapped data (the default) or * {@link ValueValidator} for unmapped * {@link org.etsi.mts.tdl.execution.java.tri.Value Value} data. */ public class DefaultAdapter implements Validator, Reporter { private Validator validator; /** * Uses {@link PojoValidator}. */ public DefaultAdapter() { this(new PojoValidator()); } /** * @param validator The validator to delegate matching to. */ public DefaultAdapter(Validator validator) { setValidator(validator); } public Validator getValidator() { return validator; } public DefaultAdapter setValidator(Validator validator) { this.validator = validator; if (validator instanceof AbstractTemplateValidator) // Route mismatch reports through this reporter ((AbstractTemplateValidator) validator).setReporter(this); return this; } public boolean equals(Object o0, Object o1) { // TODO Auto-generated method stub return false; Loading @@ -23,6 +58,12 @@ public class DefaultAdapter implements Validator, Reporter { System.out.println("[" + tester + "] " + body); } @Override public void log(String tester, String message) { String line = "[" + (tester != null ? tester : "-") + "] " + message; System.out.println(line); } @Override public void testObjectiveReached(String tester, String uri, String description) { // TODO Auto-generated method stub Loading @@ -49,14 +90,12 @@ public class DefaultAdapter implements Validator, Reporter { @Override public boolean matches(Data expected, Data actual) { // TODO Auto-generated method stub return false; return validator.matches(expected, actual); } @Override public void setVerdict(Verdict verdict) { // TODO Auto-generated method stub System.out.println("Set verdict to " + verdict.getName()); validator.setVerdict(verdict); } }
plugins/org.etsi.mts.tdl.execution.java.runtime/src/org/etsi/mts/tdl/execution/java/adapters/PojoRenderer.java +3 −9 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ package org.etsi.mts.tdl.execution.java.adapters; import java.util.Iterator; import java.util.List; import java.util.Map; import org.etsi.mts.tdl.execution.java.rt.core.TemplateImpl; import org.etsi.mts.tdl.execution.java.tri.Data; Loading Loading @@ -56,7 +55,7 @@ public final class PojoRenderer { out.append('"').append(value).append('"'); return; } if (PojoReflection.isLeaf(value) && !(value instanceof List) && !(value instanceof Map)) { if (PojoReflection.isLeaf(value) && !(value instanceof List)) { out.append(value); return; } Loading @@ -73,13 +72,8 @@ public final class PojoRenderer { } out.append('{'); boolean first = true; if (value instanceof Map) { for (Map.Entry<?, ?> e : ((Map<?, ?>) value).entrySet()) first = renderNamed(String.valueOf(e.getKey()), e.getValue(), template, first, out); } else { for (PojoReflection.Member m : PojoReflection.members(value.getClass())) first = renderNamed(m.getName(), m.read(value), template, first, out); } out.append('}'); } Loading
plugins/org.etsi.mts.tdl.execution.java.runtime/src/org/etsi/mts/tdl/execution/java/adapters/PojoValidator.java 0 → 100644 +97 −0 Original line number Diff line number Diff line package org.etsi.mts.tdl.execution.java.adapters; import java.util.Iterator; import java.util.List; import org.etsi.mts.tdl.execution.java.tri.Data; import org.etsi.mts.tdl.execution.java.tri.Template; import org.etsi.mts.tdl.execution.java.tri.Validator; /** * Validator for mapped data. Compares the expected and actual object graphs * structurally by reflection: JDK types and enums by <code>equals()</code>, * lists item by item with equal size, maps by the expected keys, other objects * field by field including superclasses (read through the JavaBeans getter of a * field if the class provides one, directly otherwise). Template markings and * unassigned member treatment are applied as described in * {@link AbstractTemplateValidator}; template members are navigated by Java * field/property name. */ public class PojoValidator extends AbstractTemplateValidator { @Override public boolean matches(Data expected, Data actual) { if (expected == null) return true; Object actualValue = actual != null ? actual.getValue() : null; return matches(expected.getValue(), actualValue, expected.getTemplate(), ""); } private boolean matches(Object expected, Object actual, Template template, String path) { if (template.getSpecialValue() != null) return matchesSpecial(actual != null, template, path); if (expected == null) return actual == null || mismatch(path, "expected: omit, actual: " + describe(actual)); if (actual == null) return mismatch(path, "expected: " + describe(expected) + ", actual: omit"); if (expected instanceof List) return matchesItems((List<?>) expected, actual, template, path); if (PojoReflection.isLeaf(expected)) return expected.equals(actual) || mismatch(path, "expected: " + describe(expected) + ", actual: " + describe(actual)); return matchesFields(expected, actual, template, path); } private boolean matchesItems(List<?> expected, Object actual, Template template, String path) { if (!(actual instanceof List)) return mismatch(path, "expected: a list, actual: " + actual.getClass().getName()); List<?> actualItems = (List<?>) actual; // TODO temporarily disabled size matching of collections // if (expected.size() != actualItems.size()) // return mismatch(path, "expected: " + expected.size() + " items, actual: " + actualItems.size()); Iterator<?> e = expected.iterator(); Iterator<?> a = actualItems.iterator(); for (int i = 0; e.hasNext(); i++) { Object expectedItem = e.next(); // A missing actual item counts as absent Object actualItem = a.hasNext() ? a.next() : null; Template itemTemplate = template.getItem(i); String itemPath = itemPath(path, i); if (expectedItem == null && itemTemplate.getSpecialValue() == null) { // Unassigned at TDL level if (!matchesUnassigned(actualItem != null, template, itemPath)) return false; } else if (!matches(expectedItem, actualItem, itemTemplate, itemPath)) return false; } return true; } private boolean matchesFields(Object expected, Object actual, Template template, String path) { if (!expected.getClass().isAssignableFrom(actual.getClass())) return mismatch(path, "expected: " + expected.getClass().getName() + ", actual: " + actual.getClass().getName()); for (PojoReflection.Member m : PojoReflection.members(expected.getClass())) if (!matchesMember(m.getName(), m.read(expected), m.read(actual), template, path)) return false; return true; } private boolean matchesMember(String name, Object expectedMember, Object actualMember, Template template, String path) { Template memberTemplate = template.getMember(name); String memberPath = memberPath(path, name); if (expectedMember == null && memberTemplate.getSpecialValue() == null) // Unassigned at TDL level return matchesUnassigned(actualMember != null, template, memberPath); return matches(expectedMember, actualMember, memberTemplate, memberPath); } private String describe(Object value) { return PojoRenderer.render(value, null); } }