Actually allow the Sone to be missing in getOptionalSone().
[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 import static net.pterodactylus.sone.data.Reply.FUTURE_REPLY_FILTER;
22
23 import java.util.Collection;
24 import java.util.List;
25
26 import net.pterodactylus.sone.core.Core;
27 import net.pterodactylus.sone.data.Post;
28 import net.pterodactylus.sone.data.PostReply;
29 import net.pterodactylus.sone.data.Profile;
30 import net.pterodactylus.sone.data.Profile.Field;
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 = getString(simpleFieldSet, parameterName, null);
123                 return (soneId == null) ? Optional.<Sone>absent() : 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         protected SimpleFieldSet encodePost(Post post, String prefix) {
270                 return createPostBuilderFromPost(post, prefix).get();
271         }
272
273         protected SimpleFieldSet encodePostWithReplies(Post post, String prefix) {
274                 SimpleFieldSetBuilder postBuilder = createPostBuilderFromPost(post, prefix);
275
276                 List<PostReply> replies = from(post.getReplies()).filter(FUTURE_REPLY_FILTER).toList();
277                 postBuilder.put(encodeReplies(replies, prefix));
278
279                 return postBuilder.get();
280         }
281
282         private SimpleFieldSetBuilder createPostBuilderFromPost(Post post, String prefix) {
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                 return postBuilder;
295         }
296
297         protected SimpleFieldSet encodePosts(Collection<? extends Post> posts, String prefix) {
298                 SimpleFieldSetBuilder postBuilder = createPostBuilderFromPosts(posts, prefix);
299
300                 return postBuilder.get();
301         }
302
303         private SimpleFieldSetBuilder createPostBuilderFromPosts(Collection<? extends Post> posts, String prefix) {
304                 SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
305
306                 int postIndex = 0;
307                 postBuilder.put(prefix + "Count", posts.size());
308                 for (Post post : posts) {
309                         String postPrefix = prefix + postIndex++;
310                         postBuilder.put(encodePost(post, postPrefix + "."));
311                 }
312
313                 return postBuilder;
314         }
315
316         protected SimpleFieldSet encodePostsWithReplies(Collection<? extends Post> posts, String prefix) {
317                 SimpleFieldSetBuilder postBuilder = createPostBuilderFromPosts(posts, prefix);
318
319                 int postIndex = 0;
320                 for (Post post : posts) {
321                         String postPrefix = prefix + postIndex++;
322                         postBuilder.put(encodeReplies(from(post.getReplies()).filter(FUTURE_REPLY_FILTER).toList(), postPrefix + "."));
323                 }
324
325                 return postBuilder.get();
326         }
327
328         /**
329          * Creates a simple field set from the given collection of replies.
330          *
331          * @param replies
332          *            The replies to encode
333          * @param prefix
334          *            The prefix for the field names (may be empty, but not
335          *            {@code null})
336          * @return The simple field set containing the replies
337          */
338         protected static SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
339                 SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder();
340
341                 int replyIndex = 0;
342                 replyBuilder.put(prefix + "Replies.Count", replies.size());
343                 for (PostReply reply : replies) {
344                         String replyPrefix = prefix + "Replies." + replyIndex++ + ".";
345                         replyBuilder.put(replyPrefix + "ID", reply.getId());
346                         replyBuilder.put(replyPrefix + "Sone", reply.getSone().getId());
347                         replyBuilder.put(replyPrefix + "Time", reply.getTime());
348                         replyBuilder.put(replyPrefix + "Text", encodeString(reply.getText()));
349                 }
350
351                 return replyBuilder.get();
352         }
353
354         /**
355          * Creates a simple field set from the given collection of Sones that like
356          * an element.
357          *
358          * @param likes
359          *            The liking Sones
360          * @param prefix
361          *            The prefix for the field names (may be empty but not
362          *            {@code null})
363          * @return The simple field set containing the likes
364          */
365         protected static SimpleFieldSet encodeLikes(Collection<? extends Sone> likes, String prefix) {
366                 SimpleFieldSetBuilder likesBuilder = new SimpleFieldSetBuilder();
367
368                 int likeIndex = 0;
369                 likesBuilder.put(prefix + "Count", likes.size());
370                 for (Sone sone : likes) {
371                         String sonePrefix = prefix + likeIndex++ + ".";
372                         likesBuilder.put(sonePrefix + "ID", sone.getId());
373                 }
374
375                 return likesBuilder.get();
376         }
377
378         //
379         // OBJECT METHODS
380         //
381
382         @Override
383         public String toString() {
384                 return getClass().getName() + "[writeAccess=" + writeAccess + "]";
385         }
386
387 }