Skip to content
This repository was archived by the owner on Jan 31, 2022. It is now read-only.

Commit 3466838

Browse files
author
Clément Le Provost
authored
Merge pull request #177 from algolia/feat/places
Fixes #175.
2 parents b476a94 + c60b2b3 commit 3466838

File tree

16 files changed

+1897
-927
lines changed

16 files changed

+1897
-927
lines changed

algoliasearch/src/main/java/com/algolia/search/saas/AbstractClient.java

Lines changed: 730 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
/*
2+
* Copyright (c) 2016 Algolia
3+
* http://www.algolia.com/
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
24+
package com.algolia.search.saas;
25+
26+
import android.support.annotation.NonNull;
27+
import android.support.annotation.Nullable;
28+
import android.text.TextUtils;
29+
30+
import org.json.JSONArray;
31+
import org.json.JSONException;
32+
33+
import java.io.UnsupportedEncodingException;
34+
import java.net.URLDecoder;
35+
import java.net.URLEncoder;
36+
import java.util.Map;
37+
import java.util.TreeMap;
38+
39+
40+
// ----------------------------------------------------------------------
41+
// IMPLEMENTATION NOTES
42+
// ----------------------------------------------------------------------
43+
// The query parameters are stored as an untyped map of strings.
44+
// This class provides:
45+
// - low-level accessors to the untyped parameters;
46+
// - higher-level, typed accessors.
47+
// The latter simply serialize their values into the untyped map and parse
48+
// them back from it.
49+
// ----------------------------------------------------------------------
50+
51+
/**
52+
* An abstract search query.
53+
*/
54+
public abstract class AbstractQuery {
55+
56+
// ----------------------------------------------------------------------
57+
// Types
58+
// ----------------------------------------------------------------------
59+
60+
/**
61+
* A pair of (latitude, longitude).
62+
* Used in geo-search.
63+
*/
64+
public static final class LatLng {
65+
public final double lat;
66+
public final double lng;
67+
68+
public LatLng(double lat, double lng) {
69+
this.lat = lat;
70+
this.lng = lng;
71+
}
72+
73+
@Override
74+
public boolean equals(Object other) {
75+
return other != null && other instanceof LatLng
76+
&& this.lat == ((LatLng)other).lat && this.lng == ((LatLng)other).lng;
77+
}
78+
79+
@Override
80+
public int hashCode() {
81+
return (int)Math.round(lat * lng % Integer.MAX_VALUE);
82+
}
83+
84+
/**
85+
* Parse a `LatLng` from its string representation.
86+
*
87+
* @param value A string representation of a (latitude, longitude) pair, in the format `12.345,67.890`
88+
* (number of digits may vary).
89+
* @return A `LatLng` instance describing the given geolocation, or `null` if `value` is `null` or does not
90+
* represent a valid geolocation.
91+
*/
92+
@Nullable public static LatLng parse(String value) {
93+
if (value == null) {
94+
return null;
95+
}
96+
String[] components = value.split(",");
97+
if (components.length != 2) {
98+
return null;
99+
}
100+
try {
101+
return new LatLng(Double.valueOf(components[0]), Double.valueOf(components[1]));
102+
} catch (NumberFormatException e) {
103+
return null;
104+
}
105+
}
106+
}
107+
108+
// ----------------------------------------------------------------------
109+
// Fields
110+
// ----------------------------------------------------------------------
111+
112+
/** Query parameters, as an untyped key-value array. */
113+
// NOTE: Using a tree map to have parameters sorted by key on output.
114+
private Map<String, String> parameters = new TreeMap<>();
115+
116+
// ----------------------------------------------------------------------
117+
// Construction
118+
// ----------------------------------------------------------------------
119+
120+
/**
121+
* Construct an empty query.
122+
*/
123+
protected AbstractQuery() {
124+
}
125+
126+
/**
127+
* Clone an existing query.
128+
* @param other The query to be cloned.
129+
*/
130+
protected AbstractQuery(@NonNull AbstractQuery other) {
131+
parameters = new TreeMap<>(other.parameters);
132+
}
133+
134+
// ----------------------------------------------------------------------
135+
// Equality
136+
// ----------------------------------------------------------------------
137+
138+
@Override
139+
public boolean equals(Object other) {
140+
return other != null && other instanceof AbstractQuery && this.parameters.equals(((AbstractQuery)other).parameters);
141+
}
142+
143+
@Override
144+
public int hashCode() {
145+
return parameters.hashCode();
146+
}
147+
148+
// ----------------------------------------------------------------------
149+
// Misc.
150+
// ----------------------------------------------------------------------
151+
152+
/**
153+
* Obtain a debug representation of this query.
154+
* To get the raw query URL part, please see {@link #build()}.
155+
* @return A debug representation of this query.
156+
*/
157+
@Override public @NonNull String toString() {
158+
return String.format("%s{%s}", this.getClass().getSimpleName(), this.build());
159+
}
160+
161+
// ----------------------------------------------------------------------
162+
// Parsing/serialization
163+
// ----------------------------------------------------------------------
164+
165+
/**
166+
* Build the URL query parameter string representing this object.
167+
* @return A string suitable for use inside the query part of a URL (i.e. after the question mark).
168+
*/
169+
public @NonNull String build() {
170+
StringBuilder stringBuilder = new StringBuilder();
171+
try {
172+
for (Map.Entry<String, String> entry : parameters.entrySet()) {
173+
String key = entry.getKey();
174+
if (stringBuilder.length() > 0)
175+
stringBuilder.append('&');
176+
stringBuilder.append(urlEncode(key));
177+
String value = entry.getValue();
178+
if (value != null) {
179+
stringBuilder.append('=');
180+
stringBuilder.append(urlEncode(value));
181+
}
182+
}
183+
} catch (UnsupportedEncodingException e) {
184+
throw new RuntimeException(e); // should never happen: UTF-8 is always supported
185+
}
186+
return stringBuilder.toString();
187+
}
188+
189+
static private String urlEncode(String value) throws UnsupportedEncodingException {
190+
// NOTE: We prefer to have space encoded as `%20` instead of `+`, so we patch `URLEncoder`'s behaviour.
191+
// This works because `+` itself is percent-escaped (into `%2B`).
192+
return URLEncoder.encode(value, "UTF-8").replace("+", "%20");
193+
}
194+
195+
/**
196+
* Parse a URL query parameter string and store the resulting parameters into this query.
197+
* @param queryParameters URL query parameter string.
198+
*/
199+
protected void parseFrom(@NonNull String queryParameters) {
200+
try {
201+
String[] parameters = queryParameters.split("&");
202+
for (String parameter : parameters) {
203+
String[] components = parameter.split("=");
204+
if (components.length < 1 || components.length > 2)
205+
continue; // ignore invalid values
206+
String name = URLDecoder.decode(components[0], "UTF-8");
207+
String value = components.length >= 2 ? URLDecoder.decode(components[1], "UTF-8") : null;
208+
set(name, value);
209+
} // for each parameter
210+
} catch (UnsupportedEncodingException e) {
211+
// Should never happen since UTF-8 is one of the default encodings.
212+
throw new RuntimeException(e);
213+
}
214+
}
215+
216+
protected static Boolean parseBoolean(String value) {
217+
if (value == null) {
218+
return null;
219+
}
220+
if (value.trim().toLowerCase().equals("true")) {
221+
return true;
222+
}
223+
Integer intValue = parseInt(value);
224+
return intValue != null && intValue != 0;
225+
}
226+
227+
protected static Integer parseInt(String value) {
228+
if (value == null) {
229+
return null;
230+
}
231+
try {
232+
return Integer.parseInt(value.trim());
233+
} catch (NumberFormatException e) {
234+
return null;
235+
}
236+
}
237+
238+
protected static String buildJSONArray(String[] values) {
239+
JSONArray array = new JSONArray();
240+
for (String value : values) {
241+
array.put(value);
242+
}
243+
return array.toString();
244+
}
245+
246+
protected static String[] parseArray(String string) {
247+
if (string == null) {
248+
return null;
249+
}
250+
// First try to parse JSON notation.
251+
try {
252+
JSONArray array = new JSONArray(string);
253+
String[] result = new String[array.length()];
254+
for (int i = 0; i < result.length; ++i) {
255+
result[i] = array.optString(i);
256+
}
257+
return result;
258+
}
259+
// Otherwise parse as a comma-separated list.
260+
catch (JSONException e) {
261+
return string.split(",");
262+
}
263+
}
264+
265+
protected static String buildCommaArray(String[] values) {
266+
return TextUtils.join(",", values);
267+
}
268+
269+
protected static String[] parseCommaArray(String string) {
270+
return string == null ? null : string.split(",");
271+
}
272+
273+
/**
274+
* @deprecated Please use {@link LatLng#parse(String)} instead.
275+
*/
276+
@Nullable public static LatLng parseLatLng(String value) {
277+
return LatLng.parse(value);
278+
}
279+
280+
// ----------------------------------------------------------------------
281+
// Low-level (untyped) accessors
282+
// ----------------------------------------------------------------------
283+
284+
/**
285+
* Set a parameter in an untyped fashion.
286+
* This low-level accessor is intended to access parameters that this client does not yet support.
287+
* @param name The parameter's name.
288+
* @param value The parameter's value, or null to remove it.
289+
* It will first be converted to a String by the `toString()` method.
290+
* @return This instance (used to chain calls).
291+
*/
292+
public @NonNull AbstractQuery set(@NonNull String name, @Nullable Object value) {
293+
if (value == null) {
294+
parameters.remove(name);
295+
} else {
296+
parameters.put(name, value.toString());
297+
}
298+
return this;
299+
}
300+
301+
/**
302+
* Get a parameter in an untyped fashion.
303+
* @param name The parameter's name.
304+
* @return The parameter's value, or null if a parameter with the specified name does not exist.
305+
*/
306+
public @Nullable String get(@NonNull String name) {
307+
return parameters.get(name);
308+
}
309+
}

0 commit comments

Comments
 (0)