001/**
002 * Copyright (c) 2012, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.twitter;
031
032import java.io.IOException;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.HashMap;
036import java.util.List;
037import java.util.Map;
038import java.util.Scanner;
039
040import org.openimaj.twitter.USMFStatus.Link;
041import org.openimaj.twitter.USMFStatus.User;
042
043/**
044 * GeneralJSONTwitter extends GeneralJSON to provide an object that GSon can
045 * fill from a twitter json string. It can also then be used by USMFFStatus to
046 * fill a USMFSStatus with the relevant twitter fields.
047 *
048 * @author Laurence Willmore (lgw1e10@ecs.soton.ac.uk), Sina Samangooei (ss@ecs.soton.ac.uk)
049 *
050 */
051public class GeneralJSONTwitter extends GeneralJSON {
052
053
054
055        /*
056         * Twitter has no service field, therefore created.
057         */
058        /**
059         *
060         */
061        public String s = "twitter";
062
063        /**
064         * New style retweets
065         */
066        public GeneralJSONTwitter retweeted_status;
067
068        /*
069         * Named fields used by Gson to build object from JSON text
070         */
071        /**
072         * This is a string because sometimes retweet_count can look like: "100+"
073         */
074        public String retweet_count;
075        /**
076         *
077         */
078        public String in_reply_to_screen_name;
079        /**
080         *
081         */
082        public String text;
083        /**
084         *
085         */
086        public Map<String, List<Map<String, Object>>> entities = null;
087        /**
088         *
089         */
090        public Map<String, Object> user = null;
091
092        /**
093         *
094         */
095        public Map<String, Object> place = null;
096        /**
097         *
098         */
099        public Object geo;
100        /**
101         *
102         */
103        public Object coordinates = null;
104        /**
105         *
106         */
107        public boolean retweeted;
108        /**
109         *
110         */
111
112        public double in_reply_to_status_id;
113        /**
114         *
115         */
116        public double in_reply_to_user_id;
117        /**
118         *
119         */
120        public boolean truncated;
121        /**
122         *
123         */
124        public long id;
125        /**
126         *
127         */
128        public String created_at;
129
130        /**
131         *
132         */
133        public String source;
134
135        /**
136         *
137         */
138        public String id_str;
139
140        @SuppressWarnings("unchecked")
141        @Override
142        public void fillUSMF(USMFStatus status) {
143                // Populate message fields
144                status.application = this.source;
145                status.date = this.created_at;
146                if (this.coordinates != null) {
147                        double[] coords = new double[2];
148                        ArrayList<Double> coordList = null;
149                        if(coordinates instanceof Map){
150                                coordList = (ArrayList<Double>)((Map<?,?>)coordinates).get("coordinates");
151                        }
152                        coords[0] = coordList.get(0);
153                        coords[1] = coordList.get(1);
154                        status.geo = coords;
155                }
156                if(this.place != null && place.containsKey("name") && place.containsKey("country_code")){
157                        status.location = (String) place.get("name");
158                        status.country_code = (String) place.get("country_code");
159                }
160                status.id = this.id;
161                status.text = this.text;
162                status.service = "Twitter";
163
164                // Check if user is null, and make invalid if it is
165                if (this.user != null)
166                {
167                        // Populate the User
168                        String key = "profile_image_url";
169                        if (this.user.containsKey(key) && this.user.get(key) != null)
170                                status.user.avatar = (String) this.user.get(key);
171                        key = "description";
172                        if (this.user.containsKey(key) && this.user.get(key) != null)
173                                status.user.description = (String) this.user.get(key);
174                        key = "id";
175                        if (this.user.containsKey(key) && this.user.get(key) != null)
176                                status.user.id = (Double) this.user.get("id");
177                        key = "lang";
178                        if (this.user.containsKey(key) && this.user.get(key) != null)
179                                status.user.language = (String) this.user.get("lang");
180                        key = "statuses_count";
181                        if (this.user.containsKey(key) && this.user.get(key) != null)
182                                status.user.postings = (Double) this.user.get("statuses_count");
183                        key = "name";
184                        if (this.user.containsKey(key) && this.user.get(key) != null)
185                                status.user.real_name = (String) this.user.get("name");
186                        key = "screen_name";
187                        if (this.user.containsKey(key) && this.user.get(key) != null)
188                                status.user.name = (String) this.user.get("screen_name");
189                        key = "followers_count";
190                        if (this.user.containsKey(key) && this.user.get(key) != null)
191                                status.user.subscribers = (Double) this.user.get("followers_count");
192                        key = "utc_offset";
193                        if (this.user.containsKey(key) && this.user.get(key) != null)
194                                status.user.utc = (Double) this.user.get("utc_offset");
195                        key = "url";
196                        if (this.user.containsKey(key) && this.user.get(key) != null)
197                                status.user.website = (String) this.user.get("url");
198                }
199
200
201
202                // Populate the links
203                if (entities != null) {
204                        for (Map<String, Object> link : entities.get("urls")) {
205                                USMFStatus.Link l = new USMFStatus.Link();
206                                Object url = link.get("expanded_url");
207                                if (url != null) {
208                                        l.href = (String) url;
209                                        status.links.add(l);
210                                }
211                        }
212
213                        // Populate the keywords from hashtags
214                        for (Map<String, Object> tag : entities.get("hashtags")) {
215                                Object st = tag.get("text");
216                                if (st != null) {
217                                        status.keywords.add((String) st);
218                                }
219                        }
220
221                        // Populate the to users from user mentions
222                        for (Map<String, Object> user : entities.get("user_mentions")) {
223                                USMFStatus.User u = new USMFStatus.User();
224                                u.name = (String) user.get("screen_name");
225                                u.real_name = (String) user.get("name");
226                                u.id = (Double) user.get("id");
227                                status.to_users.add(u);
228                        }
229                        if(this.in_reply_to_screen_name != null){
230                                status.reply_to = new User();
231                                status.reply_to.name = this.in_reply_to_screen_name;
232                                status.reply_to.id = this.in_reply_to_user_id;
233
234                        }
235                }
236                this.fillAnalysis(status);
237        }
238
239        @Override
240        public void fromUSMF(USMFStatus status) {
241                // Populate message fields
242                this.source = status.application;
243                this.created_at = status.date;
244                this.geo = fillCoord(status.geo);
245
246                this.id = status.id;
247                this.text = status.text;
248                if(status.reply_to!=null){
249                        this.in_reply_to_screen_name = status.reply_to.name;
250                        this.in_reply_to_user_id = status.reply_to.id;
251                }
252                this.user = fillUserMap(status.user);
253                this.entities = fillEntities(status.links,status.keywords,status.to_users);
254                status.fillAnalysis(this);
255
256        }
257
258        private static Map<String, Object> fillCoord(double[] geo) {
259                Map<String, Object> coord = new HashMap<String,Object>();
260                coord.put("type", "Point");
261                coord.put("coordinates", Arrays.asList(geo));
262                return coord;
263        }
264
265        private static Map<String, List<Map<String, Object>>> fillEntities(ArrayList<Link> links, ArrayList<String> keywords,ArrayList<User> to_users) {
266                Map<String, List<Map<String, Object>>> ents = new HashMap<String, List<Map<String,Object>>>();
267                ents.put("urls", fillURLsList(links));
268                ents.put("hashtags", fillHashtagsList(keywords));
269                ents.put("user_mentions", fillMentionsList(to_users));
270                return ents ;
271        }
272
273        private static List<Map<String, Object>> fillMentionsList(ArrayList<User> to_users) {
274                List<Map<String, Object>> ret = new ArrayList<Map<String,Object>>();
275                for (User user : to_users) {
276                        ret.add(fillUserMap(user));
277                }
278                return ret;
279        }
280
281        private static List<Map<String, Object>> fillHashtagsList(ArrayList<String> keywords) {
282                List<Map<String, Object>> ret = new ArrayList<Map<String,Object>>();
283                for (String string : keywords) {
284                        Map<String, Object> item = new HashMap<String, Object>();
285                        item.put("text", string);
286                        ret.add(item);
287                }
288                return ret;
289        }
290
291        private static List<Map<String, Object>> fillURLsList(ArrayList<Link> links) {
292                List<Map<String, Object>> urls = new ArrayList<Map<String,Object>>();
293                for (Link link : links) {
294                        urls.add(fillURL(link));
295                }
296                return urls;
297        }
298
299        private static Map<String, Object> fillURL(Link link) {
300                Map<String, Object> ret = new HashMap<String, Object>();
301                // Maybe get more for the extras field?
302                ret.put("url", link.href);
303                return ret ;
304        }
305
306        private static Map<String, Object> fillUserMap(User user) {
307                Map<String, Object> map = new HashMap<String,Object>();
308                // Populate the User
309                String key = "profile_image_url";
310                fillMapEntry(map,key,user.avatar);
311                key = "description";
312                fillMapEntry(map,key,user.description);
313                key = "id";
314                fillMapEntry(map,key,user.id);
315                key = "lang";
316                fillMapEntry(map,key,user.language);
317                key = "statuses_count";
318                fillMapEntry(map,key,user.postings);
319                key = "name";
320                fillMapEntry(map,key,user.real_name);
321                key = "screen_name";
322                fillMapEntry(map,key,user.name);
323                key = "followers_count";
324                fillMapEntry(map,key,user.subscribers);
325                key = "utc_offset";
326                fillMapEntry(map,key,user.utc);
327                key = "url";
328                fillMapEntry(map,key,user.website);
329                return map;
330        }
331
332        private static void fillMapEntry(Map<String, Object> map, String key, Object value) {
333                if(value!=null) map.put(key, value);
334        }
335
336        @Override
337        public void readASCII(Scanner in) throws IOException {
338                USMFStatus status = new USMFStatus(this.getClass());
339                status.readASCII(in);
340                this.fromUSMF(status);
341        }
342
343        @Override
344        public GeneralJSON instanceFromString(String line){
345                GeneralJSONTwitter jsonInstance = null;
346                try {
347                        jsonInstance = gson.fromJson(line, GeneralJSONTwitter.class);
348                } catch (Throwable e) {
349                        throw new RuntimeException(e);
350                }
351                if (jsonInstance.id == 0) {
352                        GeneralJSONTwitterRawText raw = new GeneralJSONTwitterRawText();
353                        raw .text = jsonInstance.text;
354                        return raw;
355                }
356                return jsonInstance;
357        }
358
359
360
361}