View Javadoc

1   /*
2    * Copyright (C) 2010 Atlassian
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.atlassian.jira.rest.client.internal.json;
18  
19  import com.atlassian.jira.rest.client.api.domain.BasicUser;
20  import com.atlassian.jira.rest.client.api.ExpandableProperty;
21  import com.atlassian.jira.rest.client.api.OptionalIterable;
22  import com.atlassian.jira.rest.client.api.RestClientException;
23  import com.google.common.base.Optional;
24  import com.google.common.collect.Maps;
25  import org.codehaus.jettison.json.JSONArray;
26  import org.codehaus.jettison.json.JSONException;
27  import org.codehaus.jettison.json.JSONObject;
28  import org.joda.time.DateTime;
29  import org.joda.time.format.DateTimeFormat;
30  import org.joda.time.format.DateTimeFormatter;
31  import org.joda.time.format.ISODateTimeFormat;
32  
33  import javax.annotation.Nullable;
34  import java.net.URI;
35  import java.net.URISyntaxException;
36  import java.util.ArrayList;
37  import java.util.Collection;
38  import java.util.Iterator;
39  import java.util.Map;
40  
41  public class JsonParseUtil {
42  	public static final String JIRA_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
43  	public static final DateTimeFormatter JIRA_DATE_TIME_FORMATTER = DateTimeFormat.forPattern(JIRA_DATE_TIME_PATTERN);
44  	public static final DateTimeFormatter JIRA_DATE_FORMATTER = ISODateTimeFormat.date();
45  	public static final String SELF_ATTR = "self";
46  
47  	public static <T> Collection<T> parseJsonArray(final JSONArray jsonArray, final JsonObjectParser<T> jsonParser)
48  			throws JSONException {
49  		final Collection<T> res = new ArrayList<T>(jsonArray.length());
50  		for (int i = 0; i < jsonArray.length(); i++) {
51  			res.add(jsonParser.parse(jsonArray.getJSONObject(i)));
52  		}
53  		return res;
54  	}
55  
56  	public static <T> OptionalIterable<T> parseOptionalJsonArray(final JSONArray jsonArray, final JsonObjectParser<T> jsonParser)
57  			throws JSONException {
58  		if (jsonArray == null) {
59  			return OptionalIterable.absent();
60  		} else {
61  			return new OptionalIterable<T>(JsonParseUtil.<T>parseJsonArray(jsonArray, jsonParser));
62  		}
63  	}
64  
65  	@SuppressWarnings("UnusedDeclaration")
66  	public static <T> ExpandableProperty<T> parseExpandableProperty(final JSONObject json, final JsonObjectParser<T> expandablePropertyBuilder)
67  			throws JSONException {
68  		return parseExpandableProperty(json, false, expandablePropertyBuilder);
69  	}
70  
71  	@Nullable
72  	public static <T> ExpandableProperty<T> parseOptionalExpandableProperty(@Nullable final JSONObject json, final JsonObjectParser<T> expandablePropertyBuilder)
73  			throws JSONException {
74  		return parseExpandableProperty(json, true, expandablePropertyBuilder);
75  	}
76  
77  	@Nullable
78  	private static <T> ExpandableProperty<T> parseExpandableProperty(@Nullable final JSONObject json, final Boolean optional, final JsonObjectParser<T> expandablePropertyBuilder)
79  			throws JSONException {
80  		if (json == null) {
81  			if (!optional) {
82  				throw new IllegalArgumentException("json object cannot be null while optional is false");
83  			}
84  			return null;
85  		}
86  
87  		final int numItems = json.getInt("size");
88  		final Collection<T> items;
89  		JSONArray itemsJa = json.getJSONArray("items");
90  
91  		if (itemsJa.length() > 0) {
92  			items = new ArrayList<T>(numItems);
93  			for (int i = 0; i < itemsJa.length(); i++) {
94  				final T item = expandablePropertyBuilder.parse(itemsJa.getJSONObject(i));
95  				items.add(item);
96  			}
97  		} else {
98  			items = null;
99  		}
100 
101 		return new ExpandableProperty<T>(numItems, items);
102 	}
103 
104 
105 	public static URI getSelfUri(final JSONObject jsonObject) throws JSONException {
106 		return parseURI(jsonObject.getString(SELF_ATTR));
107 	}
108 
109 	public static URI optSelfUri(final JSONObject jsonObject, final URI defaultUri) throws JSONException {
110 		final String selfUri = jsonObject.optString(SELF_ATTR, null);
111 		return selfUri != null ? parseURI(selfUri) : defaultUri;
112 	}
113 
114 	@SuppressWarnings("unused")
115 	public static JSONObject getNestedObject(JSONObject json, final String... path) throws JSONException {
116 		for (String s : path) {
117 			json = json.getJSONObject(s);
118 		}
119 		return json;
120 	}
121 
122 	@Nullable
123 	public static JSONObject getNestedOptionalObject(JSONObject json, final String... path) throws JSONException {
124 		for (int i = 0; i < path.length - 1; i++) {
125 			String s = path[i];
126 			json = json.getJSONObject(s);
127 		}
128 		return json.optJSONObject(path[path.length - 1]);
129 	}
130 
131 	@SuppressWarnings("unused")
132 	public static JSONArray getNestedArray(JSONObject json, final String... path) throws JSONException {
133 		for (int i = 0; i < path.length - 1; i++) {
134 			String s = path[i];
135 			json = json.getJSONObject(s);
136 		}
137 		return json.getJSONArray(path[path.length - 1]);
138 	}
139 
140 	public static JSONArray getNestedOptionalArray(JSONObject json, final String... path) throws JSONException {
141 		for (int i = 0; json != null && i < path.length - 1; i++) {
142 			String s = path[i];
143 			json = json.optJSONObject(s);
144 		}
145 		return json == null ? null : json.optJSONArray(path[path.length - 1]);
146 	}
147 
148 
149 	public static String getNestedString(JSONObject json, final String... path) throws JSONException {
150 		for (int i = 0; i < path.length - 1; i++) {
151 			String s = path[i];
152 			json = json.getJSONObject(s);
153 		}
154 		return json.getString(path[path.length - 1]);
155 	}
156 
157 	@SuppressWarnings("unused")
158 	public static boolean getNestedBoolean(JSONObject json, final String... path) throws JSONException {
159 		for (int i = 0; i < path.length - 1; i++) {
160 			String s = path[i];
161 			json = json.getJSONObject(s);
162 		}
163 		return json.getBoolean(path[path.length - 1]);
164 	}
165 
166 
167 	public static URI parseURI(final String str) {
168 		try {
169 			return new URI(str);
170 		} catch (URISyntaxException e) {
171 			throw new RestClientException(e);
172 		}
173 	}
174 
175 	@Nullable
176 	public static URI parseOptionalURI(final JSONObject jsonObject, final String attributeName) {
177 		final String s = getOptionalString(jsonObject, attributeName);
178 		return s != null ? parseURI(s) : null;
179 	}
180 
181 	@Nullable
182 	public static BasicUser parseBasicUser(@Nullable final JSONObject json) throws JSONException {
183 		if (json == null) {
184 			return null;
185 		}
186 		final String username = json.getString("name");
187 		if (!json.has(JsonParseUtil.SELF_ATTR) && "Anonymous".equals(username)) {
188 			return null; // insane representation for unassigned user - JRADEV-4262
189 		}
190 
191 		// deleted user? BUG in REST API: JRA-30263
192 		final URI selfUri = optSelfUri(json, BasicUser.INCOMPLETE_URI);
193 		return new BasicUser(selfUri, username, json.optString("displayName", null));
194 	}
195 
196 	public static DateTime parseDateTime(final JSONObject jsonObject, final String attributeName) throws JSONException {
197 		return parseDateTime(jsonObject.getString(attributeName));
198 	}
199 
200 	@Nullable
201 	public static DateTime parseOptionalDateTime(final JSONObject jsonObject, final String attributeName) throws JSONException {
202 		final String s = getOptionalString(jsonObject, attributeName);
203 		return s != null ? parseDateTime(s) : null;
204 	}
205 
206 	public static DateTime parseDateTime(final String str) {
207 		try {
208 			return JIRA_DATE_TIME_FORMATTER.parseDateTime(str);
209 		} catch (Exception e) {
210 			throw new RestClientException(e);
211 		}
212 	}
213 
214 	/**
215 	 * Tries to parse date and time and return that. If fails then tries to parse date only.
216 	 *
217 	 * @param str String contains either date and time or date only
218 	 * @return date and time or date only
219 	 */
220 	public static DateTime parseDateTimeOrDate(final String str) {
221 		try {
222 			return JIRA_DATE_TIME_FORMATTER.parseDateTime(str);
223 		} catch (Exception ignored) {
224 			try {
225 				return JIRA_DATE_FORMATTER.parseDateTime(str);
226 			} catch (Exception e) {
227 				throw new RestClientException(e);
228 			}
229 		}
230 	}
231 
232 	public static DateTime parseDate(final String str) {
233 		try {
234 			return JIRA_DATE_FORMATTER.parseDateTime(str);
235 		} catch (Exception e) {
236 			throw new RestClientException(e);
237 		}
238 	}
239 
240 	public static String formatDate(final DateTime dateTime) {
241 		return JIRA_DATE_FORMATTER.print(dateTime);
242 	}
243 
244 	@SuppressWarnings("unused")
245 	public static String formatDateTime(final DateTime dateTime) {
246 		return JIRA_DATE_TIME_FORMATTER.print(dateTime);
247 	}
248 
249 
250 	@Nullable
251 	public static String getNullableString(final JSONObject jsonObject, final String attributeName) throws JSONException {
252 		final Object o = jsonObject.get(attributeName);
253 		if (o == JSONObject.NULL) {
254 			return null;
255 		}
256 		return o.toString();
257 	}
258 
259 
260 	@Nullable
261 	public static String getOptionalString(final JSONObject jsonObject, final String attributeName) {
262 		final Object res = jsonObject.opt(attributeName);
263 		if (res == JSONObject.NULL || res == null) {
264 			return null;
265 		}
266 		return res.toString();
267 	}
268 
269 	@SuppressWarnings("unused")
270 	@Nullable
271 	public static JSONObject getOptionalJsonObject(final JSONObject jsonObject, final String attributeName) {
272 		final JSONObject res = jsonObject.optJSONObject(attributeName);
273 		if (res == JSONObject.NULL || res == null) {
274 			return null;
275 		}
276 		return res;
277 	}
278 
279 
280 	public static Collection<String> toStringCollection(final JSONArray jsonArray) throws JSONException {
281 		final ArrayList<String> res = new ArrayList<String>(jsonArray.length());
282 		for (int i = 0; i < jsonArray.length(); i++) {
283 			res.add(jsonArray.getString(i));
284 		}
285 		return res;
286 	}
287 
288 	public static Integer parseOptionInteger(final JSONObject json, final String attributeName) throws JSONException {
289 		return json.has(attributeName) ? json.getInt(attributeName) : null;
290 	}
291 
292 	@Nullable
293 	public static Long getOptionalLong(final JSONObject jsonObject, final String attributeName) throws JSONException {
294 		return jsonObject.has(attributeName) ? jsonObject.getLong(attributeName) : null;
295 	}
296 
297 	public static Optional<JSONArray> getOptionalArray(final JSONObject jsonObject, final String attributeName)
298 			throws JSONException {
299 		return jsonObject.has(attributeName) ?
300 				Optional.of(jsonObject.getJSONArray(attributeName)) : Optional.<JSONArray>absent();
301 	}
302 
303 	public static Map<String, URI> getAvatarUris(final JSONObject jsonObject) throws JSONException {
304 		Map<String, URI> uris = Maps.newHashMap();
305 
306 		final Iterator iterator = jsonObject.keys();
307 		while (iterator.hasNext()) {
308 			final Object o = iterator.next();
309 			if (!(o instanceof String)) {
310 				throw new JSONException(
311 						"Cannot parse URIs: key is expected to be valid String. Got " + (o == null ? "null" : o.getClass())
312 								+ " instead.");
313 			}
314 			final String key = (String) o;
315 			uris.put(key, JsonParseUtil.parseURI(jsonObject.getString(key)));
316 		}
317 		return uris;
318 	}
319 
320 	@SuppressWarnings("unchecked")
321 	public static Iterator<String> getStringKeys(final JSONObject json) {
322 		return json.keys();
323 	}
324 
325 	public static Map<String, String> toStringMap(final JSONArray names, final JSONObject values) throws JSONException {
326 		final Map<String, String> result = Maps.newHashMap();
327 		for (int i = 0; i < names.length(); i++) {
328 			final String key = names.getString(i);
329 			result.put(key, values.getString(key));
330 		}
331 		return result;
332 	}
333 }