001/*
002 * flattr4j - A Java library for Flattr
003 *
004 * Copyright (C) 2011 Richard "Shred" Körber
005 *   http://flattr4j.shredzone.org
006 *
007 * This program is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License / GNU Lesser
009 * General Public License as published by the Free Software Foundation,
010 * either version 3 of the License, or (at your option) any later version.
011 *
012 * Licensed under the Apache License, Version 2.0 (the "License");
013 * you may not use this file except in compliance with the License.
014 *
015 * This program is distributed in the hope that it will be useful,
016 * but WITHOUT ANY WARRANTY; without even the implied warranty of
017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
018 */
019package org.shredzone.flattr4j.connector;
020
021import java.io.Externalizable;
022import java.io.IOException;
023import java.io.ObjectInput;
024import java.io.ObjectOutput;
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Date;
029import java.util.List;
030
031import org.json.JSONArray;
032import org.json.JSONException;
033import org.json.JSONObject;
034import org.json.JSONTokener;
035import org.shredzone.flattr4j.exception.MarshalException;
036
037/**
038 * Represents the raw Flattr data.
039 * <p>
040 * Basically, this is a wrapper around {@link JSONObject}, which takes care for the
041 * {@link JSONException} and also for serialization of JSON structures.
042 *
043 * @author Richard "Shred" Körber
044 */
045public class FlattrObject implements Serializable, Externalizable {
046    private static final long serialVersionUID = -6640392574244365803L;
047
048    private transient JSONObject data;
049
050    /**
051     * Creates a new, empty {@link FlattrObject}.
052     */
053    public FlattrObject() {
054        data = new JSONObject();
055    }
056
057    /**
058     * Creates a {@link FlattrObject} from the given {@link JSONObject}.
059     *
060     * @param data
061     *            {@link JSONObject} to use. It is not cloned. It's contents may be
062     *            changed by this {@link FlattrObject}.
063     */
064    public FlattrObject(JSONObject data) {
065        this.data = data;
066    }
067
068    /**
069     * Creates a {@link FlattrObject} from the given JSON string.
070     *
071     * @param json
072     *            JSON string to initialize the {@link FlattrObject} with
073     */
074    public FlattrObject(String json) {
075        try {
076            this.data = (JSONObject) new JSONTokener(json).nextValue();
077        } catch (JSONException ex) {
078            throw new MarshalException(ex);
079        }
080    }
081
082    /**
083     * Checks if there is a key.
084     *
085     * @param key
086     *            Key to check for
087     * @return {@code true} if there is such a key (value may still be {@code null}).
088     */
089    public boolean has(String key) {
090        return data.has(key);
091    }
092
093    /**
094     * Gets an Object from the given key.
095     *
096     * @param key
097     *            Key to read from
098     * @return Object that was read
099     * @throws MarshalException
100     *             if there was no such key
101     */
102    public Object getObject(String key) {
103        try {
104            return data.get(key);
105        } catch (JSONException ex) {
106            throw new MarshalException(key, ex);
107        }
108    }
109
110    /**
111     * Gets a String from the given key.
112     *
113     * @param key
114     *            Key to read from
115     * @return String that was read
116     * @throws MarshalException
117     *             if there was no such key
118     */
119    public String get(String key) {
120        try {
121            return data.getString(key);
122        } catch (JSONException ex) {
123            throw new MarshalException(key, ex);
124        }
125    }
126
127    /**
128     * Gets a String from the given subKey which is a property of the given key.
129     *
130     * @param key
131     *            Key of the parent object
132     * @param subKey
133     *            Key to read from
134     * @return String that was read
135     * @throws MarshalException
136     *             if there was no such key or subKey
137     */
138    public String getSubString(String key, String subKey) {
139        try {
140            JSONObject obj = data.getJSONObject(key);
141            return obj.getString(subKey);
142        } catch (JSONException ex) {
143            throw new MarshalException(key, ex);
144        }
145    }
146
147    /**
148     * Gets a {@link FlattrObject} from the given key.
149     *
150     * @param key
151     *            Key to read from
152     * @return {@link FlattrObject} that was read
153     * @throws MarshalException
154     *             if there was no such key
155     */
156    public FlattrObject getFlattrObject(String key) {
157        try {
158            return new FlattrObject(data.getJSONObject(key));
159        } catch (JSONException ex) {
160            throw new MarshalException(key, ex);
161        }
162    }
163
164    /**
165     * Gets an integer from the given key.
166     *
167     * @param key
168     *            Key to read from
169     * @return integer that was read
170     * @throws MarshalException
171     *             if there was no such key, or if it did not contain the expected type
172     */
173    public int getInt(String key) {
174        try {
175            return data.getInt(key);
176        } catch (JSONException ex) {
177            throw new MarshalException(key, ex);
178        }
179    }
180
181    /**
182     * Gets a long from the given key.
183     *
184     * @param key
185     *            Key to read from
186     * @return long that was read
187     * @throws MarshalException
188     *             if there was no such key, or if it did not contain the expected type
189     * @since 2.5
190     */
191    public long getLong(String key) {
192        try {
193            return data.getLong(key);
194        } catch (JSONException ex) {
195            throw new MarshalException(key, ex);
196        }
197    }
198
199    /**
200     * Gets a boolean from the given key.
201     *
202     * @param key
203     *            Key to read from
204     * @return boolean that was read
205     * @throws MarshalException
206     *             if there was no such key, or if it did not contain the expected type
207     */
208    public boolean getBoolean(String key) {
209        try {
210            return data.getBoolean(key);
211        } catch (JSONException ex) {
212            throw new MarshalException(key, ex);
213        }
214    }
215
216    /**
217     * Gets a {@link Date} from the given key.
218     *
219     * @param key
220     *            Key to read from
221     * @return {@link Date} that was read, or {@code null} if no date was set
222     * @throws MarshalException
223     *             if there was no such key, or if it did not contain the expected type
224     */
225    public Date getDate(String key) {
226        try {
227            if (data.isNull(key)) {
228                return null;
229            }
230
231            long ts = data.getLong(key);
232            return (ts != 0 ? new Date(ts * 1000L) : null);
233        } catch (JSONException ex) {
234            throw new MarshalException(key, ex);
235        }
236    }
237
238    /**
239     * Gets a collection of String from the given key.
240     *
241     * @param key
242     *            Key to read from
243     * @return Collection of Strings
244     * @throws MarshalException
245     *             if there was no such key, or if it did not contain the expected type
246     */
247    public List<String> getStrings(String key) {
248        try {
249            JSONArray array = data.getJSONArray(key);
250            List<String> result = new ArrayList<String>(array.length());
251            for (int ix = 0; ix < array.length(); ix++) {
252                result.add(array.getString(ix));
253            }
254            return result;
255        } catch (JSONException ex) {
256            throw new MarshalException(key, ex);
257        }
258    }
259
260    /**
261     * Gets a collection of {@link FlattrObject} from the given key.
262     *
263     * @param key
264     *            Key to read from
265     * @return Collection of {@link FlattrObject}
266     * @throws MarshalException
267     *             if there was no such key, or if it did not contain the expected type
268     */
269    public List<FlattrObject> getObjects(String key) {
270        try {
271            JSONArray array = data.getJSONArray(key);
272            List<FlattrObject> result = new ArrayList<FlattrObject>(array.length());
273            for (int ix = 0; ix < array.length(); ix++) {
274                result.add(new FlattrObject(array.getJSONObject(ix)));
275            }
276            return result;
277        } catch (JSONException ex) {
278            throw new MarshalException(key, ex);
279        }
280    }
281
282    /**
283     * Changes the key and sets it to the given value.
284     *
285     * @param key
286     *            Key to write to
287     * @param value
288     *            Value to be written
289     * @throws MarshalException
290     *             if the key could not be changed
291     */
292    public void put(String key, Object value) {
293        try {
294            data.put(key, value);
295        } catch (JSONException ex) {
296            throw new MarshalException(key, ex);
297        }
298    }
299
300    /**
301     * Puts a collection of strings as array object to the given key.
302     *
303     * @param key
304     *            Key to write to
305     * @param value
306     *            Collection of Strings to write
307     * @throws MarshalException
308     *             if the key could not be changed
309     */
310    public void putStrings(String key, Collection<String> value) {
311        try {
312            JSONArray array = new JSONArray();
313            if (value != null) {
314                for (String tag : value) {
315                    array.put(tag);
316                }
317            }
318            data.put(key, array);
319        } catch (JSONException ex) {
320            throw new MarshalException(key, ex);
321        }
322    }
323
324    /**
325     * Returns the current state of the {@link FlattrObject} as JSON string.
326     *
327     * @return JSON representation of the current state
328     */
329    @Override
330    public String toString() {
331        return data.toString();
332    }
333
334    /**
335     * Returns the {@link JSONObject} that represents this {@link FlattrObject}. Note that
336     * changes to this {@link JSONObject} will affect the {@link FlattrObject} as well.
337     *
338     * @return {@link JSONObject}
339     */
340    public JSONObject getJSONObject() {
341        return data;
342    }
343
344    @Override
345    public void writeExternal(ObjectOutput out) throws IOException {
346        out.writeUTF(data.toString());
347    }
348
349    @Override
350    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
351        try {
352            data = new JSONObject(in.readUTF());
353        } catch (JSONException ex) {
354            throw new IOException("JSON deserialization failed", ex);
355        }
356    }
357
358}