1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.queryable.queries;
18 
19 import static com.android.queryable.annotations.StringQuery.DEFAULT_STRING_QUERY_PARAMETERS_VALUE;
20 import static com.android.queryable.util.ParcelableUtils.writeStringSet;
21 
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 
25 import com.android.bedstead.nene.types.OptionalBoolean;
26 import com.android.queryable.Queryable;
27 import com.android.queryable.QueryableBaseWithMatch;
28 import com.android.queryable.util.ParcelableUtils;
29 
30 import com.google.auto.value.AutoAnnotation;
31 
32 import java.io.Serializable;
33 import java.util.ArrayList;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Objects;
37 import java.util.Set;
38 
39 /** Implementation of {@link StringQuery}. */
40 public final class StringQueryHelper<E extends Queryable>
41         implements StringQuery<E>, Serializable{
42 
43     private static final long serialVersionUID = 1;
44 
45     private final transient E mQuery;
46     // This is required because null is a valid value
47     private boolean mEqualsIsSpecified = false;
48     private String mEqualsValue;
49     private Set<String> mNotEqualsValues = new HashSet<>();
50     private String mStartsWithValue;
51 
52     public static final class StringQueryBase extends
53             QueryableBaseWithMatch<String, StringQueryHelper<StringQueryBase>> {
StringQueryBase()54         StringQueryBase() {
55             super();
56             setQuery(new StringQueryHelper<>(this));
57         }
58 
StringQueryBase(Parcel in)59         StringQueryBase(Parcel in) {
60             super(in);
61         }
62 
63         public static final Parcelable.Creator<StringQueryHelper.StringQueryBase> CREATOR =
64                 new Parcelable.Creator<>() {
65                     public StringQueryHelper.StringQueryBase createFromParcel(Parcel in) {
66                         return new StringQueryHelper.StringQueryBase(in);
67                     }
68 
69                     public StringQueryHelper.StringQueryBase[] newArray(int size) {
70                         return new StringQueryHelper.StringQueryBase[size];
71                     }
72                 };
73     }
74 
StringQueryHelper(E query)75     public StringQueryHelper(E query) {
76         mQuery = query;
77     }
78 
StringQueryHelper(Parcel in)79     private StringQueryHelper(Parcel in) {
80         mQuery = null;
81         mEqualsIsSpecified = in.readBoolean();
82         mEqualsValue = in.readString();
83         mNotEqualsValues = ParcelableUtils.readStringSet(in);
84         mStartsWithValue = in.readString();
85     }
86 
87     @Override
isEqualTo(String string)88     public E isEqualTo(String string) {
89         mEqualsIsSpecified = true;
90         mEqualsValue = string;
91         return mQuery;
92     }
93 
94     @Override
isNotEqualTo(String string)95     public E isNotEqualTo(String string) {
96         mNotEqualsValues.add(string);
97         return mQuery;
98     }
99 
100     @Override
startsWith(String string)101     public E startsWith(String string) {
102         mStartsWithValue = string;
103         return mQuery;
104     }
105 
106     @Override
matchesAnnotation(com.android.queryable.annotations.StringQuery queryAnnotation)107     public E matchesAnnotation(com.android.queryable.annotations.StringQuery queryAnnotation) {
108         if (!queryAnnotation.startsWith().equals(DEFAULT_STRING_QUERY_PARAMETERS_VALUE)) {
109             E unused = startsWith(queryAnnotation.startsWith());
110         }
111         if (!queryAnnotation.isEqualTo().equals(DEFAULT_STRING_QUERY_PARAMETERS_VALUE)) {
112             E unused = isEqualTo(queryAnnotation.isEqualTo());
113         }
114         if (!queryAnnotation.isNotEqualTo().equals(DEFAULT_STRING_QUERY_PARAMETERS_VALUE)) {
115             E unused = isNotEqualTo(queryAnnotation.isNotEqualTo());
116         }
117         if (queryAnnotation.isNull().equals(OptionalBoolean.TRUE)) {
118             E unused = isNull();
119         }
120         if (queryAnnotation.isNull().equals(OptionalBoolean.FALSE)) {
121             E unused = isNotNull();
122         }
123 
124         return mQuery;
125     }
126 
toAnnotation()127     public com.android.queryable.annotations.StringQuery toAnnotation() {
128         return stringQuery(
129                 mStartsWithValue == null ? DEFAULT_STRING_QUERY_PARAMETERS_VALUE : mStartsWithValue,
130                 mEqualsIsSpecified ? mEqualsValue : DEFAULT_STRING_QUERY_PARAMETERS_VALUE);
131     }
132 
133     @AutoAnnotation
stringQuery( String startsWith, String isEqualTo)134     private static com.android.queryable.annotations.StringQuery stringQuery(
135             String startsWith, String isEqualTo) {
136         // TODO: Add support for isNotEqualTo
137         // TODO: Add support for isNull/isNotNull
138         return new AutoAnnotation_StringQueryHelper_stringQuery(
139                 startsWith, isEqualTo
140         );
141     }
142 
143     @Override
isEmptyQuery()144     public boolean isEmptyQuery() {
145         return !mEqualsIsSpecified
146                 && mNotEqualsValues.isEmpty()
147                 && (mStartsWithValue == null || mStartsWithValue.isEmpty());
148     }
149 
150     @Override
matches(String value)151     public boolean matches(String value) {
152         if (mEqualsIsSpecified && !Objects.equals(mEqualsValue, value)) {
153             return false;
154         }
155         if (mNotEqualsValues.contains(value)) {
156             return false;
157         }
158         if (mStartsWithValue != null && (value == null || !value.startsWith(mStartsWithValue))) {
159             return false;
160         }
161 
162         return true;
163     }
164 
matches(StringQueryHelper<?> stringQueryHelper, String value)165     public static boolean matches(StringQueryHelper<?> stringQueryHelper, String value) {
166         return stringQueryHelper.matches(value);
167     }
168 
169     /**
170      * True if this query has not been configured.
171      */
isEmpty()172     public boolean isEmpty() {
173         return mEqualsValue == null && mNotEqualsValues.isEmpty();
174     }
175 
176     /**
177      * True if this query is for an exact string match.
178      */
isQueryingForExactMatch()179     public boolean isQueryingForExactMatch() {
180         return mEqualsValue != null;
181     }
182 
183     @Override
describeQuery(String fieldName)184     public String describeQuery(String fieldName) {
185         List<String> queryStrings = new ArrayList<>();
186         if (mEqualsIsSpecified) {
187             queryStrings.add(fieldName + "=\"" + mEqualsValue + "\"");
188         }
189 
190         for (String notEquals : mNotEqualsValues) {
191             queryStrings.add(fieldName + "!=\"" + notEquals + "\"");
192         }
193 
194         if (mStartsWithValue != null) {
195             queryStrings.add(fieldName + " starts with \"" + mStartsWithValue + "\"");
196         }
197 
198         return Queryable.joinQueryStrings(queryStrings);
199     }
200 
201     @Override
describeContents()202     public int describeContents() {
203         return 0;
204     }
205 
206     @Override
writeToParcel(Parcel out, int flags)207     public void writeToParcel(Parcel out, int flags) {
208         out.writeBoolean(mEqualsIsSpecified);
209         out.writeString(mEqualsValue);
210         writeStringSet(out, mNotEqualsValues);
211         out.writeString(mStartsWithValue);
212     }
213 
214     public static final Parcelable.Creator<StringQueryHelper> CREATOR =
215             new Parcelable.Creator<StringQueryHelper>() {
216                 public StringQueryHelper createFromParcel(Parcel in) {
217                     return new StringQueryHelper(in);
218                 }
219 
220                 public StringQueryHelper[] newArray(int size) {
221                     return new StringQueryHelper[size];
222                 }
223     };
224 
225     @Override
equals(Object o)226     public boolean equals(Object o) {
227         if (this == o) return true;
228         if (!(o instanceof StringQueryHelper)) return false;
229         StringQueryHelper<?> that = (StringQueryHelper<?>) o;
230         return Objects.equals(mEqualsValue, that.mEqualsValue) && Objects.equals(
231                 mNotEqualsValues, that.mNotEqualsValues) && Objects.equals(
232                 mStartsWithValue, that.mStartsWithValue);
233     }
234 
235     @Override
hashCode()236     public int hashCode() {
237         return Objects.hash(mEqualsValue, mNotEqualsValues, mStartsWithValue);
238     }
239 }
240