package com.vaguehope.onosendai.config; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import com.vaguehope.onosendai.util.CollectionHelper; import com.vaguehope.onosendai.util.EqualHelper; import com.vaguehope.onosendai.util.ExcpetionHelper; import com.vaguehope.onosendai.util.LogWrapper; import com.vaguehope.onosendai.util.StringHelper; import com.vaguehope.onosendai.util.Titleable; public class Column implements Titleable { public static final int ID_CACHED = -101; private static final String KEY_ID = "id"; private static final String KEY_TITLE = "title"; private static final String KEY_FEEDS = "feeds"; private static final String KEY_ACCOUNT = "account"; private static final String KEY_RESOURCE = "resource"; private static final String KEY_REFRESH = "refresh"; private static final String KEY_EXCLUDE = "exclude"; private static final String KEY_NOTIFY = "notify"; private static final String KEY_INLINE_MEDIA = "inline_media"; private static final String KEY_HD_MEDIA = "hd_media"; private static final LogWrapper LOG = new LogWrapper("COL"); private final int id; private final String title; private final Set<ColumnFeed> feeds; private final int refreshIntervalMins; private final Set<Integer> excludeColumnIds; private final NotificationStyle notificationStyle; private final InlineMediaStyle inlineMediaStyle; private final boolean hdMedia; public Column (final int id, final Column c) { this(id, c.getTitle(), c.getFeeds(), c.getRefreshIntervalMins(), c.getExcludeColumnIds(), c.getNotificationStyle(), c.getInlineMediaStyle(), c.isHdMedia()); } public Column (final Set<Integer> newExcludeColumnIds, final Column c) { this(c.getId(), c.getTitle(), c.getFeeds(), c.getRefreshIntervalMins(), newExcludeColumnIds, c.getNotificationStyle(), c.getInlineMediaStyle(), c.isHdMedia()); } public Column ( final int id, final String title, final ColumnFeed feed, final int refreshIntervalMins, final Set<Integer> excludeColumnIds, final NotificationStyle notificationStyle, final InlineMediaStyle inlineMediaStyle, final boolean hdMedia) { this(id, title, feed != null ? Collections.singleton(feed) : null, refreshIntervalMins, excludeColumnIds, notificationStyle, inlineMediaStyle, hdMedia); } public Column ( final int id, final String title, final Set<ColumnFeed> feeds, final int refreshIntervalMins, final Set<Integer> excludeColumnIds, final NotificationStyle notificationStyle, final InlineMediaStyle inlineMediaStyle, final boolean hdMedia) { this.id = id; this.title = title; this.feeds = feeds != null ? Collections.unmodifiableSet(CollectionHelper.assertNoNulls(feeds)) : null; this.refreshIntervalMins = refreshIntervalMins; this.excludeColumnIds = excludeColumnIds; this.notificationStyle = notificationStyle; this.inlineMediaStyle = inlineMediaStyle; this.hdMedia = hdMedia; } public Column replaceAccount (final Account newAccount, final InternalColumnType ict) { final ColumnFeed oldFeed = ict.findInFeeds(getFeeds()); if (oldFeed == null) throw new IllegalArgumentException("ICT " + ict + " not found in feeds: " + getFeeds()); final List<ColumnFeed> newFeeds = new ArrayList<ColumnFeed>(getFeeds()); Collections.replaceAll(newFeeds, oldFeed, new ColumnFeed(newAccount.getId(), oldFeed.getResource())); return new Column(getId(), getTitle(), new LinkedHashSet<ColumnFeed>(newFeeds), getRefreshIntervalMins(), getExcludeColumnIds(), getNotificationStyle(), getInlineMediaStyle(), isHdMedia()); } @Override public int hashCode () { final int prime = 31; int result = 1; result = prime * result + this.id; result = prime * result + ((this.title == null) ? 0 : this.title.hashCode()); result = prime * result + ((this.feeds == null) ? 0 : this.feeds.hashCode()); result = prime * result + this.refreshIntervalMins; result = prime * result + ((this.excludeColumnIds == null) ? 0 : this.title.hashCode()); result = prime * result + (this.notificationStyle == null ? 0 : this.notificationStyle.hashCode()); return result; } @Override public boolean equals (final Object o) { if (o == null) return false; if (o == this) return true; if (!(o instanceof Column)) return false; final Column that = (Column) o; return EqualHelper.equal(this.id, that.id) && EqualHelper.equal(this.title, that.title) && EqualHelper.equal(this.feeds, that.feeds) && this.refreshIntervalMins == that.refreshIntervalMins && EqualHelper.equal(this.excludeColumnIds, that.excludeColumnIds) && EqualHelper.equal(this.notificationStyle, that.notificationStyle) && EqualHelper.equal(this.inlineMediaStyle, that.inlineMediaStyle) && EqualHelper.equal(this.hdMedia, that.hdMedia); } @Override public String getUiTitle () { return this.title != null && !this.title.isEmpty() ? this.title : String.format("Column %s", this.id); } @Override public String toString () { final StringBuilder s = new StringBuilder(); s.append("Column{").append(this.id) .append(",").append(this.title) .append(",").append(this.feeds) .append(",").append(this.refreshIntervalMins) .append(",").append(this.excludeColumnIds) .append(",").append(this.notificationStyle) .append(",").append(this.inlineMediaStyle) .append(",").append(this.hdMedia) .append("}"); return s.toString(); } public int getId () { return this.id; } public String getTitle () { return this.title; } /** * Does not return null. */ public Set<ColumnFeed> getFeeds () { if (this.feeds == null) return Collections.emptySet(); return this.feeds; } public int getRefreshIntervalMins () { return this.refreshIntervalMins; } public Set<Integer> getExcludeColumnIds () { return this.excludeColumnIds; } public NotificationStyle getNotificationStyle () { return this.notificationStyle; } public InlineMediaStyle getInlineMediaStyle () { return this.inlineMediaStyle; } public boolean isHdMedia () { return this.hdMedia; } /** * Does not return null. */ public Set<String> uniqAccountIds () { final Set<String> ret = new LinkedHashSet<String>(); for (final ColumnFeed cf : getFeeds()) { if (!StringHelper.isEmpty(cf.getAccountId())) ret.add(cf.getAccountId()); } return ret; } public static List<String> titles (final Collection<Column> columns) { if (columns == null) return null; final List<String> ret = new ArrayList<String>(columns.size()); for (final Column col : columns) { ret.add(col.getTitle()); } return ret; } /** * Does not return null. */ public static Set<String> uniqAccountIds (final Collection<Column> cols) { if (cols == null || cols.size() < 1) return Collections.emptySet(); final Set<String> ret = new LinkedHashSet<String>(); for (final Column col : cols) { for (final ColumnFeed cf : col.getFeeds()) { if (!StringHelper.isEmpty(cf.getAccountId())) ret.add(cf.getAccountId()); } } return ret; } public JSONObject toJson () throws JSONException { final JSONObject json = new JSONObject(); json.put(KEY_ID, getId()); json.put(KEY_TITLE, getTitle()); final Set<ColumnFeed> fs = getFeeds(); if (fs.size() == 1) { final ColumnFeed cf = fs.iterator().next(); json.put(KEY_ACCOUNT, cf.getAccountId()); json.put(KEY_RESOURCE, cf.getResource()); } else if (fs.size() > 1) { final JSONArray feedsArr = new JSONArray(); for (final ColumnFeed cf : fs) { feedsArr.put(cf.toJson()); } json.put(KEY_FEEDS, feedsArr); } json.put(KEY_REFRESH, getRefreshIntervalMins() + "mins"); json.put(KEY_EXCLUDE, toJsonArray(getExcludeColumnIds())); json.put(KEY_NOTIFY, getNotificationStyle() != null ? getNotificationStyle().toJson() : null); json.put(KEY_INLINE_MEDIA, getInlineMediaStyle() != null ? getInlineMediaStyle().serialise() : null); json.put(KEY_HD_MEDIA, isHdMedia()); return json; } private static JSONArray toJsonArray (final Set<Integer> ints) { if (ints == null) return null; final JSONArray ja = new JSONArray(); for (final Integer i : ints) { ja.put(i); } return ja; } public static Column parseJson (final String json) throws JSONException { if (json == null) return null; return parseJson((JSONObject) new JSONTokener(json).nextValue()); } public static Column parseJson (final JSONObject json) throws JSONException { try { final int id = json.optInt(KEY_ID, Integer.MIN_VALUE); if (id < 0) throw new JSONException("Column ID must be positive a integer."); final String title = json.getString(KEY_TITLE); final Set<ColumnFeed> feeds = new LinkedHashSet<ColumnFeed>(); if (json.has(KEY_RESOURCE)) { final String resource = json.getString(KEY_RESOURCE); final String account = json.has(KEY_ACCOUNT) ? json.getString(KEY_ACCOUNT) : null; feeds.add(new ColumnFeed(account, resource)); } final JSONArray jFeeds = json.optJSONArray(KEY_FEEDS); if (jFeeds != null) { for (int i = 0; i < jFeeds.length(); i++) { feeds.add(ColumnFeed.parseJson(jFeeds.getJSONObject(i))); } } boolean hasAccount = false; for (final ColumnFeed cf : feeds) { if (cf.getAccountId() != null) { hasAccount = true; break; } } final String refreshRaw = json.optString(KEY_REFRESH, null); final int refreshIntervalMins = parseFeedRefreshInterval(refreshRaw, hasAccount, title); final Set<Integer> excludeColumnIds = parseFeedExcludeColumns(json, title); final NotificationStyle notificationStyle = NotificationStyle.parseJson(json.opt(KEY_NOTIFY)); final InlineMediaStyle inlineMedia = InlineMediaStyle.parseJson(json.opt(KEY_INLINE_MEDIA)); final boolean hdMedia = json.optBoolean(KEY_HD_MEDIA, false); return new Column(id, title, feeds, refreshIntervalMins, excludeColumnIds, notificationStyle, inlineMedia, hdMedia); } catch (final JSONException e) { throw new JSONException("Failed to parse column: " + json.toString() + " > " + ExcpetionHelper.causeTrace(e, " > ")); } } private static int parseFeedRefreshInterval (final String refreshRaw, final boolean hasAccount, final String title) { final int refreshIntervalMins = TimeParser.parseDuration(refreshRaw); if (refreshIntervalMins < 0 && hasAccount) LOG.w("Column '%s' has invalid refresh interval: '%s'.", title, refreshRaw); return refreshIntervalMins; } private static Set<Integer> parseFeedExcludeColumns (final JSONObject colJson, final String title) throws JSONException { Set<Integer> excludeColumnIds = null; if (colJson.has(KEY_EXCLUDE)) { final JSONArray exArr = colJson.optJSONArray(KEY_EXCLUDE); if (exArr != null) { excludeColumnIds = asIntSet(exArr); } else { final int exId = colJson.optInt(KEY_EXCLUDE, Integer.MIN_VALUE); if (exId > Integer.MIN_VALUE) { excludeColumnIds = Collections.singleton(Integer.valueOf(exId)); } } if (excludeColumnIds == null) LOG.w("Column '%s' has invalid exclude value: '%s'.", title, colJson.getString(KEY_EXCLUDE)); } return excludeColumnIds == null ? null : Collections.unmodifiableSet(excludeColumnIds); } private static Set<Integer> asIntSet (final JSONArray arr) throws JSONException { final Set<Integer> ret = new HashSet<Integer>(); for (int i = 0; i < arr.length(); i++) { ret.add(Integer.valueOf(arr.getInt(i))); } return ret; } }