Remove PostReplyProvider methods from Core.
[Sone.git] / src / main / java / net / pterodactylus / sone / fcp / AbstractSoneCommand.java
1 /*
2  * Sone - AbstractSoneCommand.java - Copyright © 2011–2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.fcp;
19
20 import static com.google.common.collect.FluentIterable.from;
21
22 import java.util.Collection;
23 import java.util.List;
24
25 import net.pterodactylus.sone.core.Core;
26 import net.pterodactylus.sone.data.Post;
27 import net.pterodactylus.sone.data.PostReply;
28 import net.pterodactylus.sone.data.Profile;
29 import net.pterodactylus.sone.data.Profile.Field;
30 import net.pterodactylus.sone.data.Reply;
31 import net.pterodactylus.sone.data.Sone;
32 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
33 import net.pterodactylus.sone.freenet.fcp.AbstractCommand;
34 import net.pterodactylus.sone.freenet.fcp.Command;
35 import net.pterodactylus.sone.freenet.fcp.FcpException;
36 import net.pterodactylus.sone.template.SoneAccessor;
37
38 import freenet.node.FSParseException;
39 import freenet.support.SimpleFieldSet;
40
41 import com.google.common.base.Optional;
42
43 /**
44  * Abstract base implementation of a {@link Command} with Sone-related helper
45  * methods.
46  *
47  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
48  */
49 public abstract class AbstractSoneCommand extends AbstractCommand {
50
51         /** The Sone core. */
52         private final Core core;
53
54         /** Whether this command needs write access. */
55         private final boolean writeAccess;
56
57         /**
58          * Creates a new abstract Sone FCP command.
59          *
60          * @param core
61          *            The Sone core
62          */
63         protected AbstractSoneCommand(Core core) {
64                 this(core, false);
65         }
66
67         /**
68          * Creates a new abstract Sone FCP command.
69          *
70          * @param core
71          *            The Sone core
72          * @param writeAccess
73          *            {@code true} if this command requires write access,
74          *            {@code false} otherwise
75          */
76         protected AbstractSoneCommand(Core core, boolean writeAccess) {
77                 this.core = core;
78                 this.writeAccess = writeAccess;
79         }
80
81         //
82         // ACCESSORS
83         //
84
85         /**
86          * Returns the Sone core.
87          *
88          * @return The Sone core
89          */
90         protected Core getCore() {
91                 return core;
92         }
93
94         /**
95          * Returns whether this command requires write access.
96          *
97          * @return {@code true} if this command require write access, {@code false}
98          *         otherwise
99          */
100         public boolean requiresWriteAccess() {
101                 return writeAccess;
102         }
103
104         //
105         // PROTECTED METHODS
106         //
107
108         /**
109          * Encodes text in a way that makes it possible for the text to be stored in
110          * a {@link SimpleFieldSet}. Backslashes, CR, and LF are prepended with a
111          * backslash.
112          *
113          * @param text
114          *            The text to encode
115          * @return The encoded text
116          */
117         protected static String encodeString(String text) {
118                 return text.replaceAll("\\\\", "\\\\").replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r");
119         }
120
121         /**
122          * Returns a Sone whose ID is a parameter in the given simple field set.
123          *
124          * @param simpleFieldSet
125          *            The simple field set containing the ID of the Sone
126          * @param parameterName
127          *            The name under which the Sone ID is stored in the simple field
128          *            set
129          * @param localOnly
130          *            {@code true} to only return local Sones, {@code false} to
131          *            return any Sones
132          * @return The Sone
133          * @throws FcpException
134          *             if there is no Sone ID stored under the given parameter name,
135          *             or if the Sone ID is invalid
136          */
137         protected Sone getSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean localOnly) throws FcpException {
138                 return getSone(simpleFieldSet, parameterName, localOnly, true).get();
139         }
140
141         /**
142          * Returns a Sone whose ID is a parameter in the given simple field set.
143          *
144          * @param simpleFieldSet
145          *            The simple field set containing the ID of the Sone
146          * @param parameterName
147          *            The name under which the Sone ID is stored in the simple field
148          *            set
149          * @param localOnly
150          *            {@code true} to only return local Sones, {@code false} to
151          *            return any Sones
152          * @param mandatory
153          *            {@code true} if a valid Sone ID is required, {@code false}
154          *            otherwise
155          * @return The Sone, or {@code null} if {@code mandatory} is {@code false}
156          *         and the Sone ID is invalid
157          * @throws FcpException
158          *             if there is no Sone ID stored under the given parameter name,
159          *             or if {@code mandatory} is {@code true} and the Sone ID is
160          *             invalid
161          */
162         protected Optional<Sone> getSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean localOnly, boolean mandatory) throws FcpException {
163                 String soneId = simpleFieldSet.get(parameterName);
164                 if (mandatory && (soneId == null)) {
165                         throw new FcpException("Could not load Sone ID from “" + parameterName + "”.");
166                 }
167                 Optional<Sone> sone = core.getSone(soneId);
168                 if ((mandatory && !sone.isPresent()) || (mandatory && sone.isPresent() && (localOnly && !sone.get().isLocal()))) {
169                         throw new FcpException("Could not load Sone from “" + soneId + "”.");
170                 }
171                 return sone;
172         }
173
174         /**
175          * Returns a post whose ID is a parameter in the given simple field set.
176          *
177          * @param simpleFieldSet
178          *            The simple field set containing the ID of the post
179          * @param parameterName
180          *            The name under which the post ID is stored in the simple field
181          *            set
182          * @return The post
183          * @throws FcpException
184          *             if there is no post ID stored under the given parameter name,
185          *             or if the post ID is invalid
186          */
187         protected Post getPost(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
188                 try {
189                         String postId = simpleFieldSet.getString(parameterName);
190                         Optional<Post> post = core.getDatabase().getPost(postId);
191                         if (!post.isPresent()) {
192                                 throw new FcpException("Could not load post from “" + postId + "”.");
193                         }
194                         return post.get();
195                 } catch (FSParseException fspe1) {
196                         throw new FcpException("Could not post ID from “" + parameterName + "”.", fspe1);
197                 }
198         }
199
200         /**
201          * Returns a reply whose ID is a parameter in the given simple field set.
202          *
203          * @param simpleFieldSet
204          *            The simple field set containing the ID of the reply
205          * @param parameterName
206          *            The name under which the reply ID is stored in the simple
207          *            field set
208          * @return The reply
209          * @throws FcpException
210          *             if there is no reply ID stored under the given parameter
211          *             name, or if the reply ID is invalid
212          */
213         protected PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
214                 try {
215                         String replyId = simpleFieldSet.getString(parameterName);
216                         Optional<PostReply> reply = core.getDatabase().getPostReply(replyId);
217                         if (!reply.isPresent()) {
218                                 throw new FcpException("Could not load reply from “" + replyId + "”.");
219                         }
220                         return reply.get();
221                 } catch (FSParseException fspe1) {
222                         throw new FcpException("Could not reply ID from “" + parameterName + "”.", fspe1);
223                 }
224         }
225
226         /**
227          * Creates a simple field set from the given Sone, including {@link Profile}
228          * information.
229          *
230          * @param sone
231          *            The Sone to encode
232          * @param prefix
233          *            The prefix for the field names (may be empty but not {@code
234          *            null})
235          * @param localSone
236          *            An optional local Sone that is used for Sone-specific data,
237          *            such as if the Sone is followed by the local Sone
238          * @return The simple field set containing the given Sone
239          */
240         protected static SimpleFieldSet encodeSone(Sone sone, String prefix, Optional<Sone> localSone) {
241                 SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
242
243                 soneBuilder.put(prefix + "Name", sone.getName());
244                 soneBuilder.put(prefix + "NiceName", SoneAccessor.getNiceName(sone));
245                 soneBuilder.put(prefix + "LastUpdated", sone.getTime());
246                 if (localSone.isPresent()) {
247                         soneBuilder.put(prefix + "Followed", String.valueOf(localSone.get().hasFriend(sone.getId())));
248                 }
249                 Profile profile = sone.getProfile();
250                 soneBuilder.put(prefix + "Field.Count", profile.getFields().size());
251                 int fieldIndex = 0;
252                 for (Field field : profile.getFields()) {
253                         soneBuilder.put(prefix + "Field." + fieldIndex + ".Name", field.getName());
254                         soneBuilder.put(prefix + "Field." + fieldIndex + ".Value", field.getValue());
255                         ++fieldIndex;
256                 }
257
258                 return soneBuilder.get();
259         }
260
261         /**
262          * Creates a simple field set from the given collection of Sones.
263          *
264          * @param sones
265          *            The Sones to encode
266          * @param prefix
267          *            The prefix for the field names (may be empty but not
268          *            {@code null})
269          * @return The simple field set containing the given Sones
270          */
271         protected static SimpleFieldSet encodeSones(Collection<? extends Sone> sones, String prefix) {
272                 SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
273
274                 int soneIndex = 0;
275                 soneBuilder.put(prefix + "Count", sones.size());
276                 for (Sone sone : sones) {
277                         String sonePrefix = prefix + soneIndex++ + ".";
278                         soneBuilder.put(sonePrefix + "ID", sone.getId());
279                         soneBuilder.put(sonePrefix + "Name", sone.getName());
280                         soneBuilder.put(sonePrefix + "NiceName", SoneAccessor.getNiceName(sone));
281                         soneBuilder.put(sonePrefix + "Time", sone.getTime());
282                 }
283
284                 return soneBuilder.get();
285         }
286
287         /**
288          * Creates a simple field set from the given post.
289          *
290          * @param post
291          *            The post to encode
292          * @param prefix
293          *            The prefix for the field names (may be empty but not
294          *            {@code null})
295          * @param includeReplies
296          *            {@code true} to include replies, {@code false} to not include
297          *            replies
298          * @return The simple field set containing the post
299          */
300         protected SimpleFieldSet encodePost(Post post, String prefix, boolean includeReplies) {
301                 SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
302
303                 postBuilder.put(prefix + "ID", post.getId());
304                 postBuilder.put(prefix + "Sone", post.getSone().getId());
305                 if (post.getRecipientId().isPresent()) {
306                         postBuilder.put(prefix + "Recipient", post.getRecipientId().get());
307                 }
308                 postBuilder.put(prefix + "Time", post.getTime());
309                 postBuilder.put(prefix + "Text", encodeString(post.getText()));
310                 postBuilder.put(encodeLikes(core.getLikes(post), prefix + "Likes."));
311
312                 if (includeReplies) {
313                         List<PostReply> replies = post.getReplies();
314                         postBuilder.put(encodeReplies(replies, prefix));
315                 }
316
317                 return postBuilder.get();
318         }
319
320         /**
321          * Creates a simple field set from the given collection of posts.
322          *
323          * @param posts
324          *            The posts to encode
325          * @param prefix
326          *            The prefix for the field names (may be empty but not
327          *            {@code null})
328          * @param includeReplies
329          *            {@code true} to include the replies, {@code false} to not
330          *            include the replies
331          * @return The simple field set containing the posts
332          */
333         protected SimpleFieldSet encodePosts(Collection<? extends Post> posts, String prefix, boolean includeReplies) {
334                 SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
335
336                 int postIndex = 0;
337                 postBuilder.put(prefix + "Count", posts.size());
338                 for (Post post : posts) {
339                         String postPrefix = prefix + postIndex++;
340                         postBuilder.put(encodePost(post, postPrefix + ".", includeReplies));
341                         if (includeReplies) {
342                                 postBuilder.put(encodeReplies(from(post.getReplies()).filter(Reply.FUTURE_REPLY_FILTER).toList(), postPrefix + "."));
343                         }
344                 }
345
346                 return postBuilder.get();
347         }
348
349         /**
350          * Creates a simple field set from the given collection of replies.
351          *
352          * @param replies
353          *            The replies to encode
354          * @param prefix
355          *            The prefix for the field names (may be empty, but not
356          *            {@code null})
357          * @return The simple field set containing the replies
358          */
359         protected static SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
360                 SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder();
361
362                 int replyIndex = 0;
363                 replyBuilder.put(prefix + "Replies.Count", replies.size());
364                 for (PostReply reply : replies) {
365                         String replyPrefix = prefix + "Replies." + replyIndex++ + ".";
366                         replyBuilder.put(replyPrefix + "ID", reply.getId());
367                         replyBuilder.put(replyPrefix + "Sone", reply.getSone().getId());
368                         replyBuilder.put(replyPrefix + "Time", reply.getTime());
369                         replyBuilder.put(replyPrefix + "Text", encodeString(reply.getText()));
370                 }
371
372                 return replyBuilder.get();
373         }
374
375         /**
376          * Creates a simple field set from the given collection of Sones that like
377          * an element.
378          *
379          * @param likes
380          *            The liking Sones
381          * @param prefix
382          *            The prefix for the field names (may be empty but not
383          *            {@code null})
384          * @return The simple field set containing the likes
385          */
386         protected static SimpleFieldSet encodeLikes(Collection<? extends Sone> likes, String prefix) {
387                 SimpleFieldSetBuilder likesBuilder = new SimpleFieldSetBuilder();
388
389                 int likeIndex = 0;
390                 likesBuilder.put(prefix + "Count", likes.size());
391                 for (Sone sone : likes) {
392                         String sonePrefix = prefix + likeIndex++ + ".";
393                         likesBuilder.put(sonePrefix + "ID", sone.getId());
394                 }
395
396                 return likesBuilder.get();
397         }
398
399         //
400         // OBJECT METHODS
401         //
402
403         @Override
404         public String toString() {
405                 return getClass().getName() + "[writeAccess=" + writeAccess + "]";
406         }
407
408 }