🔀 Merge “release/v81” into “master”
[Sone.git] / src / main / java / net / pterodactylus / sone / core / SoneParser.java
index 5851b18..e1fd9ff 100644 (file)
@@ -1,52 +1,46 @@
 package net.pterodactylus.sone.core;
 
-import static java.util.logging.Logger.getLogger;
-import static net.pterodactylus.sone.utils.NumberParsers.parseInt;
-import static net.pterodactylus.sone.utils.NumberParsers.parseLong;
+import static java.util.concurrent.TimeUnit.*;
+import static java.util.logging.Logger.*;
+import static net.pterodactylus.sone.utils.NumberParsers.*;
 
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import java.io.*;
+import java.util.*;
+import java.util.logging.*;
 
-import net.pterodactylus.sone.data.Album;
-import net.pterodactylus.sone.data.Client;
-import net.pterodactylus.sone.data.Image;
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Profile;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.database.PostBuilder;
-import net.pterodactylus.sone.database.PostReplyBuilder;
-import net.pterodactylus.sone.database.SoneBuilder;
-import net.pterodactylus.util.xml.SimpleXML;
-import net.pterodactylus.util.xml.XML;
+import javax.annotation.*;
+import javax.inject.*;
 
-import org.w3c.dom.Document;
+import net.pterodactylus.sone.data.*;
+import net.pterodactylus.sone.data.Profile.*;
+import net.pterodactylus.sone.database.*;
+import net.pterodactylus.util.xml.*;
+
+import com.codahale.metrics.*;
+import com.google.common.base.*;
+import org.w3c.dom.*;
 
 /**
  * Parses a {@link Sone} from an XML {@link InputStream}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneParser {
 
-       private static final Logger logger = getLogger("Sone.Parser");
+       private static final Logger logger = getLogger(SoneParser.class.getName());
        private static final int MAX_PROTOCOL_VERSION = 0;
-       private final Core core;
+       private final Database database;
+       private final Histogram soneParsingDurationHistogram;
 
-       public SoneParser(Core core) {
-               this.core = core;
+       @Inject
+       public SoneParser(Database database, MetricRegistry metricRegistry) {
+               this.database = database;
+               this.soneParsingDurationHistogram = metricRegistry.histogram("sone.parse.duration", () -> new Histogram(new ExponentiallyDecayingReservoir(3000, 0)));
        }
 
+       @Nullable
        public Sone parseSone(Sone originalSone, InputStream soneInputStream) throws SoneException {
                /* TODO - impose a size limit? */
 
+               Stopwatch stopwatch = Stopwatch.createStarted();
                Document document;
                /* XML parsing is not thread-safe. */
                synchronized (this) {
@@ -58,7 +52,7 @@ public class SoneParser {
                        return null;
                }
 
-               SoneBuilder soneBuilder = core.soneBuilder().from(originalSone.getIdentity());
+               SoneBuilder soneBuilder = database.newSoneBuilder().from(originalSone.getIdentity());
                if (originalSone.isLocal()) {
                        soneBuilder = soneBuilder.local();
                }
@@ -150,8 +144,11 @@ public class SoneParser {
                                }
                                try {
                                        profile.addField(fieldName.trim()).setValue(fieldValue);
-                               } catch (IllegalArgumentException iae1) {
-                                       logger.log(Level.WARNING, String.format("Duplicate field: %s", fieldName), iae1);
+                               } catch (EmptyFieldName efn1) {
+                                       logger.log(Level.WARNING, "Empty field name!", efn1);
+                                       return null;
+                               } catch (DuplicateField df1) {
+                                       logger.log(Level.WARNING, String.format("Duplicate field: %s", fieldName), df1);
                                        return null;
                                }
                        }
