Replace PreferencesTest with Kotlin version
[Sone.git] / src / main / java / net / pterodactylus / sone / fcp / AbstractSoneCommand.java
1 /*
2  * Sone - AbstractSoneCommand.java - Copyright © 2011–2016 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.PostReply;
26 import net.pterodactylus.sone.data.Profile;
27 import net.pterodactylus.sone.data.Profile.Field;
28 import net.pterodactylus.sone.data.Sone;
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
35 import freenet.node.FSParseException;
36 import freenet.support.SimpleFieldSet;
37
38 import com.google.common.base.Optional;
39
40 /**
41  * Abstract base implementation of a {@link Command} with Sone-related helper
42  * methods.
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 static 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).get();
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 Optional<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 = core.getSone(soneId);
163                 if ((mandatory && (sone == null)) || ((sone != null) && localOnly && !sone.isLocal())) {
164                         throw new FcpException("Could not load Sone from “" + soneId + "”.");
165                 }
166                 return Optional.fromNullable(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);
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 PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
209                 try {
210                         String replyId = simpleFieldSet.getString(parameterName);
211                         PostReply reply = core.getPostReply(replyId);
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 static SimpleFieldSet encodeSone(Sone sone, String prefix, Optional<Sone> localSone) {
236                 SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
237
238                 soneBuilder.put(prefix + "ID", sone.getId());
239                 soneBuilder.put(prefix + "Name", sone.getName());
240                 soneBuilder.put(prefix + "NiceName", SoneAccessor.getNiceName(sone));
241                 soneBuilder.put(prefix + "LastUpdated", sone.getTime());
242                 if (localSone.isPresent()) {
243                         soneBuilder.put(prefix + "Followed", String.valueOf(localSone.get().hasFriend(sone.getId())));
244                 }
245                 Profile profile = sone.getProfile();
246                 soneBuilder.put(prefix + "Field.Count", profile.getFields().size());
247                 int fieldIndex = 0;
248                 for (Field field : profile.getFields()) {
249                         soneBuilder.put(prefix + "Field." + fieldIndex + ".Name", field.getName());
250                         soneBuilder.put(prefix + "Field." + fieldIndex + ".Value", field.getValue());
251                         ++fieldIndex;
252                 }
253
254                 return soneBuilder.get();
255         }
256
257         /**
258          * Creates a simple field set from the given collection of Sones.
259          *
260          * @param sones
261          *            The Sones to encode
262          * @param prefix
263          *            The prefix for the field names (may be empty but not
264          *            {@code null})
265          * @return The simple field set containing the given Sones
266          */
267         protected static SimpleFieldSet encodeSones(Collection<? extends Sone> sones, String prefix) {
268                 SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
269
270                 int soneIndex = 0;
271                 soneBuilder.put(prefix + "Count", sones.size());
272                 for (Sone sone : sones) {
273                         String sonePrefix = prefix + soneIndex++ + ".";
274                         soneBuilder.put(encodeSone(sone, sonePrefix, Optional.<Sone>absent()));
275                 }
276
277                 return soneBuilder.get();
278         }
279
280         /**
281          * Creates a simple field set from the given post.
282          *
283          * @param post
284          *            The post to encode
285          * @param prefix
286          *            The prefix for the field names (may be empty but not
287          *            {@code null})
288          * @param includeReplies
289          *            {@code true} to include replies, {@code false} to not include
290          *            replies
291          * @return The simple field set containing the post
292          */
293         protected SimpleFieldSet encodePost(Post post, String prefix, boolean includeReplies) {
294                 SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
295
296                 postBuilder.put(prefix + "ID", post.getId());
297                 postBuilder.put(prefix + "Sone", post.getSone().getId());
298                 if (post.getRecipientId().isPresent()) {
299                         postBuilder.put(prefix + "Recipient", post.getRecipientId().get());
300                 }
301                 postBuilder.put(prefix + "Time", post.getTime());
302                 postBuilder.put(prefix + "Text", encodeString(post.getText()));
303                 postBuilder.put(encodeLikes(core.getLikes(post), prefix + "Likes."));
304
305                 if (includeReplies) {
306                         List<PostReply> replies = core.getReplies(post.getId());
307                         postBuilder.put(encodeReplies(replies, prefix));
308                 }
309
310                 return postBuilder.get();
311         }
312
313         /**
314          * Creates a simple field set from the given collection of posts.
315          *
316          * @param posts
317          *            The posts to encode
318          * @param prefix
319          *            The prefix for the field names (may be empty but not
320          *            {@code null})
321          * @param includeReplies
322          *            {@code true} to include the replies, {@code false} to not
323          *            include the replies
324          * @return The simple field set containing the posts
325          */
326         protected SimpleFieldSet encodePosts(Collection<? extends Post> posts, String prefix, boolean includeReplies) {
327                 SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
328
329                 int postIndex = 0;
330                 postBuilder.put(prefix + "Count", posts.size());
331                 for (Post post : posts) {
332                         String postPrefix = prefix + postIndex++;
333                         postBuilder.put(encodePost(post, postPrefix + ".", includeReplies));
334                 }
335
336                 return postBuilder.get();
337         }
338
339         /**
340          * Creates a simple field set from the given collection of replies.
341          *
342          * @param replies
343          *            The replies to encode
344          * @param prefix
345          *            The prefix for the field names (may be empty, but not
346          *            {@code null})
347          * @return The simple field set containing the replies
348          */
349         protected SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
350                 SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder();
351
352                 int replyIndex = 0;
353                 replyBuilder.put(prefix + "Replies.Count", replies.size());
354                 for (PostReply reply : replies) {
355                         String replyPrefix = prefix + "Replies." + replyIndex++ + ".";
356                         replyBuilder.put(replyPrefix + "ID", reply.getId());
357                         replyBuilder.put(replyPrefix + "Sone", reply.getSone().getId());
358                         replyBuilder.put(replyPrefix + "Time", reply.getTime());
359                         replyBuilder.put(replyPrefix + "Text", encodeString(reply.getText()));
360                         replyBuilder.put(encodeLikes(core.getLikes(reply), replyPrefix + "Likes."));
361                 }
362
363                 return replyBuilder.get();
364         }
365
366         /**
367          * Creates a simple field set from the given collection of Sones that like
368          * an element.
369          *
370          * @param likes
371          *            The liking Sones
372          * @param prefix
373          *            The prefix for the field names (may be empty but not
374          *            {@code null})
375          * @return The simple field set containing the likes
376          */
377         protected static SimpleFieldSet encodeLikes(Collection<? extends Sone> likes, String prefix) {
378                 SimpleFieldSetBuilder likesBuilder = new SimpleFieldSetBuilder();
379
380                 int likeIndex = 0;
381                 likesBuilder.put(prefix + "Count", likes.size());
382                 for (Sone sone : likes) {
383                         String sonePrefix = prefix + likeIndex++ + ".";
384                         likesBuilder.put(sonePrefix + "ID", sone.getId());
385                 }
386
387                 return likesBuilder.get();
388         }
389
390         //
391         // OBJECT METHODS
392         //
393
394         /**
395          * {@inheritDoc}
396          */
397         @Override
398         public String toString() {
399                 return getClass().getName() + "[writeAccess=" + writeAccess + "]";
400         }
401
402 }