Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Properly represent FeaturesMatchingResult model if multiple option is enabled #2170

Merged
merged 5 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/main/java/io/appium/java_client/ComparesImages.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object>) response);
return new OccurrenceMatchingResult(response);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -32,20 +30,25 @@
public abstract class ComparisonResult {
private static final String VISUALIZATION = "visualization";

@Getter(AccessLevel.PROTECTED) private final Map<String, Object> commandResult;
protected final Object commandResult;

public ComparisonResult(Map<String, Object> commandResult) {
public ComparisonResult(Object commandResult) {
this.commandResult = commandResult;
}

protected Map<String, Object> getResultAsMap() {
//noinspection unchecked
return (Map<String, Object>) commandResult;
}

/**
* Verifies if the corresponding property is present in the commend result
* and throws an exception if not.
*
* @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));
Expand All @@ -59,13 +62,13 @@ 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);
}

/**
* 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public FeaturesMatchingResult(Map<String, Object> input) {
*/
public int getCount() {
verifyPropertyPresence(COUNT);
return ((Long) getCommandResult().get(COUNT)).intValue();
return ((Long) getResultAsMap().get(COUNT)).intValue();
}

/**
Expand All @@ -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();
}

/**
Expand All @@ -67,7 +67,7 @@ public int getTotalCount() {
public List<Point> getPoints1() {
verifyPropertyPresence(POINTS1);
//noinspection unchecked
return ((List<Map<String, Object>>) getCommandResult().get(POINTS1)).stream()
return ((List<Map<String, Object>>) getResultAsMap().get(POINTS1)).stream()
.map(ComparisonResult::mapToPoint)
.collect(Collectors.toList());
}
Expand All @@ -80,7 +80,7 @@ public List<Point> getPoints1() {
public Rectangle getRect1() {
verifyPropertyPresence(RECT1);
//noinspection unchecked
return mapToRect((Map<String, Object>) getCommandResult().get(RECT1));
return mapToRect((Map<String, Object>) getResultAsMap().get(RECT1));
}

/**
Expand All @@ -91,7 +91,7 @@ public Rectangle getRect1() {
public List<Point> getPoints2() {
verifyPropertyPresence(POINTS2);
//noinspection unchecked
return ((List<Map<String, Object>>) getCommandResult().get(POINTS2)).stream()
return ((List<Map<String, Object>>) getResultAsMap().get(POINTS2)).stream()
.map(ComparisonResult::mapToPoint)
.collect(Collectors.toList());
}
Expand All @@ -104,6 +104,6 @@ public List<Point> getPoints2() {
public Rectangle getRect2() {
verifyPropertyPresence(RECT2);
//noinspection unchecked
return mapToRect((Map<String, Object>) getCommandResult().get(RECT2));
return mapToRect((Map<String, Object>) getResultAsMap().get(RECT2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,131 @@

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;

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<String, Object> input) {
this(input, true);
public OccurrenceMatchingResult(Object input) {
super(input);
hasMultiple = input instanceof List;
}

private OccurrenceMatchingResult(Map<String, Object> input, boolean isAtRoot) {
super(input);
this.isAtRoot = isAtRoot;
/**
* Check whether the current instance contains multiple matches.
*
* @return True or false.
*/
public boolean hasMultiple() {
return hasMultiple;
}

/**
* 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<String, Object>) getCommandResult().get(RECT));
return mapToRect((Map<String, Object>) 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 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 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.
*/
public void storeVisualization(int matchIndex, File destination) throws IOException {
getMatch(matchIndex).storeVisualization(destination);
}

/**
Expand All @@ -54,18 +151,37 @@ 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<OccurrenceMatchingResult> getMultiple() {
if (!isAtRoot) {
throw new IllegalStateException("Only the root match could contain multiple submatches");
}
verifyPropertyPresence(MULTIPLE);
return getMultipleMatches(false);
}

private List<OccurrenceMatchingResult> 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
List<Map<String, Object>> multiple = (List<Map<String, Object>>) getCommandResult().get(MULTIPLE);
return multiple.stream()
.map(m -> new OccurrenceMatchingResult(m, false))
var matches = ((List<Map<String, Object>>) 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ public SimilarityMatchingResult(Map<String, Object> 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);
}
}