+ }
+
+ /**
+ * Returns whether the given ID is the ID of a local Sone.
+ *
+ * @param id
+ * The Sone ID to check for its locality
+ * @return {@code true} if the given ID is a local Sone, {@code false}
+ * otherwise
+ */
+ public boolean isLocalSone(String id) {
+ synchronized (localSones) {
+ return localSones.containsKey(id);
+ }
+ }
+
+ /**
+ * Returns all local Sones.
+ *
+ * @return All local Sones
+ */
+ public Set<Sone> getLocalSones() {
+ synchronized (localSones) {
+ return new HashSet<Sone>(localSones.values());
+ }
+ }
+
+ /**
+ * Returns the local Sone with the given ID.
+ *
+ * @param id
+ * The ID of the Sone to get
+ * @return The Sone with the given ID
+ */
+ public Sone getLocalSone(String id) {
+ synchronized (localSones) {
+ Sone sone = localSones.get(id);
+ if (sone == null) {
+ sone = new Sone(id);
+ localSones.put(id, sone);
+ }
+ return sone;
+ }
+ }
+
+ /**
+ * Returns all remote Sones.
+ *
+ * @return All remote Sones
+ */
+ public Set<Sone> getRemoteSones() {
+ synchronized (remoteSones) {
+ return new HashSet<Sone>(remoteSones.values());
+ }
+ }
+
+ /**
+ * Returns the remote Sone with the given ID.
+ *
+ * @param id
+ * The ID of the remote Sone to get
+ * @return The Sone with the given ID
+ */
+ public Sone getRemoteSone(String id) {
+ return getRemoteSone(id, true);
+ }
+
+ /**
+ * Returns the remote Sone with the given ID.
+ *
+ * @param id
+ * The ID of the remote Sone to get
+ * @param create
+ * {@code true} to always create a Sone, {@code false} to return
+ * {@code null} if no Sone with the given ID exists
+ * @return The Sone with the given ID
+ */
+ public Sone getRemoteSone(String id, boolean create) {
+ synchronized (remoteSones) {
+ Sone sone = remoteSones.get(id);
+ if ((sone == null) && create) {
+ sone = new Sone(id);
+ remoteSones.put(id, sone);
+ }
+ return sone;
+ }
+ }
+
+ /**
+ * Returns whether the given Sone is a remote Sone.
+ *
+ * @param sone
+ * The Sone to check
+ * @return {@code true} if the given Sone is a remote Sone, {@code false}
+ * otherwise
+ */
+ public boolean isRemoteSone(Sone sone) {
+ synchronized (remoteSones) {
+ return remoteSones.containsKey(sone.getId());
+ }
+ }
+
+ /**
+ * Returns whether the Sone with the given ID is a remote Sone.
+ *
+ * @param id
+ * The ID of the Sone to check
+ * @return {@code true} if the Sone with the given ID is a remote Sone,
+ * {@code false} otherwise
+ */
+ public boolean isRemoteSone(String id) {
+ synchronized (remoteSones) {
+ return remoteSones.containsKey(id);
+ }
+ }
+
+ /**
+ * Returns whether the given Sone is a new Sone. After this check, the Sone
+ * is marked as known, i.e. a second call with the same parameters will
+ * always yield {@code false}.
+ *
+ * @param sone
+ * The sone to check for
+ * @return {@code true} if the given Sone is new, false otherwise
+ */
+ public boolean isNewSone(Sone sone) {
+ synchronized (newSones) {
+ boolean isNew = !knownSones.contains(sone.getId()) && newSones.remove(sone.getId());
+ knownSones.add(sone.getId());
+ return isNew;
+ }
+ }
+
+ /**
+ * Returns the post with the given ID.
+ *
+ * @param postId
+ * The ID of the post to get
+ * @return The post, or {@code null} if there is no such post
+ */
+ public Post getPost(String postId) {
+ synchronized (posts) {
+ Post post = posts.get(postId);
+ if (post == null) {
+ post = new Post(postId);
+ posts.put(postId, post);
+ }
+ return post;
+ }
+ }
+
+ /**
+ * Returns whether the given post ID is new. After this method returns it is
+ * marked a known post ID.
+ *
+ * @param postId
+ * The post ID
+ * @return {@code true} if the post is considered to be new, {@code false}
+ * otherwise
+ */
+ public boolean isNewPost(String postId) {
+ synchronized (newPosts) {
+ boolean isNew = !knownPosts.contains(postId) && newPosts.remove(postId);
+ knownPosts.add(postId);
+ return isNew;
+ }
+ }
+
+ /**
+ * Returns the reply with the given ID.
+ *
+ * @param replyId
+ * The ID of the reply to get
+ * @return The reply, or {@code null} if there is no such reply
+ */
+ public Reply getReply(String replyId) {
+ synchronized (replies) {
+ Reply reply = replies.get(replyId);
+ if (reply == null) {
+ reply = new Reply(replyId);
+ replies.put(replyId, reply);
+ }
+ return reply;
+ }
+ }
+
+ /**
+ * Returns all replies for the given post, order ascending by time.
+ *
+ * @param post
+ * The post to get all replies for
+ * @return All replies for the given post
+ */
+ public List<Reply> getReplies(Post post) {
+ Set<Sone> sones = getSones();
+ List<Reply> replies = new ArrayList<Reply>();
+ for (Sone sone : sones) {
+ for (Reply reply : sone.getReplies()) {
+ if (reply.getPost().equals(post)) {
+ replies.add(reply);
+ }
+ }
+ }
+ Collections.sort(replies, Reply.TIME_COMPARATOR);
+ return replies;
+ }
+
+ /**
+ * Returns all Sones that have liked the given post.
+ *
+ * @param post
+ * The post to get the liking Sones for
+ * @return The Sones that like the given post
+ */
+ public Set<Sone> getLikes(Post post) {
+ Set<Sone> sones = new HashSet<Sone>();
+ for (Sone sone : getSones()) {
+ if (sone.getLikedPostIds().contains(post.getId())) {
+ sones.add(sone);
+ }
+ }
+ return sones;
+ }
+
+ /**
+ * Returns all Sones that have liked the given reply.
+ *
+ * @param reply
+ * The reply to get the liking Sones for
+ * @return The Sones that like the given reply
+ */
+ public Set<Sone> getLikes(Reply reply) {
+ Set<Sone> sones = new HashSet<Sone>();
+ for (Sone sone : getSones()) {
+ if (sone.getLikedReplyIds().contains(reply.getId())) {
+ sones.add(sone);
+ }
+ }
+ return sones;