Remove booleans from method signatures, create explicit methods.
[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         protected Optional<Sone> getOptionalSone(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
122                 String soneId = getMandatoryParameter(simpleFieldSet, parameterName);
123                 return core.getSone(soneId);
124         }
125
126         protected Sone getMandatoryLocalSone(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
127                 Sone sone = getMandatorySone(simpleFieldSet, parameterName);
128                 if (!sone.isLocal()) {
129                         throw new FcpException("Could not load Sone from “" + sone.getId() + "”.");
130                 }
131                 return sone;
132         }
133
134         protected Sone getMandatorySone(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
135                 String soneId = getMandatoryParameter(simpleFieldSet, parameterName);
136                 Optional<Sone> sone = getMandatorySone(soneId);
137                 return sone.get();
138         }
139
140         private Optional<Sone> getMandatorySone(String soneId) throws FcpException {
141                 Optional<Sone> sone = core.getSone(soneId);
142                 if (!sone.isPresent()) {
143                         throw new FcpException("Could not load Sone from “" + soneId + "”.");
144                 }
145                 return sone;
146         }
147
148         private String getMandatoryParameter(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
149                 String soneId = simpleFieldSet.get(parameterName);
150                 if (soneId == null) {
151                         throw new FcpException("Could not load Sone ID from “" + parameterName + "”.");
152                 }
153                 return soneId;
154         }
155
156         /**
157          * Returns a post whose ID is a parameter in the given simple field set.
158          *
159          * @param simpleFieldSet
160          *            The simple field set containing the ID of the post
161          * @param parameterName
162          *            The name under which the post ID is stored in the simple field
163          *            set
164          * @return The post
165          * @throws FcpException
166          *             if there is no post ID stored under the given parameter name,
167          *             or if the post ID is invalid
168          */
169         protected Post getPost(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
170                 try {
171                         String postId = simpleFieldSet.getString(parameterName);
172                         Optional<Post> post = core.getDatabase().getPost(postId);
173                         if (!post.isPresent()) {
174                                 throw new FcpException("Could not load post from “" + postId + "”.");
175                         }
176                         return post.get();
177                 } catch (FSParseException fspe1) {
178                         throw new FcpException("Could not post ID from “" + parameterName + "”.", fspe1);
179                 }
180         }
181
182         /**
183          * Returns a reply whose ID is a parameter in the given simple field set.
184          *
185          * @param simpleFieldSet
186          *            The simple field set containing the ID of the reply
187          * @param parameterName
188          *            The name under which the reply ID is stored in the simple
189          *            field set
190          * @return The reply
191          * @throws FcpException
192          *             if there is no reply ID stored under the given parameter
193          *             name, or if the reply ID is invalid
194          */
195         protected PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
196                 try {
197                         String replyId = simpleFieldSet.getString(parameterName);
198                         Optional<PostReply> reply = core.getDatabase().getPostReply(replyId);
199                         if (!reply.isPresent()) {
200                                 throw new FcpException("Could not load reply from “" + replyId + "”.");
201                         }
202                         return reply.get();
203                 } catch (FSParseException fspe1) {
204                         throw new FcpException("Could not reply ID from “" + parameterName + "”.", fspe1);
205                 }
206         }
207
208         /**
209          * Creates a simple field set from the given Sone, including {@link Profile}
210          * information.
211          *
212          * @param sone
213          *            The Sone to encode
214          * @param prefix
215          *            The prefix for the field names (may be empty but not {@code
216          *            null})
217          * @param localSone
218          *            An optional local Sone that is used for Sone-specific data,
219          *            such as if the Sone is followed by the local Sone
220          * @return The simple field set containing the given Sone
221          */
222         protected static SimpleFieldSet encodeSone(Sone sone, String prefix, Optional<Sone> localSone) {
223                 SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
224
225                 soneBuilder.put(prefix + "Name", sone.getName());
226                 soneBuilder.put(prefix + "NiceName", SoneAccessor.getNiceName(sone));
227                 soneBuilder.put(prefix + "LastUpdated", sone.getTime());
228                 if (localSone.isPresent()) {
229                         soneBuilder.put(prefix + "Followed", String.valueOf(localSone.get().hasFriend(sone.getId())));
230                 }
231                 Profile profile = sone.getProfile();
232                 soneBuilder.put(prefix + "Field.Count", profile.getFields().size());
233                 int fieldIndex = 0;
234                 for (Field field : profile.getFields()) {
235                         soneBuilder.put(prefix + "Field." + fieldIndex + ".Name", field.getName());
236                         soneBuilder.put(prefix + "Field." + fieldIndex + ".Value", field.getValue());
237                         ++fieldIndex;
238                 }
239
240                 return soneBuilder.get();
241         }
242
243         /**
244          * Creates a simple field set from the given collection of Sones.
245          *
246          * @param sones
247          *            The Sones to encode
248          * @param prefix
249          *            The prefix for the field names (may be empty but not
250          *            {@code null})
251          * @return The simple field set containing the given Sones
252          */
253         protected static SimpleFieldSet encodeSones(Collection<? extends Sone> sones, String prefix) {
254                 SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
255
256                 int soneIndex = 0;
257                 soneBuilder.put(prefix + "Count", sones.size());
258                 for (Sone sone : sones) {
259                         String sonePrefix = prefix + soneIndex++ + ".";
260                         soneBuilder.put(sonePrefix + "ID", sone.getId());
261                         soneBuilder.put(sonePrefix + "Name", sone.getName());
262                         soneBuilder.put(sonePrefix + "NiceName", SoneAccessor.getNiceName(sone));
263                         soneBuilder.put(sonePrefix + "Time", sone.getTime());
264                 }
265
266                 return soneBuilder.get();
267         }
268
269         /**
270          * Creates a simple field set from the given post.
271          *
272          * @param post
273          *            The post to encode
274          * @param prefix
275          *            The prefix for the field names (may be empty but not
276          *            {@code null})
277          * @param includeReplies
278          *            {@code true} to include replies, {@code false} to not include
279          *            replies
280          * @return The simple field set containing the post
281          */
282         protected SimpleFieldSet encodePost(Post post, String prefix, boolean includeReplies) {
283                 SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
284
285                 postBuilder.put(prefix + "ID", post.getId());
286                 postBuilder.put(prefix + "Sone", post.getSone().getId());
287                 if (post.getRecipientId().isPresent()) {
288                         postBuilder.put(prefix + "Recipient", post.getRecipientId().get());
289                 }
290                 postBuilder.put(prefix + "Time", post.getTime());
291                 postBuilder.put(prefix + "Text", encodeString(post.getText()));
292                 postBuilder.put(encodeLikes(core.getLikes(post), prefix + "Likes."));
293
294                 if (includeReplies) {
295                         List<PostReply> replies = post.getReplies();
296                         postBuilder.put(encodeReplies(replies, prefix));
297                 }
298
299                 return postBuilder.get();
300         }
301
302         /**
303          * Creates a simple field set from the given collection of posts.
304          *
305          * @param posts
306          *            The posts to encode
307          * @param prefix
308          *            The prefix for the field names (may be empty but not
309          *            {@code null})
310          * @param includeReplies
311          *            {@code true} to include the replies, {@code false} to not
312          *            include the replies
313          * @return The simple field set containing the posts
314          */
315         protected SimpleFieldSet encodePosts(Collection<? extends Post> posts, String prefix, boolean includeReplies) {
316                 SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
317
318                 int postIndex = 0;
319                 postBuilder.put(prefix + "Count", posts.size());
320                 for (Post post : posts) {
321                         String postPrefix = prefix + postIndex++;
322                         postBuilder.put(encodePost(post, postPrefix + ".", includeReplies));
323                         if (includeReplies) {
324                                 postBuilder.put(encodeReplies(from(post.getReplies()).filter(Reply.FUTURE_REPLY_FILTER).toList(), postPrefix + "."));
325                         }
326                 }
327
328                 return postBuilder.get();
329         }
330
331         /**
332          * Creates a simple field set from the given collection of replies.
333          *
334          * @param replies
335          *            The replies to encode
336          * @param prefix
337          *            The prefix for the field names (may be empty, but not
338          *            {@code null})
339          * @return The simple field set containing the replies
340          */
341         protected static SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
342                 SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder();
343
344                 int replyIndex = 0;
345                 replyBuilder.put(prefix + "Replies.Count", replies.size());
346                 for (PostReply reply : replies) {
347                         String replyPrefix = prefix + "Replies." + replyIndex++ + ".";
348                         replyBuilder.put(replyPrefix + "ID", reply.getId());
349                         replyBuilder.put(replyPrefix + "Sone", reply.getSone().getId());
350                         replyBuilder.put(replyPrefix + "Time", reply.getTime());
351                         replyBuilder.put(replyPrefix + "Text", encodeString(reply.getText()));
352                 }
353
354                 return replyBuilder.get();
355         }
356
357         /**
358          * Creates a simple field set from the given collection of Sones that like
359          * an element.
360          *
361          * @param likes
362          *            The liking Sones
363          * @param prefix
364          *            The prefix for the field names (may be empty but not
365          *            {@code null})
366          * @return The simple field set containing the likes
367          */
368         protected static SimpleFieldSet encodeLikes(Collection<? extends Sone> likes, String prefix) {
369                 SimpleFieldSetBuilder likesBuilder = new SimpleFieldSetBuilder();
370
371                 int likeIndex = 0;
372                 likesBuilder.put(prefix + "Count", likes.size());
373                 for (Sone sone : likes) {
374                         String sonePrefix = prefix + likeIndex++ + ".";
375                         likesBuilder.put(sonePrefix + "ID", sone.getId());
376                 }
377
378                 return likesBuilder.get();
379         }
380
381         //
382         // OBJECT METHODS
383         //
384
385         @Override
386         public String toString() {
387                 return getClass().getName() + "[writeAccess=" + writeAccess + "]";
388         }
389
390 }