Distinguish between local and “normal” Sones in FCP handler.
[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 java.util.Collection;
21 import java.util.List;
22
23 import net.pterodactylus.sone.core.Core;
24 import net.pterodactylus.sone.data.LocalSone;
25 import net.pterodactylus.sone.data.Post;
26 import net.pterodactylus.sone.data.PostReply;
27 import net.pterodactylus.sone.data.Profile;
28 import net.pterodactylus.sone.data.Profile.Field;
29 import net.pterodactylus.sone.data.Reply;
30 import net.pterodactylus.sone.data.Sone;
31 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
32 import net.pterodactylus.sone.freenet.fcp.AbstractCommand;
33 import net.pterodactylus.sone.freenet.fcp.Command;
34 import net.pterodactylus.sone.freenet.fcp.FcpException;
35 import net.pterodactylus.sone.template.SoneAccessor;
36
37 import com.google.common.base.Optional;
38 import com.google.common.collect.Collections2;
39
40 import freenet.node.FSParseException;
41 import freenet.support.SimpleFieldSet;
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          * @return The Sone
130          * @throws FcpException
131          *             if there is no Sone ID stored under the given parameter name,
132          *             or if the Sone ID is invalid
133          */
134         protected Sone getSone(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
135                 return getSone(simpleFieldSet, parameterName, true).get();
136         }
137
138         /**
139          * Returns a Sone whose ID is a parameter in the given simple field set.
140          *
141          * @param simpleFieldSet
142          *            The simple field set containing the ID of the Sone
143          * @param parameterName
144          *            The name under which the Sone ID is stored in the simple field
145          *            set
146          * @return The Sone
147          * @throws FcpException
148          *             if there is no Sone ID stored under the given parameter name,
149          *             or if the Sone ID is invalid
150          */
151         protected LocalSone getLocalSone(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
152                 return getLocalSone(simpleFieldSet, parameterName, true).get();
153         }
154
155         /**
156          * Returns a Sone whose ID is a parameter in the given simple field set.
157          *
158          * @param simpleFieldSet
159          *            The simple field set containing the ID of the Sone
160          * @param parameterName
161          *            The name under which the Sone ID is stored in the simple field
162          *            set
163          * @param mandatory
164          *            {@code true} if a valid Sone ID is required, {@code false}
165          *            otherwise
166          * @return The Sone, or {@code null} if {@code mandatory} is {@code false}
167          *         and the Sone ID is invalid
168          * @throws FcpException
169          *             if there is no Sone ID stored under the given parameter name,
170          *             or if {@code mandatory} is {@code true} and the Sone ID is
171          *             invalid
172          */
173         protected Optional<Sone> getSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean mandatory) throws FcpException {
174                 String soneId = simpleFieldSet.get(parameterName);
175                 if (mandatory && (soneId == null)) {
176                         throw new FcpException("Could not load Sone ID from “" + parameterName + "”.");
177                 }
178                 Optional<Sone> sone = core.getSone(soneId);
179                 if ((mandatory && !sone.isPresent()) || (mandatory && sone.isPresent())) {
180                         throw new FcpException("Could not load Sone from “" + soneId + "”.");
181                 }
182                 return sone;
183         }
184
185         /**
186          * Returns a Sone whose ID is a parameter in the given simple field set.
187          *
188          * @param simpleFieldSet
189          *            The simple field set containing the ID of the Sone
190          * @param parameterName
191          *            The name under which the Sone ID is stored in the simple field
192          *            set
193          * @param mandatory
194          *            {@code true} if a valid Sone ID is required, {@code false}
195          *            otherwise
196          * @return The Sone, or {@code null} if {@code mandatory} is {@code false}
197          *         and the Sone ID is invalid
198          * @throws FcpException
199          *             if there is no Sone ID stored under the given parameter name,
200          *             or if {@code mandatory} is {@code true} and the Sone ID is
201          *             invalid
202          */
203         protected Optional<LocalSone> getLocalSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean mandatory) throws FcpException {
204                 String soneId = simpleFieldSet.get(parameterName);
205                 if (mandatory && (soneId == null)) {
206                         throw new FcpException("Could not load Sone ID from “" + parameterName + "”.");
207                 }
208                 Optional<LocalSone> sone = core.getLocalSone(soneId);
209                 if ((mandatory && !sone.isPresent()) || (mandatory && sone.isPresent() && !sone.get().isLocal())) {
210                         throw new FcpException("Could not load Sone from “" + soneId + "”.");
211                 }
212                 return sone;
213         }
214
215         /**
216          * Returns a post whose ID is a parameter in the given simple field set.
217          *
218          * @param simpleFieldSet
219          *            The simple field set containing the ID of the post
220          * @param parameterName
221          *            The name under which the post ID is stored in the simple field
222          *            set
223          * @return The post
224          * @throws FcpException
225          *             if there is no post ID stored under the given parameter name,
226          *             or if the post ID is invalid
227          */
228         protected Post getPost(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
229                 try {
230                         String postId = simpleFieldSet.getString(parameterName);
231                         Optional<Post> post = core.getPost(postId);
232                         if (!post.isPresent()) {
233                                 throw new FcpException("Could not load post from “" + postId + "”.");
234                         }
235                         return post.get();
236                 } catch (FSParseException fspe1) {
237                         throw new FcpException("Could not post ID from “" + parameterName + "”.", fspe1);
238                 }
239         }
240
241         /**
242          * Returns a reply whose ID is a parameter in the given simple field set.
243          *
244          * @param simpleFieldSet
245          *            The simple field set containing the ID of the reply
246          * @param parameterName
247          *            The name under which the reply ID is stored in the simple
248          *            field set
249          * @return The reply
250          * @throws FcpException
251          *             if there is no reply ID stored under the given parameter
252          *             name, or if the reply ID is invalid
253          */
254         protected PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
255                 try {
256                         String replyId = simpleFieldSet.getString(parameterName);
257                         Optional<PostReply> reply = core.getPostReply(replyId);
258                         if (!reply.isPresent()) {
259                                 throw new FcpException("Could not load reply from “" + replyId + "”.");
260                         }
261                         return reply.get();
262                 } catch (FSParseException fspe1) {
263                         throw new FcpException("Could not reply ID from “" + parameterName + "”.", fspe1);
264                 }
265         }
266
267         /**
268          * Creates a simple field set from the given Sone, including {@link Profile}
269          * information.
270          *
271          * @param sone
272          *            The Sone to encode
273          * @param prefix
274          *            The prefix for the field names (may be empty but not {@code
275          *            null})
276          * @param localSone
277          *            An optional local Sone that is used for Sone-specific data,
278          *            such as if the Sone is followed by the local Sone
279          * @return The simple field set containing the given Sone
280          */
281         protected static SimpleFieldSet encodeSone(Sone sone, String prefix, Optional<LocalSone> localSone) {
282                 SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
283
284                 soneBuilder.put(prefix + "Name", sone.getName());
285                 soneBuilder.put(prefix + "NiceName", SoneAccessor.getNiceName(sone));
286                 soneBuilder.put(prefix + "LastUpdated", sone.getTime());
287                 if (localSone.isPresent()) {
288                         soneBuilder.put(prefix + "Followed", String.valueOf(localSone.get().hasFriend(sone.getId())));
289                 }
290                 Profile profile = sone.getProfile();
291                 soneBuilder.put(prefix + "Field.Count", profile.getFields().size());
292                 int fieldIndex = 0;
293                 for (Field field : profile.getFields()) {
294                         soneBuilder.put(prefix + "Field." + fieldIndex + ".Name", field.getName());
295                         soneBuilder.put(prefix + "Field." + fieldIndex + ".Value", field.getValue());
296                         ++fieldIndex;
297                 }
298
299                 return soneBuilder.get();
300         }
301
302         /**
303          * Creates a simple field set from the given collection of Sones.
304          *
305          * @param sones
306          *            The Sones to encode
307          * @param prefix
308          *            The prefix for the field names (may be empty but not
309          *            {@code null})
310          * @return The simple field set containing the given Sones
311          */
312         protected static SimpleFieldSet encodeSones(Collection<? extends Sone> sones, String prefix) {
313                 SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
314
315                 int soneIndex = 0;
316                 soneBuilder.put(prefix + "Count", sones.size());
317                 for (Sone sone : sones) {
318                         String sonePrefix = prefix + soneIndex++ + ".";
319                         soneBuilder.put(sonePrefix + "ID", sone.getId());
320                         soneBuilder.put(sonePrefix + "Name", sone.getName());
321                         soneBuilder.put(sonePrefix + "NiceName", SoneAccessor.getNiceName(sone));
322                         soneBuilder.put(sonePrefix + "Time", sone.getTime());
323                 }
324
325                 return soneBuilder.get();
326         }
327
328         /**
329          * Creates a simple field set from the given post.
330          *
331          * @param post
332          *            The post to encode
333          * @param prefix
334          *            The prefix for the field names (may be empty but not
335          *            {@code null})
336          * @param includeReplies
337          *            {@code true} to include replies, {@code false} to not include
338          *            replies
339          * @return The simple field set containing the post
340          */
341         protected SimpleFieldSet encodePost(Post post, String prefix, boolean includeReplies) {
342                 SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
343
344                 postBuilder.put(prefix + "ID", post.getId());
345                 postBuilder.put(prefix + "Sone", post.getSone().getId());
346                 if (post.getRecipientId().isPresent()) {
347                         postBuilder.put(prefix + "Recipient", post.getRecipientId().get());
348                 }
349                 postBuilder.put(prefix + "Time", post.getTime());
350                 postBuilder.put(prefix + "Text", encodeString(post.getText()));
351                 postBuilder.put(encodeLikes(core.getLikes(post), prefix + "Likes."));
352
353                 if (includeReplies) {
354                         List<PostReply> replies = core.getReplies(post.getId());
355                         postBuilder.put(encodeReplies(replies, prefix));
356                 }
357
358                 return postBuilder.get();
359         }
360
361         /**
362          * Creates a simple field set from the given collection of posts.
363          *
364          * @param posts
365          *            The posts to encode
366          * @param prefix
367          *            The prefix for the field names (may be empty but not
368          *            {@code null})
369          * @param includeReplies
370          *            {@code true} to include the replies, {@code false} to not
371          *            include the replies
372          * @return The simple field set containing the posts
373          */
374         protected SimpleFieldSet encodePosts(Collection<? extends Post> posts, String prefix, boolean includeReplies) {
375                 SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
376
377                 int postIndex = 0;
378                 postBuilder.put(prefix + "Count", posts.size());
379                 for (Post post : posts) {
380                         String postPrefix = prefix + postIndex++;
381                         postBuilder.put(encodePost(post, postPrefix + ".", includeReplies));
382                         if (includeReplies) {
383                                 postBuilder.put(encodeReplies(Collections2.filter(core.getReplies(post.getId()), Reply.FUTURE_REPLY_FILTER), postPrefix + "."));
384                         }
385                 }
386
387                 return postBuilder.get();
388         }
389
390         /**
391          * Creates a simple field set from the given collection of replies.
392          *
393          * @param replies
394          *            The replies to encode
395          * @param prefix
396          *            The prefix for the field names (may be empty, but not
397          *            {@code null})
398          * @return The simple field set containing the replies
399          */
400         protected static SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
401                 SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder();
402
403                 int replyIndex = 0;
404                 replyBuilder.put(prefix + "Replies.Count", replies.size());
405                 for (PostReply reply : replies) {
406                         String replyPrefix = prefix + "Replies." + replyIndex++ + ".";
407                         replyBuilder.put(replyPrefix + "ID", reply.getId());
408                         replyBuilder.put(replyPrefix + "Sone", reply.getSone().getId());
409                         replyBuilder.put(replyPrefix + "Time", reply.getTime());
410                         replyBuilder.put(replyPrefix + "Text", encodeString(reply.getText()));
411                 }
412
413                 return replyBuilder.get();
414         }
415
416         /**
417          * Creates a simple field set from the given collection of Sones that like
418          * an element.
419          *
420          * @param likes
421          *            The liking Sones
422          * @param prefix
423          *            The prefix for the field names (may be empty but not
424          *            {@code null})
425          * @return The simple field set containing the likes
426          */
427         protected static SimpleFieldSet encodeLikes(Collection<? extends Sone> likes, String prefix) {
428                 SimpleFieldSetBuilder likesBuilder = new SimpleFieldSetBuilder();
429
430                 int likeIndex = 0;
431                 likesBuilder.put(prefix + "Count", likes.size());
432                 for (Sone sone : likes) {
433                         String sonePrefix = prefix + likeIndex++ + ".";
434                         likesBuilder.put(sonePrefix + "ID", sone.getId());
435                 }
436
437                 return likesBuilder.get();
438         }
439
440         //
441         // OBJECT METHODS
442         //
443
444         /**
445          * {@inheritDoc}
446          */
447         @Override
448         public String toString() {
449                 return getClass().getName() + "[writeAccess=" + writeAccess + "]";
450         }
451
452 }