Skip to content

Commit 3efeaeb

Browse files
fix: Properly represent FeaturesMatchingResult model if multiple option is enabled (#2170)
1 parent 6f83f1d commit 3efeaeb

File tree

5 files changed

+153
-36
lines changed

5 files changed

+153
-36
lines changed

src/main/java/io/appium/java_client/ComparesImages.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,7 @@ default OccurrenceMatchingResult findImageOccurrence(byte[] fullImage, byte[] pa
126126
@Nullable OccurrenceMatchingOptions options) {
127127
Object response = CommandExecutionHelper.execute(this,
128128
compareImagesCommand(ComparisonMode.MATCH_TEMPLATE, fullImage, partialImage, options));
129-
//noinspection unchecked
130-
return new OccurrenceMatchingResult((Map<String, Object>) response);
129+
return new OccurrenceMatchingResult(response);
131130
}
132131

133132
/**

src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package io.appium.java_client.imagecomparison;
1818

19-
import lombok.AccessLevel;
20-
import lombok.Getter;
2119
import org.openqa.selenium.Point;
2220
import org.openqa.selenium.Rectangle;
2321

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

35-
@Getter(AccessLevel.PROTECTED) private final Map<String, Object> commandResult;
33+
protected final Object commandResult;
3634

37-
public ComparisonResult(Map<String, Object> commandResult) {
35+
public ComparisonResult(Object commandResult) {
3836
this.commandResult = commandResult;
3937
}
4038

39+
protected Map<String, Object> getResultAsMap() {
40+
//noinspection unchecked
41+
return (Map<String, Object>) commandResult;
42+
}
43+
4144
/**
4245
* Verifies if the corresponding property is present in the commend result
4346
* and throws an exception if not.
4447
*
4548
* @param propertyName the actual property name to be verified for presence
4649
*/
4750
protected void verifyPropertyPresence(String propertyName) {
48-
if (!commandResult.containsKey(propertyName)) {
51+
if (!getResultAsMap().containsKey(propertyName)) {
4952
throw new IllegalStateException(
5053
String.format("There is no '%s' attribute in the resulting command output %s. "
5154
+ "Did you set the options properly?", propertyName, commandResult));
@@ -59,13 +62,13 @@ protected void verifyPropertyPresence(String propertyName) {
5962
*/
6063
public byte[] getVisualization() {
6164
verifyPropertyPresence(VISUALIZATION);
62-
return ((String) getCommandResult().get(VISUALIZATION)).getBytes(StandardCharsets.UTF_8);
65+
return ((String) getResultAsMap().get(VISUALIZATION)).getBytes(StandardCharsets.UTF_8);
6366
}
6467

6568
/**
6669
* Stores visualization image into the given file.
6770
*
68-
* @param destination file to save image.
71+
* @param destination File path to save the image to.
6972
* @throws IOException On file system I/O error.
7073
*/
7174
public void storeVisualization(File destination) throws IOException {

src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingResult.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public FeaturesMatchingResult(Map<String, Object> input) {
4343
*/
4444
public int getCount() {
4545
verifyPropertyPresence(COUNT);
46-
return ((Long) getCommandResult().get(COUNT)).intValue();
46+
return ((Long) getResultAsMap().get(COUNT)).intValue();
4747
}
4848

4949
/**
@@ -56,7 +56,7 @@ public int getCount() {
5656
*/
5757
public int getTotalCount() {
5858
verifyPropertyPresence(TOTAL_COUNT);
59-
return ((Long) getCommandResult().get(TOTAL_COUNT)).intValue();
59+
return ((Long) getResultAsMap().get(TOTAL_COUNT)).intValue();
6060
}
6161

6262
/**
@@ -67,7 +67,7 @@ public int getTotalCount() {
6767
public List<Point> getPoints1() {
6868
verifyPropertyPresence(POINTS1);
6969
//noinspection unchecked
70-
return ((List<Map<String, Object>>) getCommandResult().get(POINTS1)).stream()
70+
return ((List<Map<String, Object>>) getResultAsMap().get(POINTS1)).stream()
7171
.map(ComparisonResult::mapToPoint)
7272
.collect(Collectors.toList());
7373
}
@@ -80,7 +80,7 @@ public List<Point> getPoints1() {
8080
public Rectangle getRect1() {
8181
verifyPropertyPresence(RECT1);
8282
//noinspection unchecked
83-
return mapToRect((Map<String, Object>) getCommandResult().get(RECT1));
83+
return mapToRect((Map<String, Object>) getResultAsMap().get(RECT1));
8484
}
8585

8686
/**
@@ -91,7 +91,7 @@ public Rectangle getRect1() {
9191
public List<Point> getPoints2() {
9292
verifyPropertyPresence(POINTS2);
9393
//noinspection unchecked
94-
return ((List<Map<String, Object>>) getCommandResult().get(POINTS2)).stream()
94+
return ((List<Map<String, Object>>) getResultAsMap().get(POINTS2)).stream()
9595
.map(ComparisonResult::mapToPoint)
9696
.collect(Collectors.toList());
9797
}
@@ -104,6 +104,6 @@ public List<Point> getPoints2() {
104104
public Rectangle getRect2() {
105105
verifyPropertyPresence(RECT2);
106106
//noinspection unchecked
107-
return mapToRect((Map<String, Object>) getCommandResult().get(RECT2));
107+
return mapToRect((Map<String, Object>) getResultAsMap().get(RECT2));
108108
}
109109
}

src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java

Lines changed: 133 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,131 @@
1818

1919
import org.openqa.selenium.Rectangle;
2020

21+
import java.io.File;
22+
import java.io.IOException;
2123
import java.util.List;
2224
import java.util.Map;
2325
import java.util.stream.Collectors;
2426

2527
public class OccurrenceMatchingResult extends ComparisonResult {
2628
private static final String RECT = "rect";
27-
private static final String MULTIPLE = "multiple";
29+
private static final String SCORE = "score";
2830

29-
private final boolean isAtRoot;
31+
private final boolean hasMultiple;
3032

31-
public OccurrenceMatchingResult(Map<String, Object> input) {
32-
this(input, true);
33+
public OccurrenceMatchingResult(Object input) {
34+
super(input);
35+
hasMultiple = input instanceof List;
3336
}
3437

35-
private OccurrenceMatchingResult(Map<String, Object> input, boolean isAtRoot) {
36-
super(input);
37-
this.isAtRoot = isAtRoot;
38+
/**
39+
* Check whether the current instance contains multiple matches.
40+
*
41+
* @return True or false.
42+
*/
43+
public boolean hasMultiple() {
44+
return hasMultiple;
3845
}
3946

4047
/**
41-
* Returns rectangle of partial image occurrence.
48+
* Returns rectangle of the partial image occurrence.
4249
*
4350
* @return The region of the partial image occurrence on the full image.
4451
*/
4552
public Rectangle getRect() {
53+
if (hasMultiple) {
54+
return getRect(0);
55+
}
4656
verifyPropertyPresence(RECT);
4757
//noinspection unchecked
48-
return mapToRect((Map<String, Object>) getCommandResult().get(RECT));
58+
return mapToRect((Map<String, Object>) getResultAsMap().get(RECT));
59+
}
60+
61+
/**
62+
* Returns rectangle of the partial image occurrence for the given match index.
63+
*
64+
* @param matchIndex Match index.
65+
* @return Matching rectangle.
66+
* @throws IllegalStateException If the current instance does not represent multiple matches.
67+
*/
68+
public Rectangle getRect(int matchIndex) {
69+
return getMatch(matchIndex).getRect();
70+
}
71+
72+
/**
73+
* Returns the score of the partial image occurrence.
74+
*
75+
* @return Matching score in range 0..1.
76+
*/
77+
public double getScore() {
78+
if (hasMultiple) {
79+
return getScore(0);
80+
}
81+
verifyPropertyPresence(SCORE);
82+
var value = getResultAsMap().get(SCORE);
83+
if (value instanceof Long) {
84+
return ((Long) value).doubleValue();
85+
}
86+
return (Double) value;
87+
}
88+
89+
/**
90+
* Returns the score of the partial image occurrence for the given match index.
91+
*
92+
* @param matchIndex Match index.
93+
* @return Matching score in range 0..1.
94+
* @throws IllegalStateException If the current instance does not represent multiple matches.
95+
*/
96+
public double getScore(int matchIndex) {
97+
return getMatch(matchIndex).getScore();
98+
}
99+
100+
/**
101+
* Returns the visualization of the matching result.
102+
*
103+
* @return The visualization of the matching result represented as base64-encoded PNG image.
104+
*/
105+
@Override
106+
public byte[] getVisualization() {
107+
return hasMultiple ? getVisualization(0) : super.getVisualization();
108+
}
109+
110+
/**
111+
* Returns the visualization of the partial image occurrence for the given match index.
112+
*
113+
* @param matchIndex Match index.
114+
* @return The visualization of the matching result represented as base64-encoded PNG image.
115+
* @throws IllegalStateException If the current instance does not represent multiple matches.
116+
*/
117+
public byte[] getVisualization(int matchIndex) {
118+
return getMatch(matchIndex).getVisualization();
119+
}
120+
121+
/**
122+
* Stores visualization image into the given file.
123+
*
124+
* @param destination File path to save the image to.
125+
* @throws IOException On file system I/O error.
126+
*/
127+
@Override
128+
public void storeVisualization(File destination) throws IOException {
129+
if (hasMultiple) {
130+
getMatch(0).storeVisualization(destination);
131+
} else {
132+
super.storeVisualization(destination);
133+
}
134+
}
135+
136+
/**
137+
* Stores visualization image into the given file.
138+
*
139+
* @param matchIndex Match index.
140+
* @param destination File path to save the image to.
141+
* @throws IOException On file system I/O error.
142+
* @throws IllegalStateException If the current instance does not represent multiple matches.
143+
*/
144+
public void storeVisualization(int matchIndex, File destination) throws IOException {
145+
getMatch(matchIndex).storeVisualization(destination);
49146
}
50147

51148
/**
@@ -54,18 +151,37 @@ public Rectangle getRect() {
54151
*
55152
* @since Appium 1.21.0
56153
* @return The list containing properties of each single match or an empty list.
57-
* @throws IllegalStateException If the accessor is called on a non-root match instance.
154+
* @throws IllegalStateException If the current instance does not represent multiple matches.
58155
*/
59156
public List<OccurrenceMatchingResult> getMultiple() {
60-
if (!isAtRoot) {
61-
throw new IllegalStateException("Only the root match could contain multiple submatches");
62-
}
63-
verifyPropertyPresence(MULTIPLE);
157+
return getMultipleMatches(false);
158+
}
64159

160+
private List<OccurrenceMatchingResult> getMultipleMatches(boolean throwIfEmpty) {
161+
if (!hasMultiple) {
162+
throw new IllegalStateException(String.format(
163+
"This %s does not represent multiple matches. Did you set options properly?",
164+
getClass().getSimpleName()
165+
));
166+
}
65167
//noinspection unchecked
66-
List<Map<String, Object>> multiple = (List<Map<String, Object>>) getCommandResult().get(MULTIPLE);
67-
return multiple.stream()
68-
.map(m -> new OccurrenceMatchingResult(m, false))
168+
var matches = ((List<Map<String, Object>>) commandResult).stream()
169+
.map(OccurrenceMatchingResult::new)
69170
.collect(Collectors.toList());
171+
if (matches.isEmpty() && throwIfEmpty) {
172+
throw new IllegalStateException("Zero matches have been found. Try the lookup with different options.");
173+
}
174+
return matches;
175+
}
176+
177+
private OccurrenceMatchingResult getMatch(int index) {
178+
var matches = getMultipleMatches(true);
179+
if (index < 0 || index >= matches.size()) {
180+
throw new IndexOutOfBoundsException(String.format(
181+
"The match #%s does not exist. The total number of found matches is %s",
182+
index, matches.size()
183+
));
184+
}
185+
return matches.get(index);
70186
}
71187
}

src/main/java/io/appium/java_client/imagecomparison/SimilarityMatchingResult.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@ public SimilarityMatchingResult(Map<String, Object> input) {
3333
*/
3434
public double getScore() {
3535
verifyPropertyPresence(SCORE);
36-
//noinspection unchecked
37-
if (getCommandResult().get(SCORE) instanceof Long) {
38-
return ((Long) getCommandResult().get(SCORE)).doubleValue();
36+
if (getResultAsMap().get(SCORE) instanceof Long) {
37+
return ((Long) getResultAsMap().get(SCORE)).doubleValue();
3938
}
40-
return (double) getCommandResult().get(SCORE);
39+
return (double) getResultAsMap().get(SCORE);
4140
}
4241
}

0 commit comments

Comments
 (0)