Skip to content

Commit 15c7a90

Browse files
authored
Support comparing to another fields (#46)
1 parent 4229e56 commit 15c7a90

15 files changed

+488
-135
lines changed

src/main/java/com/yahoo/bullet/parsing/FieldTypeAdapterFactory.java

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import com.google.gson.Gson;
99
import com.google.gson.JsonElement;
10+
import com.google.gson.JsonObject;
1011
import com.google.gson.JsonParseException;
1112
import com.google.gson.TypeAdapter;
1213
import com.google.gson.TypeAdapterFactory;
@@ -16,13 +17,10 @@
1617
import com.google.gson.stream.JsonWriter;
1718

1819
import java.io.IOException;
19-
import java.util.HashSet;
2020
import java.util.LinkedHashMap;
21-
import java.util.List;
2221
import java.util.Map;
2322
import java.util.Objects;
24-
import java.util.Set;
25-
import java.util.function.Function;
23+
import java.util.function.Predicate;
2624

2725
/**
2826
* Adapted from * Google GSON's RuntimeTypeAdapterFactory to support a field based adapter. Instead of adding a new
@@ -43,39 +41,34 @@
4341
*/
4442
public class FieldTypeAdapterFactory<T> implements TypeAdapterFactory {
4543
private final Class<T> base;
46-
private final Map<Class<?>, Set<String>> registeredTypes = new LinkedHashMap<>();
47-
private final Function<JsonElement, String> extractor;
44+
private final Map<Class<?>, Predicate<JsonObject>> registeredTypes = new LinkedHashMap<>();
4845

49-
private FieldTypeAdapterFactory(Class<T> base, Function<JsonElement, String> extractor) {
46+
private FieldTypeAdapterFactory(Class<T> base) {
5047
this.base = base;
51-
this.extractor = extractor;
5248
}
5349

5450
/**
5551
* Creates a FieldTypeAdapterFactory of this type.
5652
*
5753
* @param base The base type for all types that this factory handles.
58-
* @param fieldExtractor A {@link Function} that takes a JSONElement and returns the extracted String field that
59-
* determines the type.
6054
* @param <T> The base type.
6155
* @return The created factory.
6256
*/
63-
public static <T> FieldTypeAdapterFactory<T> of(Class<T> base, Function<JsonElement, String> fieldExtractor) {
64-
return new FieldTypeAdapterFactory<>(base, fieldExtractor);
57+
public static <T> FieldTypeAdapterFactory<T> of(Class<T> base) {
58+
return new FieldTypeAdapterFactory<>(base);
6559
}
6660

6761
/**
68-
* Register a subtype for the factory with the values it is to support. If the list of matchingValues is not
69-
* disjoint across the subtypes, the order in which this method was called will determine which subType is matched.
62+
* Register a subtype for the factory with the values it is to support.
7063
*
7164
* @param subType A subtype to handle.
72-
* @param matchingValues The matching values that will decide this class
65+
* @param condition The {@link Predicate} that will decide this class
7366
* @return this object for chaining.
7467
*/
75-
public FieldTypeAdapterFactory<T> registerSubType(Class<? extends T> subType, List<String> matchingValues) {
68+
public FieldTypeAdapterFactory<T> registerSubType(Class<? extends T> subType, Predicate<JsonObject> condition) {
7669
Objects.requireNonNull(subType);
77-
Objects.requireNonNull(matchingValues);
78-
registeredTypes.put(subType, new HashSet<>(matchingValues));
70+
Objects.requireNonNull(condition);
71+
registeredTypes.put(subType, condition);
7972
return this;
8073
}
8174

@@ -88,26 +81,22 @@ public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
8881
for (Class<?> clazz : registeredTypes.keySet()) {
8982
registeredAdapters.put(clazz, gson.getAdapter(clazz));
9083
}
91-
return new FieldTypeAdapter<>(extractor, registeredAdapters, registeredTypes);
84+
return new FieldTypeAdapter<>(registeredAdapters, registeredTypes);
9285
}
9386

9487
// Type checking for R's super type has already happened at registration. It's safe to ignore type check warnings.
9588
@SuppressWarnings("unchecked")
9689
private static class FieldTypeAdapter<R> extends TypeAdapter<R> {
97-
private final Function<JsonElement, String> extractor;
9890
private Map<Class<?>, TypeAdapter<?>> adapters;
99-
private Map<Class<?>, Set<String>> types;
91+
private Map<Class<?>, Predicate<JsonObject>> types;
10092

10193
/**
10294
* Constructor for the adapter that takes an extraction mechanism and map of adapters and types.
10395
*
104-
* @param extractor A {@link Function} that takes a {@link JsonElement} and returns the string field from it.
10596
* @param adapters A Map of Class to TypeAdapters for that Class.
10697
* @param types A Map of Class to the Set of Strings that are to be matched against the output of extractor.
10798
*/
108-
public FieldTypeAdapter(Function<JsonElement, String> extractor, Map<Class<?>, TypeAdapter<?>> adapters,
109-
Map<Class<?>, Set<String>> types) {
110-
this.extractor = extractor;
99+
public FieldTypeAdapter(Map<Class<?>, TypeAdapter<?>> adapters, Map<Class<?>, Predicate<JsonObject>> types) {
111100
this.adapters = adapters;
112101
this.types = types;
113102
}
@@ -122,9 +111,8 @@ public void write(JsonWriter out, R value) throws IOException {
122111
}
123112

124113
private TypeAdapter<R> getAdapterFor(JsonElement element) {
125-
String field = extractor.apply(element);
126-
for (Map.Entry<Class<?>, Set<String>> entry : types.entrySet()) {
127-
if (entry.getValue().contains(field)) {
114+
for (Map.Entry<Class<?>, Predicate<JsonObject>> entry : types.entrySet()) {
115+
if (entry.getValue().test(element.getAsJsonObject())) {
128116
return (TypeAdapter<R>) adapters.get(entry.getKey());
129117
}
130118
}

src/main/java/com/yahoo/bullet/parsing/FilterClause.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,45 @@
2020
import static com.yahoo.bullet.parsing.Clause.Operation.REGEX_LIKE;
2121

2222
@Slf4j @Getter @Setter
23-
public class FilterClause extends Clause {
23+
public abstract class FilterClause<T> extends Clause {
2424
@Expose
25-
private String field;
25+
protected String field;
26+
2627
@Expose
27-
private List<String> values;
28+
protected List<T> values;
2829

2930
// An optimization to cache the compiled patterns per FilterClause rather than redoing it per record
30-
private List<Pattern> patterns;
31+
protected List<Pattern> patterns;
32+
33+
public static final String VALUES_FIELD = "values";
3134

3235
/**
3336
* Default Constructor. GSON recommended.
3437
*/
3538
public FilterClause() {
3639
field = null;
37-
values = null;
3840
operation = null;
41+
values = null;
3942
}
4043

4144
@Override
42-
public void configure(BulletConfig configuration) {
43-
if (operation == REGEX_LIKE) {
44-
patterns = values.stream().map(FilterClause::compile).filter(Objects::nonNull).collect(Collectors.toList());
45-
}
45+
public String toString() {
46+
return "{" + super.toString() + ", " + "field: " + field + ", " + "values: " + values + "}";
4647
}
4748

49+
/**
50+
* Get the value string from an object.
51+
*
52+
* @param value The value object to get from.
53+
* @return The value string.
54+
*/
55+
public abstract String getValue(T value);
56+
4857
@Override
49-
public String toString() {
50-
return "{" + super.toString() + ", " + "field: " + field + ", " + "values: " + values + "}";
58+
public void configure(BulletConfig configuration) {
59+
if (operation == REGEX_LIKE) {
60+
patterns = values.stream().map(v -> FilterClause.compile(getValue(v))).filter(Objects::nonNull).collect(Collectors.toList());
61+
}
5162
}
5263

5364
private static Pattern compile(String value) {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2018, Oath Inc.
3+
* Licensed under the terms of the Apache License, Version 2.0.
4+
* See the LICENSE file associated with the project for terms.
5+
*/
6+
package com.yahoo.bullet.parsing;
7+
8+
import com.google.gson.annotations.Expose;
9+
import com.google.gson.annotations.SerializedName;
10+
import lombok.AllArgsConstructor;
11+
import lombok.Getter;
12+
13+
import java.util.List;
14+
import java.util.stream.Collectors;
15+
16+
public class ObjectFilterClause extends FilterClause<ObjectFilterClause.Value> {
17+
@Getter @AllArgsConstructor
18+
public static class Value {
19+
public enum Kind {
20+
@SerializedName("VALUE")
21+
VALUE,
22+
@SerializedName("FIELD")
23+
FIELD,
24+
@SerializedName("CAST")
25+
CAST
26+
}
27+
@Expose
28+
private Kind kind;
29+
@Expose
30+
private String value;
31+
32+
@Override
33+
public String toString() {
34+
return "{kind: " + kind + ", " + "value: " + value + "}";
35+
}
36+
}
37+
38+
/**
39+
* Default Constructor. GSON recommended.
40+
*/
41+
public ObjectFilterClause() {
42+
super();
43+
}
44+
45+
/**
46+
* Constructor takes a {@link StringFilterClause} object to construct from.
47+
*
48+
* @param stringFilterClause The {@link StringFilterClause} object tor construct from.
49+
*/
50+
public ObjectFilterClause(StringFilterClause stringFilterClause) {
51+
this.operation = stringFilterClause.operation;
52+
this.field = stringFilterClause.field;
53+
this.patterns = stringFilterClause.patterns;
54+
List<String> stringValues = stringFilterClause.getValues();
55+
if (stringValues != null) {
56+
values = stringValues.stream().map(s -> s == null ? null : new Value(Value.Kind.VALUE, s)).collect(Collectors.toList());
57+
}
58+
}
59+
60+
@Override
61+
public String getValue(Value value) {
62+
return value.getValue();
63+
}
64+
}

src/main/java/com/yahoo/bullet/parsing/Parser.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,47 @@
77

88
import com.google.gson.Gson;
99
import com.google.gson.GsonBuilder;
10+
import com.google.gson.JsonArray;
11+
import com.google.gson.JsonElement;
12+
import com.google.gson.JsonObject;
1013
import com.yahoo.bullet.common.BulletConfig;
1114

1215
public class Parser {
1316
private static final FieldTypeAdapterFactory<Clause> CLAUSE_FACTORY =
14-
FieldTypeAdapterFactory.of(Clause.class, t -> t.getAsJsonObject().get(Clause.OPERATION_FIELD).getAsString())
15-
.registerSubType(FilterClause.class, Clause.Operation.RELATIONALS)
16-
.registerSubType(LogicalClause.class, Clause.Operation.LOGICALS);
17+
FieldTypeAdapterFactory.of(Clause.class)
18+
.registerSubType(ObjectFilterClause.class, Parser::isObjectFilterClause)
19+
.registerSubType(StringFilterClause.class, Parser::isStringFilterClause)
20+
.registerSubType(LogicalClause.class, Parser::isLogicalClause);
1721
private static final Gson GSON = new GsonBuilder().registerTypeAdapterFactory(CLAUSE_FACTORY)
1822
.excludeFieldsWithoutExposeAnnotation()
1923
.create();
2024

25+
private static Boolean isFilterClause(JsonObject jsonObject) {
26+
JsonElement jsonElement = jsonObject.get(Clause.OPERATION_FIELD);
27+
return jsonElement != null && Clause.Operation.RELATIONALS.contains(jsonElement.getAsString());
28+
}
29+
30+
private static Boolean isStringFilterClause(JsonObject jsonObject) {
31+
if (!isFilterClause(jsonObject)) {
32+
return false;
33+
}
34+
JsonArray values = (JsonArray) jsonObject.get(FilterClause.VALUES_FIELD);
35+
return values != null && values.size() != 0 && values.get(0).isJsonPrimitive();
36+
}
37+
38+
private static Boolean isObjectFilterClause(JsonObject jsonObject) {
39+
if (!isFilterClause(jsonObject)) {
40+
return false;
41+
}
42+
JsonArray values = (JsonArray) jsonObject.get(FilterClause.VALUES_FIELD);
43+
return values != null && values.size() != 0 && values.get(0).isJsonObject();
44+
}
45+
46+
private static Boolean isLogicalClause(JsonObject jsonObject) {
47+
JsonElement jsonElement = jsonObject.get(Clause.OPERATION_FIELD);
48+
return jsonElement != null && Clause.Operation.LOGICALS.contains(jsonElement.getAsString());
49+
}
50+
2151
/**
2252
* Parses a Query out of the query string.
2353
*
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2018, Oath Inc.
3+
* Licensed under the terms of the Apache License, Version 2.0.
4+
* See the LICENSE file associated with the project for terms.
5+
*/
6+
package com.yahoo.bullet.parsing;
7+
8+
public class StringFilterClause extends FilterClause<String> {
9+
/**
10+
* Default Constructor. GSON recommended.
11+
*/
12+
public StringFilterClause() {
13+
super();
14+
}
15+
16+
@Override
17+
public String getValue(String value) {
18+
return value;
19+
}
20+
}

0 commit comments

Comments
 (0)