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.model;
020
021import java.util.Date;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Set;
025
026import org.shredzone.flattr4j.FlattrService;
027import org.shredzone.flattr4j.connector.FlattrObject;
028import org.shredzone.flattr4j.exception.MarshalException;
029
030/**
031 * A {@link Thing} that has been registered with Flattr. Two {@link Thing} are considered
032 * equal if they contain the same id. Some properties of a {@link Thing} can be modified
033 * by setters. After that, invoke
034 * {@link FlattrService#update(org.shredzone.flattr4j.model.Thing)} to persist the
035 * changes.
036 * <p>
037 * This class is not threadsafe.
038 *
039 * @author Richard "Shred" Körber
040 */
041public class Thing extends Resource implements ThingId, UserId, CategoryId, LanguageId {
042    private static final long serialVersionUID = 2822280427303390055L;
043
044    private transient List<String> tags = null;
045    private transient User user = null;
046    private Set<String> updatedKeys = new HashSet<String>();
047
048    /**
049     * Returns a {@link ThingId} for the given Thing id.
050     *
051     * @param id
052     *            Thing id
053     * @return A {@link ThingId} object for this id
054     */
055    public static ThingId withId(final String id) {
056        return new ThingId() {
057            @Override
058            public String getThingId() {
059                return id;
060            }
061        };
062    }
063
064    public Thing(FlattrObject data) {
065        super(data);
066    }
067
068    /**
069     * Thing's unique id at Flattr.
070     */
071    @Override
072    public String getThingId() {
073        return String.valueOf(data.getInt("id"));
074    }
075
076    /**
077     * URL that returns details of this resource as JSON.
078     */
079    public String getResource() {
080        return data.get("resource");
081    }
082
083    /**
084     * Human readable link to this resource at Flattr.
085     */
086    public String getLink() {
087        return data.get("link");
088    }
089
090    /**
091     * Creation date of the Thing.
092     */
093    public Date getCreated() {
094        return data.getDate("created_at");
095    }
096
097    /**
098     * How many times this Thing was flattred.
099     */
100    public int getClicks() {
101        return data.getInt("flattrs");
102    }
103
104    /**
105     * URL of the Thing.
106     */
107    public String getUrl() {
108        return data.get("url");
109    }
110
111    /**
112     * Title of the Thing.
113     */
114    public String getTitle() {
115        return data.get("title");
116    }
117
118    public void setTitle(String title) {
119        data.put("title", title);
120        updatedKeys.add("title");
121    }
122
123    /**
124     * URL of an image for this Thing. Empty string if there is none.
125     *
126     * @since 2.0
127     */
128    public String getImage() {
129        return (data.has("image") ? data.get("image") : "");
130    }
131
132    /**
133     * User this Thing belongs to.
134     */
135    @Override
136    public String getUserId() {
137        return data.getSubString("owner", "username");
138    }
139
140    /**
141     * User this Thing belongs to.
142     * <p>
143     * All properties are only available when the service was set to full mode!
144     *
145     * @since 2.2
146     */
147    public User getUser() {
148        if (user == null) {
149            user = new User(data.getFlattrObject("owner"));
150        }
151        return user;
152    }
153
154    /**
155     * Category this Thing belongs to.
156     */
157    @Override
158    public String getCategoryId() {
159        return data.get("category");
160    }
161
162    public void setCategory(CategoryId category) {
163        data.put("category", (category != null ? category.getCategoryId() : null));
164        updatedKeys.add("category");
165    }
166
167    /**
168     * A descriptive text about the Thing.
169     */
170    public String getDescription() {
171        return data.get("description");
172    }
173
174    public void setDescription(String description) {
175        data.put("description", description);
176        updatedKeys.add("description");
177    }
178
179    /**
180     * Tags this Thing is tagged with.
181     */
182    public List<String> getTags() {
183        if (tags == null) {
184            tags = data.getStrings("tags");
185        }
186        return tags;
187    }
188
189    public void setTags(List<String> tags) {
190        this.tags = tags;
191        updatedKeys.add("tags");
192    }
193
194    public void addTag(String tag) {
195        if (tags == null) {
196            tags = data.getStrings("tags");
197        }
198        tags.add(tag);
199        updatedKeys.add("tags");
200    }
201
202    /**
203     * Language id of the Thing.
204     */
205    @Override
206    public String getLanguageId() {
207        return data.get("language");
208    }
209
210    public void setLanguage(LanguageId language) {
211        data.put("language", (language != null ? language.getLanguageId() : null));
212        updatedKeys.add("language");
213    }
214
215    /**
216     * Is the Thing hidden from the public list of Things at Flattr?
217     */
218    public boolean isHidden() {
219        return data.getBoolean("hidden");
220    }
221
222    public void setHidden(boolean hidden) {
223        data.put("hidden", hidden);
224        updatedKeys.add("hidden");
225    }
226
227    /**
228     * Is this Thing flattred?
229     *
230     * @since 2.0
231     */
232    public boolean isFlattred() {
233        return data.getBoolean("flattred");
234    }
235
236    /**
237     * Is this Thing subscribed?
238     *
239     * @since 2.6
240     */
241    public boolean isSubscribed() {
242        return data.getBoolean("subscribed");
243    }
244
245    /**
246     * Date of last Flattr. Only available to the owner of this Thing.
247     *
248     * @since 2.0
249     */
250    public Date getLastFlattr() {
251        return data.getDate("last_flattr_at");
252    }
253
254    /**
255     * Date of last Update. Only available to the owner of this Thing.
256     *
257     * @since 2.0
258     */
259    public Date getUpdated() {
260        return data.getDate("updated_at");
261    }
262
263    /**
264     * Merges the contents of a submission to this {@link Thing}. This method is useful if
265     * you want to modify an existing {@link Thing} by a {@link Submission} object.
266     * <p>
267     * For all unset properties of {@link Submission}, the {@link Thing} properties are
268     * cleared. After merging, the {@link Thing} is in a state as if it was created with
269     * the given {@link Submission}. If you only want to change single properties of
270     * {@link Thing}, use the setters instead.
271     * <p>
272     * <em>NOTE:</em> The URL of a {@link Thing} cannot be changed. The {@link Submission}
273     * object must contain either this {@link Thing}'s URL or {@code null} (for
274     * convenience). Otherwise an {@link IllegalArgumentException} is thrown.
275     *
276     * @param submission
277     *            {@link Submission} to merge
278     * @since 2.0
279     */
280    public void merge(Submission submission) {
281        if (submission.getUrl() != null && !this.getUrl().equals(submission.getUrl())) {
282            throw new IllegalArgumentException("Thing URL '" + getUrl()
283                            + "' cannot be changed to '" + submission.getUrl() + "'");
284        }
285
286        this.setCategory(submission.getCategory());
287        this.setTitle(submission.getTitle());
288        this.setDescription(submission.getDescription());
289        this.setLanguage(submission.getLanguage());
290        this.setTags(submission.getTags());
291
292        if (submission.isHidden() != null) {
293            this.setHidden(submission.isHidden());
294        }
295    }
296
297    /**
298     * Returns a {@link FlattrObject} for the updates that have been applied to this
299     * Thing.
300     *
301     * @return {@link FlattrObject} for the updates, or {@code null} if this Thing was not
302     *         modified.
303     * @since 2.0
304     */
305    public FlattrObject toUpdate() {
306        if (updatedKeys.isEmpty()) {
307            return null;
308        }
309
310        data.putStrings("tags", tags);
311
312        FlattrObject result = new FlattrObject();
313        for (String key : updatedKeys) {
314            if ("tags".equals(key)) {
315                // Strangely, tags are expected to be joined in a comma separated string
316                StringBuilder sb = new StringBuilder();
317                for (String tag : tags) {
318                    if (tag.indexOf(',') >= 0) {
319                        throw new MarshalException("tag '" + tag + "' contains invalid character ','");
320                    }
321                    sb.append(',').append(tag);
322                }
323                if (sb.length() > 0) {
324                    sb.deleteCharAt(0);
325                }
326                result.put(key, sb.toString());
327            } else {
328                if (data.has(key)) {
329                    result.put(key, data.getObject(key));
330                }
331            }
332        }
333        return result;
334    }
335
336    /**
337     * Returns the URL of a PDF document containing a QR code of the Thing. The PDF
338     * can be printed, sticked on the wall, and then flattered using a mobile phone.
339     */
340    public String getQrPdfUrl() {
341        return "https://flattr.com/thing/qr/" + getThingId();
342    }
343
344    @Override
345    public boolean equals(Object obj) {
346        String pk = getThingId();
347        if (pk == null || obj == null || !(obj instanceof Thing)) {
348            return false;
349        }
350        return pk.equals(((Thing) obj).getThingId());
351    }
352
353    @Override
354    public int hashCode() {
355        String pk = getThingId();
356        return (pk != null ? pk.hashCode() : 0);
357    }
358
359}