From e7fe6126bf6ecdb3ba496410e45450c6fd9608ee Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 14 May 2024 17:18:56 +0200 Subject: [PATCH 1/5] fix: Properly represent FeaturesMatchingResult model if `multiple` option is enabled --- .../io/appium/java_client/ComparesImages.java | 3 +- .../imagecomparison/ComparisonResult.java | 15 +- .../FeaturesMatchingResult.java | 12 +- .../OccurrenceMatchingResult.java | 134 +++++++++++++++--- .../SimilarityMatchingResult.java | 7 +- 5 files changed, 133 insertions(+), 38 deletions(-) diff --git a/src/main/java/io/appium/java_client/ComparesImages.java b/src/main/java/io/appium/java_client/ComparesImages.java index 1b29c35cf..5a9a58b1c 100644 --- a/src/main/java/io/appium/java_client/ComparesImages.java +++ b/src/main/java/io/appium/java_client/ComparesImages.java @@ -126,8 +126,7 @@ default OccurrenceMatchingResult findImageOccurrence(byte[] fullImage, byte[] pa @Nullable OccurrenceMatchingOptions options) { Object response = CommandExecutionHelper.execute(this, compareImagesCommand(ComparisonMode.MATCH_TEMPLATE, fullImage, partialImage, options)); - //noinspection unchecked - return new OccurrenceMatchingResult((Map) response); + return new OccurrenceMatchingResult(response); } /** diff --git a/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java b/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java index 49e487b59..0ad5c40f9 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java @@ -16,8 +16,6 @@ package io.appium.java_client.imagecomparison; -import lombok.AccessLevel; -import lombok.Getter; import org.openqa.selenium.Point; import org.openqa.selenium.Rectangle; @@ -32,12 +30,17 @@ public abstract class ComparisonResult { private static final String VISUALIZATION = "visualization"; - @Getter(AccessLevel.PROTECTED) private final Map commandResult; + protected final Object commandResult; - public ComparisonResult(Map commandResult) { + public ComparisonResult(Object commandResult) { this.commandResult = commandResult; } + protected Map getResultAsMap() { + //noinspection unchecked + return (Map) commandResult; + } + /** * Verifies if the corresponding property is present in the commend result * and throws an exception if not. @@ -45,7 +48,7 @@ public ComparisonResult(Map commandResult) { * @param propertyName the actual property name to be verified for presence */ protected void verifyPropertyPresence(String propertyName) { - if (!commandResult.containsKey(propertyName)) { + if (!getResultAsMap().containsKey(propertyName)) { throw new IllegalStateException( String.format("There is no '%s' attribute in the resulting command output %s. " + "Did you set the options properly?", propertyName, commandResult)); @@ -59,7 +62,7 @@ protected void verifyPropertyPresence(String propertyName) { */ public byte[] getVisualization() { verifyPropertyPresence(VISUALIZATION); - return ((String) getCommandResult().get(VISUALIZATION)).getBytes(StandardCharsets.UTF_8); + return ((String) getResultAsMap().get(VISUALIZATION)).getBytes(StandardCharsets.UTF_8); } /** diff --git a/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingResult.java b/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingResult.java index 2ba90c7dd..0a983e50a 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingResult.java @@ -43,7 +43,7 @@ public FeaturesMatchingResult(Map input) { */ public int getCount() { verifyPropertyPresence(COUNT); - return ((Long) getCommandResult().get(COUNT)).intValue(); + return ((Long) getResultAsMap().get(COUNT)).intValue(); } /** @@ -56,7 +56,7 @@ public int getCount() { */ public int getTotalCount() { verifyPropertyPresence(TOTAL_COUNT); - return ((Long) getCommandResult().get(TOTAL_COUNT)).intValue(); + return ((Long) getResultAsMap().get(TOTAL_COUNT)).intValue(); } /** @@ -67,7 +67,7 @@ public int getTotalCount() { public List getPoints1() { verifyPropertyPresence(POINTS1); //noinspection unchecked - return ((List>) getCommandResult().get(POINTS1)).stream() + return ((List>) getResultAsMap().get(POINTS1)).stream() .map(ComparisonResult::mapToPoint) .collect(Collectors.toList()); } @@ -80,7 +80,7 @@ public List getPoints1() { public Rectangle getRect1() { verifyPropertyPresence(RECT1); //noinspection unchecked - return mapToRect((Map) getCommandResult().get(RECT1)); + return mapToRect((Map) getResultAsMap().get(RECT1)); } /** @@ -91,7 +91,7 @@ public Rectangle getRect1() { public List getPoints2() { verifyPropertyPresence(POINTS2); //noinspection unchecked - return ((List>) getCommandResult().get(POINTS2)).stream() + return ((List>) getResultAsMap().get(POINTS2)).stream() .map(ComparisonResult::mapToPoint) .collect(Collectors.toList()); } @@ -104,6 +104,6 @@ public List getPoints2() { public Rectangle getRect2() { verifyPropertyPresence(RECT2); //noinspection unchecked - return mapToRect((Map) getCommandResult().get(RECT2)); + return mapToRect((Map) getResultAsMap().get(RECT2)); } } diff --git a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java index 510f64b8e..ab78d34d4 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java @@ -16,36 +16,139 @@ package io.appium.java_client.imagecomparison; +import lombok.Getter; import org.openqa.selenium.Rectangle; +import java.io.File; +import java.io.IOException; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +@Getter public class OccurrenceMatchingResult extends ComparisonResult { private static final String RECT = "rect"; - private static final String MULTIPLE = "multiple"; + private static final String SCORE = "score"; - private final boolean isAtRoot; + private final boolean hasMultiple; - public OccurrenceMatchingResult(Map input) { - this(input, true); + public OccurrenceMatchingResult(Object input) { + super(input); + hasMultiple = input instanceof List; } - private OccurrenceMatchingResult(Map input, boolean isAtRoot) { - super(input); - this.isAtRoot = isAtRoot; + private List getMultipleMatches(boolean throwIfEmpty) { + if (!hasMultiple) { + throw new IllegalStateException(String.format( + "This %s does not represent multiple matches. Did you set options properly?", + getClass().getSimpleName() + )); + } + //noinspection unchecked + var matches = ((List>) commandResult).stream() + .map(OccurrenceMatchingResult::new) + .collect(Collectors.toList()); + if (matches.isEmpty() && throwIfEmpty) { + throw new IllegalStateException("Zero matches have been found. Try the lookup with different options."); + } + return matches; + } + + private OccurrenceMatchingResult getMatch(int index) { + var matches = getMultipleMatches(true); + if (index < 0 || index >= matches.size()) { + throw new IndexOutOfBoundsException(String.format( + "The match #%s does not exist. The total number of found matches is %s", + index, matches.size() + )); + } + return matches.get(index); } /** - * Returns rectangle of partial image occurrence. + * Returns rectangle of the partial image occurrence. * * @return The region of the partial image occurrence on the full image. */ public Rectangle getRect() { + if (hasMultiple) { + return getRect(0); + } verifyPropertyPresence(RECT); //noinspection unchecked - return mapToRect((Map) getCommandResult().get(RECT)); + return mapToRect((Map) getResultAsMap().get(RECT)); + } + + /** + * Returns rectangle of the partial image occurrence for the given match index. + * + * @param matchIndex Match index. + * @return Matching rectangle. + * @throws IllegalStateException If the current instance does not represent multiple matches. + */ + public Rectangle getRect(int matchIndex) { + return getMatch(matchIndex).getRect(); + } + + /** + * Returns the score of the partial image occurrence. + * + * @return Matching score in range 0..1. + */ + public double getScore() { + if (hasMultiple) { + return getScore(0); + } + verifyPropertyPresence(SCORE); + var value = getResultAsMap().get(SCORE); + if (value instanceof Long) { + return ((Long) value).doubleValue(); + } + return ((Double) value); + } + + /** + * Returns the score of the partial image occurrence for the given match index. + * + * @param matchIndex Match index. + * @return Matching score in range 0..1. + * @throws IllegalStateException If the current instance does not represent multiple matches. + */ + public double getScore(int matchIndex) { + return getMatch(matchIndex).getScore(); + } + + /** + * Returns the visualization of the matching result. + * + * @return The visualization of the matching result represented as base64-encoded PNG image. + */ + @Override + public byte[] getVisualization() { + return hasMultiple ? getVisualization(0) : super.getVisualization(); + } + + /** + * Returns the visualization of the partial image occurrence for the given match index. + * + * @param matchIndex Match index. + * @return The visualization of the matching result represented as base64-encoded PNG image. + * @throws IllegalStateException If the current instance does not represent multiple matches. + */ + public byte[] getVisualization(int matchIndex) { + return getMatch(matchIndex).getVisualization(); + } + + /** + * Stores visualization image into the given file. + * + * @param matchIndex Match index. + * @param destination file to save image. + * @throws IOException On file system I/O error. + * @throws IllegalStateException If the current instance does not represent multiple matches. + */ + public void storeVisualization(int matchIndex, File destination) throws IOException { + getMatch(matchIndex).storeVisualization(destination); } /** @@ -54,18 +157,9 @@ public Rectangle getRect() { * * @since Appium 1.21.0 * @return The list containing properties of each single match or an empty list. - * @throws IllegalStateException If the accessor is called on a non-root match instance. + * @throws IllegalStateException If the current instance does not represent multiple matches. */ public List getMultiple() { - if (!isAtRoot) { - throw new IllegalStateException("Only the root match could contain multiple submatches"); - } - verifyPropertyPresence(MULTIPLE); - - //noinspection unchecked - List> multiple = (List>) getCommandResult().get(MULTIPLE); - return multiple.stream() - .map(m -> new OccurrenceMatchingResult(m, false)) - .collect(Collectors.toList()); + return getMultipleMatches(false); } } diff --git a/src/main/java/io/appium/java_client/imagecomparison/SimilarityMatchingResult.java b/src/main/java/io/appium/java_client/imagecomparison/SimilarityMatchingResult.java index 50c388ead..0806e7b53 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/SimilarityMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/SimilarityMatchingResult.java @@ -33,10 +33,9 @@ public SimilarityMatchingResult(Map input) { */ public double getScore() { verifyPropertyPresence(SCORE); - //noinspection unchecked - if (getCommandResult().get(SCORE) instanceof Long) { - return ((Long) getCommandResult().get(SCORE)).doubleValue(); + if (getResultAsMap().get(SCORE) instanceof Long) { + return ((Long) getResultAsMap().get(SCORE)).doubleValue(); } - return (double) getCommandResult().get(SCORE); + return (double) getResultAsMap().get(SCORE); } } From 133f6e4c0ed46a5764b2d78712acbeeafa97fc29 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 14 May 2024 17:24:37 +0200 Subject: [PATCH 2/5] lint --- .../java_client/imagecomparison/OccurrenceMatchingResult.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java index ab78d34d4..715472789 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java @@ -104,7 +104,7 @@ public double getScore() { if (value instanceof Long) { return ((Long) value).doubleValue(); } - return ((Double) value); + return (Double) value; } /** @@ -148,7 +148,7 @@ public byte[] getVisualization(int matchIndex) { * @throws IllegalStateException If the current instance does not represent multiple matches. */ public void storeVisualization(int matchIndex, File destination) throws IOException { - getMatch(matchIndex).storeVisualization(destination); + getMatch(matchIndex).storeVisualization(destination); } /** From 165ae71c0695c11e2409580a66ebefe10d717c77 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 14 May 2024 17:28:25 +0200 Subject: [PATCH 3/5] Add store --- .../imagecomparison/ComparisonResult.java | 2 +- .../OccurrenceMatchingResult.java | 77 ++++++++++++------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java b/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java index 0ad5c40f9..0fba408d4 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java @@ -68,7 +68,7 @@ public byte[] getVisualization() { /** * Stores visualization image into the given file. * - * @param destination file to save image. + * @param destination File path to save the image to. * @throws IOException On file system I/O error. */ public void storeVisualization(File destination) throws IOException { diff --git a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java index 715472789..5ed453052 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java @@ -25,7 +25,6 @@ import java.util.Map; import java.util.stream.Collectors; -@Getter public class OccurrenceMatchingResult extends ComparisonResult { private static final String RECT = "rect"; private static final String SCORE = "score"; @@ -37,32 +36,11 @@ public OccurrenceMatchingResult(Object input) { hasMultiple = input instanceof List; } - private List getMultipleMatches(boolean throwIfEmpty) { - if (!hasMultiple) { - throw new IllegalStateException(String.format( - "This %s does not represent multiple matches. Did you set options properly?", - getClass().getSimpleName() - )); - } - //noinspection unchecked - var matches = ((List>) commandResult).stream() - .map(OccurrenceMatchingResult::new) - .collect(Collectors.toList()); - if (matches.isEmpty() && throwIfEmpty) { - throw new IllegalStateException("Zero matches have been found. Try the lookup with different options."); - } - return matches; - } - - private OccurrenceMatchingResult getMatch(int index) { - var matches = getMultipleMatches(true); - if (index < 0 || index >= matches.size()) { - throw new IndexOutOfBoundsException(String.format( - "The match #%s does not exist. The total number of found matches is %s", - index, matches.size() - )); - } - return matches.get(index); + /** + * @return Whether the current instance contains multiple matches. + */ + public boolean hasMultiple() { + return hasMultiple; } /** @@ -139,11 +117,26 @@ public byte[] getVisualization(int matchIndex) { return getMatch(matchIndex).getVisualization(); } + /** + * Stores visualization image into the given file. + * + * @param destination File path to save the image to. + * @throws IOException On file system I/O error. + */ + @Override + public void storeVisualization(File destination) throws IOException { + if (hasMultiple) { + getMatch(0).storeVisualization(destination); + } else { + super.storeVisualization(destination); + } + } + /** * Stores visualization image into the given file. * * @param matchIndex Match index. - * @param destination file to save image. + * @param destination File path to save the image to. * @throws IOException On file system I/O error. * @throws IllegalStateException If the current instance does not represent multiple matches. */ @@ -162,4 +155,32 @@ public void storeVisualization(int matchIndex, File destination) throws IOExcept public List getMultiple() { return getMultipleMatches(false); } + + private List getMultipleMatches(boolean throwIfEmpty) { + if (!hasMultiple) { + throw new IllegalStateException(String.format( + "This %s does not represent multiple matches. Did you set options properly?", + getClass().getSimpleName() + )); + } + //noinspection unchecked + var matches = ((List>) commandResult).stream() + .map(OccurrenceMatchingResult::new) + .collect(Collectors.toList()); + if (matches.isEmpty() && throwIfEmpty) { + throw new IllegalStateException("Zero matches have been found. Try the lookup with different options."); + } + return matches; + } + + private OccurrenceMatchingResult getMatch(int index) { + var matches = getMultipleMatches(true); + if (index < 0 || index >= matches.size()) { + throw new IndexOutOfBoundsException(String.format( + "The match #%s does not exist. The total number of found matches is %s", + index, matches.size() + )); + } + return matches.get(index); + } } From 1a19050f07807d3213e8a5e33b16f9c0df2cb81e Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 14 May 2024 17:29:08 +0200 Subject: [PATCH 4/5] extra import --- .../java_client/imagecomparison/OccurrenceMatchingResult.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java index 5ed453052..aec4528d8 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java @@ -16,7 +16,6 @@ package io.appium.java_client.imagecomparison; -import lombok.Getter; import org.openqa.selenium.Rectangle; import java.io.File; From 765c55766280567161c3887406b601b9be9cc8ff Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 14 May 2024 17:34:19 +0200 Subject: [PATCH 5/5] lint --- .../java_client/imagecomparison/OccurrenceMatchingResult.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java index aec4528d8..7b0266f23 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java @@ -36,7 +36,9 @@ public OccurrenceMatchingResult(Object input) { } /** - * @return Whether the current instance contains multiple matches. + * Check whether the current instance contains multiple matches. + * + * @return True or false. */ public boolean hasMultiple() { return hasMultiple;