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