@@ -159,7 +156,7 @@ public class SoneParser {
 
                /* parse posts. */
                SimpleXML postsXml = soneXml.getNode("posts");
-               Set<Post> posts = new HashSet<Post>();
+               Set<Post> posts = new HashSet<>();
                if (postsXml == null) {
                        /* TODO - mark Sone as bad. */
                        logger.log(Level.WARNING, String.format("Downloaded Sone %s has no posts!", sone));
@@ -175,7 +172,7 @@ public class SoneParser {
                                        return null;
                                }
                                try {
-                                       PostBuilder postBuilder = core.postBuilder();
+                                       PostBuilder postBuilder = database.newPostBuilder();
                                        /* TODO - parse time correctly. */
                                        postBuilder.withId(postId).from(sone.getId()).withTime(Long.parseLong(postTime)).withText(postText);
                                        if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
@@ -192,7 +189,7 @@ public class SoneParser {
 
                /* parse replies. */
                SimpleXML repliesXml = soneXml.getNode("replies");
-               Set<PostReply> replies = new HashSet<PostReply>();
+               Set<PostReply> replies = new HashSet<>();
                if (repliesXml == null) {
                        /* TODO - mark Sone as bad. */
                        logger.log(Level.WARNING, String.format("Downloaded Sone %s has no replies!", sone));
@@ -208,7 +205,7 @@ public class SoneParser {
                                        return null;
                                }
                                try {
-                                       PostReplyBuilder postReplyBuilder = core.postReplyBuilder();
+                                       PostReplyBuilder postReplyBuilder = database.newPostReplyBuilder();
                                        /* TODO - parse time correctly. */
                                        postReplyBuilder.withId(replyId).from(sone.getId()).to(replyPostId).withTime(Long.parseLong(replyTime)).withText(replyText);
                                        replies.add(postReplyBuilder.build());
@@ -222,7 +219,7 @@ public class SoneParser {
 
                /* parse liked post IDs. */
                SimpleXML likePostIdsXml = soneXml.getNode("post-likes");
-               Set<String> likedPostIds = new HashSet<String>();
+               Set<String> likedPostIds = new HashSet<>();
                if (likePostIdsXml == null) {
                        /* TODO - mark Sone as bad. */
                        logger.log(Level.WARNING, String.format("Downloaded Sone %s has no post likes!", sone));
@@ -235,7 +232,7 @@ public class SoneParser {
 
                /* parse liked reply IDs. */
                SimpleXML likeReplyIdsXml = soneXml.getNode("reply-likes");
-               Set<String> likedReplyIds = new HashSet<String>();
+               Set<String> likedReplyIds = new HashSet<>();
                if (likeReplyIdsXml == null) {
                        /* TODO - mark Sone as bad. */
                        logger.log(Level.WARNING, String.format("Downloaded Sone %s has no reply likes!", sone));
@@ -248,28 +245,28 @@ public class SoneParser {
 
                /* parse albums. */
                SimpleXML albumsXml = soneXml.getNode("albums");
-               Map<String, Image> allImages = new HashMap<String, Image>();
-               List<Album> topLevelAlbums = new ArrayList<Album>();
+               Map<String, Image> allImages = new HashMap<>();
+               List<Album> topLevelAlbums = new ArrayList<>();
+               Map<String, Album> allAlbums = new HashMap<>();
                if (albumsXml != null) {
                        for (SimpleXML albumXml : albumsXml.getNodes("album")) {
                                String id = albumXml.getValue("id", null);
                                String parentId = albumXml.getValue("parent", null);
                                String title = albumXml.getValue("title", null);
                                String description = albumXml.getValue("description", "");
-                               String albumImageId = albumXml.getValue("album-image", null);
                                if ((id == null) || (title == null)) {
                                        logger.log(Level.WARNING, String.format("Downloaded Sone %s contains invalid album!", sone));
                                        return null;
                                }
                                Album parent = null;
                                if (parentId != null) {
-                                       parent = core.getAlbum(parentId);
+                                       parent = allAlbums.get(parentId);
                                        if (parent == null) {
                                                logger.log(Level.WARNING, String.format("Downloaded Sone %s has album with invalid parent!", sone));
                                                return null;
                                        }
                                }
-                               Album album = core.albumBuilder()
+                               Album album = database.newAlbumBuilder()
                                                .withId(id)
                                                .by(sone)
                                                .build()
@@ -282,6 +279,7 @@ public class SoneParser {
                                } else {
                                        topLevelAlbums.add(album);
                                }
+                               allAlbums.put(album.getId(), album);
                                SimpleXML imagesXml = albumXml.getNode("images");
                                if (imagesXml != null) {
                                        for (SimpleXML imageXml : imagesXml.getNodes("image")) {
@@ -303,14 +301,13 @@ public class SoneParser {
                                                        logger.log(Level.WARNING, String.format("Downloaded Sone %s contains image %s with invalid dimensions (%s, %s)!", sone, imageId, imageWidthString, imageHeightString));
                                                        return null;
                                                }
-                                               Image image = core.imageBuilder().withId(imageId).build().modify().setSone(sone).setKey(imageKey).setCreationTime(creationTime).update();
+                                               Image image = database.newImageBuilder().withId(imageId).build().modify().setSone(sone).setKey(imageKey).setCreationTime(creationTime).update();
                                                image = image.modify().setTitle(imageTitle).setDescription(imageDescription).update();
                                                image = image.modify().setWidth(imageWidth).setHeight(imageHeight).update();
                                                album.addImage(image);
                                                allImages.put(imageId, image);
                                        }
                                }
-                               album.modify().setAlbumImage(albumImageId).update();
                        }
                }
 
@@ -320,18 +317,20 @@ public class SoneParser {
                }
 
                /* okay, apparently everything was parsed correctly. Now import. */
-               /* atomic setter operation on the Sone. */
-               synchronized (sone) {
-                       sone.setProfile(profile);
-                       sone.setPosts(posts);
-                       sone.setReplies(replies);
-                       sone.setLikePostIds(likedPostIds);
-                       sone.setLikeReplyIds(likedReplyIds);
-                       for (Album album : topLevelAlbums) {
-                               sone.getRootAlbum().addAlbum(album);
-                       }
+               sone.setProfile(profile);
+               sone.setPosts(posts);
+               sone.setReplies(replies);
+               sone.setLikePostIds(likedPostIds);
+               sone.setLikeReplyIds(likedReplyIds);
+               for (Album album : topLevelAlbums) {
+                       sone.getRootAlbum().addAlbum(album);
                }
 
+               // record the duration
+               stopwatch.stop();
+               soneParsingDurationHistogram.update(stopwatch.elapsed(MICROSECONDS));
+               logger.fine(() -> "Parsed " + originalSone.getIdentity().getId() + "@" + originalSone.getLatestEdition() + " in " + stopwatch.elapsed(MICROSECONDS) + "μs.");
+
                return sone;
 
        }