From: David ‘Bombe’ Roden Date: Thu, 13 Sep 2012 12:34:58 +0000 (+0200) Subject: Merge branch 'next' into new-database-38 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=927de326e0af0f11a27b3444f4e25b0796c877db Merge branch 'next' into new-database-38 Conflicts: src/main/java/net/pterodactylus/sone/core/Core.java src/main/java/net/pterodactylus/sone/data/Sone.java src/main/java/net/pterodactylus/sone/main/SonePlugin.java src/main/java/net/pterodactylus/sone/text/SoneTextParser.java --- 927de326e0af0f11a27b3444f4e25b0796c877db diff --cc src/main/java/net/pterodactylus/sone/core/Core.java index 922f0d1,9e1e6c6..0b1379a --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@@ -47,7 -46,7 +47,8 @@@ import net.pterodactylus.sone.data.Sone import net.pterodactylus.sone.data.Sone.ShowCustomAvatars; import net.pterodactylus.sone.data.Sone.SoneStatus; import net.pterodactylus.sone.data.TemporaryImage; + import net.pterodactylus.sone.data.impl.PostImpl; +import net.pterodactylus.sone.database.Database; import net.pterodactylus.sone.fcp.FcpInterface; import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired; import net.pterodactylus.sone.freenet.wot.Identity; @@@ -182,11 -185,12 +184,13 @@@ public class Core extends AbstractServi * The freenet interface * @param identityManager * The identity manager + * @param webOfTrustUpdater + * The WebOfTrust updater */ - public Core(Configuration configuration, Database database, FreenetInterface freenetInterface, IdentityManager identityManager) { - public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater) { ++ public Core(Configuration configuration, Database database, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater) { super("Sone Core"); this.configuration = configuration; + this.database = database; this.freenetInterface = freenetInterface; this.identityManager = identityManager; this.soneDownloader = new SoneDownloader(this, freenetInterface); @@@ -969,26 -1041,7 +948,8 @@@ } /** - * Retrieves the trust relationship from the origin to the target. If the - * trust relationship can not be retrieved, {@code null} is returned. - * - * @see Identity#getTrust(OwnIdentity) - * @param origin - * The origin of the trust tree - * @param target - * The target of the trust - * @return The trust relationship - */ - public Trust getTrust(Sone origin, Sone target) { - if (!origin.isLocal()) { - logger.log(Level.WARNING, String.format("Tried to get trust from remote Sone: %s", origin)); - return null; - } - return target.getIdentity().getTrust((OwnIdentity) origin.getIdentity()); - } - - /** -- * Sets the trust value of the given origin Sone for the target Sone. ++ * Sets the trust value of the given origin Sone for ++ * the target Sone. * * @param origin * The origin Sone @@@ -1200,20 -1245,18 +1153,16 @@@ logger.log(Level.WARNING, String.format("Tried to delete Sone of non-own identity: %s", sone)); return; } - synchronized (localSones) { - if (!localSones.containsKey(sone.getId())) { - logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone)); - return; - } - localSones.remove(sone.getId()); - SoneInserter soneInserter = soneInserters.remove(sone); - soneInserter.removeSoneInsertListener(this); - soneInserter.stop(); + if (!sone.isLocal()) { + logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone)); + return; } + database.removeSone(sone.getId()); + SoneInserter soneInserter = soneInserters.remove(sone); + soneInserter.removeSoneInsertListener(this); + soneInserter.stop(); - try { - ((OwnIdentity) sone.getIdentity()).removeContext("Sone"); - ((OwnIdentity) sone.getIdentity()).removeProperty("Sone.LatestEdition"); - } catch (WebOfTrustException wote1) { - logger.log(Level.WARNING, String.format("Could not remove context and properties from Sone: %s", sone), wote1); - } + webOfTrustUpdater.removeContext((OwnIdentity) sone.getIdentity(), "Sone"); + webOfTrustUpdater.removeProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition"); try { configuration.getLongValue("Sone/" + sone.getId() + "/Time").setValue(null); } catch (ConfigurationException ce1) { @@@ -1877,10 -1965,15 +1827,13 @@@ */ @Override public void serviceStop() { - for (SoneInserter soneInserter : soneInserters.values()) { - soneInserter.removeSoneInsertListener(this); - soneInserter.stop(); - synchronized (localSones) { - for (Entry soneInserter : soneInserters.entrySet()) { - soneInserter.getValue().removeSoneInsertListener(this); - soneInserter.getValue().stop(); - saveSone(soneInserter.getKey()); - } ++ for (Entry soneInserter : soneInserters.entrySet()) { ++ soneInserter.getValue().removeSoneInsertListener(this); ++ soneInserter.getValue().stop(); ++ saveSone(soneInserter.getKey()); } + saveConfiguration(); + webOfTrustUpdater.stop(); updateChecker.stop(); updateChecker.removeUpdateListener(this); soneDownloader.stop(); diff --cc src/main/java/net/pterodactylus/sone/database/memory/MemorySone.java index 1c5912f,0000000..da487b3 mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemorySone.java +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemorySone.java @@@ -1,735 -1,0 +1,737 @@@ +/* + * Sone - MemorySone.java - Copyright © 2010–2012 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.database.memory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.pterodactylus.sone.core.Options; +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.Reply; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.database.Database; +import net.pterodactylus.sone.freenet.wot.Identity; +import net.pterodactylus.util.logging.Logging; +import net.pterodactylus.util.validation.Validation; +import freenet.keys.FreenetURI; + +/** + * Implementation of a {@link Sone} that keeps all added data in memory. A + * self-created instance of this class should be converted to a {@link Database} + * -based instance of {@link Sone} as soon as possible (unless it was returned + * by a {@link MemoryDatabase}). + * + * @author David ‘Bombe’ Roden + */ +public class MemorySone implements Sone { + + /** The logger. */ + private static final Logger logger = Logging.getLogger(Sone.class); + + /** The ID of this Sone. */ + private final String id; + + /** Whether this is a local Sone. */ + private final boolean local; + + /** The identity of this Sone. */ + private Identity identity; + + /** The URI under which the Sone is stored in Freenet. */ + private volatile FreenetURI requestUri; + + /** The URI used to insert a new version of this Sone. */ + /* This will be null for remote Sones! */ + private volatile FreenetURI insertUri; + + /** The latest edition of the Sone. */ + private volatile long latestEdition; + + /** The time of the last inserted update. */ + private volatile long time; + + /** The status of this Sone. */ + private volatile SoneStatus status = SoneStatus.unknown; + + /** The profile of this Sone. */ + private volatile Profile profile = new Profile(this); + + /** The client used by the Sone. */ + private volatile Client client; + + /** Whether this Sone is known. */ + private volatile boolean known; + + /** All friend Sones. */ + private final Set friendSones = new CopyOnWriteArraySet(); + + /** All posts. */ + private final Set posts = new CopyOnWriteArraySet(); + + /** All replies. */ + private final Set replies = new CopyOnWriteArraySet(); + + /** The IDs of all liked posts. */ + private final Set likedPostIds = new CopyOnWriteArraySet(); + + /** The IDs of all liked replies. */ + private final Set likedReplyIds = new CopyOnWriteArraySet(); + + /** The albums of this Sone. */ + private final List albums = new CopyOnWriteArrayList(); + + /** Sone-specific options. */ + private final Options options = new Options(); + + /** + * Creates a new Sone. + * + * @param id + * The ID of the Sone + * @param local + * {@code true} if this Sone is local, {@code false} otherwise + */ + public MemorySone(String id, boolean local) { + this.id = id; + this.local = local; + } + + // + // ACCESSORS + // + + /** + * {@inheritDoc} + */ + @Override + public String getId() { + return id; + } + + /** + * {@inheritDoc} + */ + @Override + public Identity getIdentity() { + return identity; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setIdentity(Identity identity) throws IllegalArgumentException { + if (!identity.getId().equals(id)) { + throw new IllegalArgumentException("Identity’s ID does not match Sone’s ID!"); + } + this.identity = identity; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLocal() { + return local; + } + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return (identity != null) ? identity.getNickname() : null; + } + + /** + * {@inheritDoc} + */ + @Override + public FreenetURI getRequestUri() { + return (requestUri != null) ? requestUri.setSuggestedEdition(latestEdition) : null; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setRequestUri(FreenetURI requestUri) { + if (this.requestUri == null) { + this.requestUri = requestUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]); + return this; + } + if (!this.requestUri.equalsKeypair(requestUri)) { + logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", requestUri, this.requestUri)); + return this; + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public FreenetURI getInsertUri() { + return (insertUri != null) ? insertUri.setSuggestedEdition(latestEdition) : null; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setInsertUri(FreenetURI insertUri) { + if (this.insertUri == null) { + this.insertUri = insertUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]); + return this; + } + if (!this.insertUri.equalsKeypair(insertUri)) { + logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", insertUri, this.insertUri)); + return this; + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public long getLatestEdition() { + return latestEdition; + } + + /** + * {@inheritDoc} + */ + @Override + public void setLatestEdition(long latestEdition) { + if (!(latestEdition > this.latestEdition)) { + logger.log(Level.FINE, String.format("New latest edition %d is not greater than current latest edition %d!", latestEdition, this.latestEdition)); + return; + } + this.latestEdition = latestEdition; + } + + /** + * {@inheritDoc} + */ + @Override + public long getTime() { + return time; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setTime(long time) { + this.time = time; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public SoneStatus getStatus() { + return status; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setStatus(SoneStatus status) { + Validation.begin().isNotNull("Sone Status", status).check(); + this.status = status; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Profile getProfile() { + return new Profile(profile); + } + + /** + * {@inheritDoc} + */ + @Override + public void setProfile(Profile profile) { + this.profile = new Profile(profile); + } + + /** + * {@inheritDoc} + */ + @Override + public Client getClient() { + return client; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setClient(Client client) { + this.client = client; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isKnown() { + return known; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setKnown(boolean known) { + this.known = known; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List getFriends() { + List friends = new ArrayList(friendSones); + return friends; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasFriend(String friendSoneId) { + return friendSones.contains(friendSoneId); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone addFriend(String friendSone) { + if (!friendSone.equals(id)) { + friendSones.add(friendSone); + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone removeFriend(String friendSoneId) { + friendSones.remove(friendSoneId); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List getPosts() { + List sortedPosts; + synchronized (this) { + sortedPosts = new ArrayList(posts); + } + Collections.sort(sortedPosts, Post.TIME_COMPARATOR); + return sortedPosts; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setPosts(Collection posts) { + synchronized (this) { + this.posts.clear(); + this.posts.addAll(posts); + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public void addPost(Post post) { + if (post.getSone().equals(this) && posts.add(post)) { + logger.log(Level.FINEST, String.format("Adding %s to “%s”.", post, getName())); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removePost(Post post) { + if (post.getSone().equals(this)) { + posts.remove(post); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set getReplies() { + return Collections.unmodifiableSet(replies); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setReplies(Collection replies) { + this.replies.clear(); + this.replies.addAll(replies); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public void addReply(PostReply reply) { + if (reply.getSone().equals(this)) { + replies.add(reply); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeReply(PostReply reply) { + if (reply.getSone().equals(this)) { + replies.remove(reply); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set getLikedPostIds() { + return Collections.unmodifiableSet(likedPostIds); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setLikePostIds(Set likedPostIds) { + this.likedPostIds.clear(); + this.likedPostIds.addAll(likedPostIds); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLikedPostId(String postId) { + return likedPostIds.contains(postId); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone addLikedPostId(String postId) { + likedPostIds.add(postId); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone removeLikedPostId(String postId) { + likedPostIds.remove(postId); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getLikedReplyIds() { + return Collections.unmodifiableSet(likedReplyIds); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setLikeReplyIds(Set likedReplyIds) { + this.likedReplyIds.clear(); + this.likedReplyIds.addAll(likedReplyIds); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLikedReplyId(String replyId) { + return likedReplyIds.contains(replyId); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone addLikedReplyId(String replyId) { + likedReplyIds.add(replyId); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone removeLikedReplyId(String replyId) { + likedReplyIds.remove(replyId); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List getAlbums() { + return Collections.unmodifiableList(albums); + } + + /** + * {@inheritDoc} + */ + @Override + public List getAllAlbums() { + List flatAlbums = new ArrayList(); + flatAlbums.addAll(albums); + int lastAlbumIndex = 0; + while (lastAlbumIndex < flatAlbums.size()) { + int previousAlbumCount = flatAlbums.size(); + for (Album album : new ArrayList(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) { + flatAlbums.addAll(album.getAlbums()); + } + lastAlbumIndex = previousAlbumCount; + } + return flatAlbums; + } + + /** + * {@inheritDoc} + */ + @Override + public List getAllImages() { + List allImages = new ArrayList(); + for (Album album : getAllAlbums()) { + allImages.addAll(album.getImages()); + } + return allImages; + } + + /** + * {@inheritDoc} + */ + @Override + public void addAlbum(Album album) { + Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check(); - albums.add(album); ++ if (!albums.contains(album)) { ++ albums.add(album); ++ } + } + + /** + * {@inheritDoc} + */ + @Override + public void setAlbums(Collection albums) { + Validation.begin().isNotNull("Albums", albums).check(); + this.albums.clear(); + for (Album album : albums) { + addAlbum(album); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeAlbum(Album album) { + Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check(); + albums.remove(album); + } + + /** + * {@inheritDoc} + */ + @Override + public Album moveAlbumUp(Album album) { + Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check(); + int oldIndex = albums.indexOf(album); + if (oldIndex <= 0) { + return null; + } + albums.remove(oldIndex); + albums.add(oldIndex - 1, album); + return albums.get(oldIndex); + } + + /** + * {@inheritDoc} + */ + @Override + public Album moveAlbumDown(Album album) { + Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check(); + int oldIndex = albums.indexOf(album); + if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) { + return null; + } + albums.remove(oldIndex); + albums.add(oldIndex + 1, album); + return albums.get(oldIndex); + } + + /** + * {@inheritDoc} + */ + @Override + public Options getOptions() { + return options; + } + + // + // FINGERPRINTABLE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public synchronized String getFingerprint() { + StringBuilder fingerprint = new StringBuilder(); + fingerprint.append(profile.getFingerprint()); + + fingerprint.append("Posts("); + for (Post post : getPosts()) { + fingerprint.append("Post(").append(post.getId()).append(')'); + } + fingerprint.append(")"); + + List replies = new ArrayList(getReplies()); + Collections.sort(replies, Reply.TIME_COMPARATOR); + fingerprint.append("Replies("); + for (PostReply reply : replies) { + fingerprint.append("Reply(").append(reply.getId()).append(')'); + } + fingerprint.append(')'); + + List likedPostIds = new ArrayList(getLikedPostIds()); + Collections.sort(likedPostIds); + fingerprint.append("LikedPosts("); + for (String likedPostId : likedPostIds) { + fingerprint.append("Post(").append(likedPostId).append(')'); + } + fingerprint.append(')'); + + List likedReplyIds = new ArrayList(getLikedReplyIds()); + Collections.sort(likedReplyIds); + fingerprint.append("LikedReplies("); + for (String likedReplyId : likedReplyIds) { + fingerprint.append("Reply(").append(likedReplyId).append(')'); + } + fingerprint.append(')'); + + fingerprint.append("Albums("); + for (Album album : albums) { + fingerprint.append(album.getFingerprint()); + } + fingerprint.append(')'); + + return fingerprint.toString(); + } + + // + // INTERFACE Comparable + // + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(Sone sone) { + return NICE_NAME_COMPARATOR.compare(this, sone); + } + + // + // OBJECT METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return id.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof Sone)) { + return false; + } + return ((Sone) object).getId().equals(id); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return getClass().getName() + "[identity=" + identity + ",requestUri=" + requestUri + ",insertUri(" + String.valueOf(insertUri).length() + "),friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + ")]"; + } + +} diff --cc src/main/java/net/pterodactylus/sone/main/SonePlugin.java index da50101,d16e931..42e2fe1 --- a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java +++ b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java @@@ -24,8 -24,7 +24,9 @@@ import java.util.logging.Logger import net.pterodactylus.sone.core.Core; import net.pterodactylus.sone.core.FreenetInterface; + import net.pterodactylus.sone.core.WebOfTrustUpdater; +import net.pterodactylus.sone.database.Database; +import net.pterodactylus.sone.database.memory.MemoryDatabase; import net.pterodactylus.sone.fcp.FcpInterface; import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend; import net.pterodactylus.sone.freenet.plugin.PluginConnector; @@@ -190,11 -189,12 +191,15 @@@ public class SonePlugin implements Fred identityManager = new IdentityManager(webOfTrustConnector); identityManager.setContext("Sone"); + /* create Sone database. */ + Database soneDatabase = new MemoryDatabase(); + + /* create trust updater. */ + WebOfTrustUpdater trustUpdater = new WebOfTrustUpdater(webOfTrustConnector); + trustUpdater.init(); + /* create core. */ - core = new Core(oldConfiguration, soneDatabase, freenetInterface, identityManager); - core = new Core(oldConfiguration, freenetInterface, identityManager, trustUpdater); ++ core = new Core(oldConfiguration, soneDatabase, freenetInterface, identityManager, trustUpdater); /* create the web interface. */ webInterface = new WebInterface(this); diff --cc src/main/java/net/pterodactylus/sone/text/SoneTextParser.java index 558edb0,baa3dc9..19db65b --- a/src/main/java/net/pterodactylus/sone/text/SoneTextParser.java +++ b/src/main/java/net/pterodactylus/sone/text/SoneTextParser.java @@@ -30,7 -30,7 +30,8 @@@ import net.pterodactylus.sone.core.Post import net.pterodactylus.sone.core.SoneProvider; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.database.memory.MemorySone; + import net.pterodactylus.util.io.Closer; import net.pterodactylus.util.logging.Logging; import freenet.keys.FreenetURI; @@@ -110,187 -110,193 +111,193 @@@ public class SoneTextParser implements public Iterable parse(SoneTextParserContext context, Reader source) throws IOException { PartContainer parts = new PartContainer(); BufferedReader bufferedReader = (source instanceof BufferedReader) ? (BufferedReader) source : new BufferedReader(source); - String line; - boolean lastLineEmpty = true; - int emptyLines = 0; - while ((line = bufferedReader.readLine()) != null) { - if (line.trim().length() == 0) { - if (lastLineEmpty) { + try { + String line; + boolean lastLineEmpty = true; + int emptyLines = 0; + while ((line = bufferedReader.readLine()) != null) { + if (line.trim().length() == 0) { + if (lastLineEmpty) { + continue; + } + parts.add(new PlainTextPart("\n")); + ++emptyLines; + lastLineEmpty = emptyLines == 2; continue; } - parts.add(new PlainTextPart("\n")); - ++emptyLines; - lastLineEmpty = emptyLines == 2; - continue; - } - emptyLines = 0; - /* - * lineComplete tracks whether the block you are parsing is the - * first block of the line. this is important because sometimes you - * have to add an additional line break. - */ - boolean lineComplete = true; - while (line.length() > 0) { - int nextKsk = line.indexOf("KSK@"); - int nextChk = line.indexOf("CHK@"); - int nextSsk = line.indexOf("SSK@"); - int nextUsk = line.indexOf("USK@"); - int nextHttp = line.indexOf("http://"); - int nextHttps = line.indexOf("https://"); - int nextSone = line.indexOf("sone://"); - int nextPost = line.indexOf("post://"); - if ((nextKsk == -1) && (nextChk == -1) && (nextSsk == -1) && (nextUsk == -1) && (nextHttp == -1) && (nextHttps == -1) && (nextSone == -1) && (nextPost == -1)) { - if (lineComplete && !lastLineEmpty) { - parts.add(new PlainTextPart("\n" + line)); - } else { - parts.add(new PlainTextPart(line)); + emptyLines = 0; + /* + * lineComplete tracks whether the block you are parsing is the + * first block of the line. this is important because sometimes + * you have to add an additional line break. + */ + boolean lineComplete = true; + while (line.length() > 0) { + int nextKsk = line.indexOf("KSK@"); + int nextChk = line.indexOf("CHK@"); + int nextSsk = line.indexOf("SSK@"); + int nextUsk = line.indexOf("USK@"); + int nextHttp = line.indexOf("http://"); + int nextHttps = line.indexOf("https://"); + int nextSone = line.indexOf("sone://"); + int nextPost = line.indexOf("post://"); + if ((nextKsk == -1) && (nextChk == -1) && (nextSsk == -1) && (nextUsk == -1) && (nextHttp == -1) && (nextHttps == -1) && (nextSone == -1) && (nextPost == -1)) { + if (lineComplete && !lastLineEmpty) { + parts.add(new PlainTextPart("\n" + line)); + } else { + parts.add(new PlainTextPart(line)); + } + break; + } + int next = Integer.MAX_VALUE; + LinkType linkType = null; + if ((nextKsk > -1) && (nextKsk < next)) { + next = nextKsk; + linkType = LinkType.KSK; + } + if ((nextChk > -1) && (nextChk < next)) { + next = nextChk; + linkType = LinkType.CHK; + } + if ((nextSsk > -1) && (nextSsk < next)) { + next = nextSsk; + linkType = LinkType.SSK; + } + if ((nextUsk > -1) && (nextUsk < next)) { + next = nextUsk; + linkType = LinkType.USK; + } + if ((nextHttp > -1) && (nextHttp < next)) { + next = nextHttp; + linkType = LinkType.HTTP; + } + if ((nextHttps > -1) && (nextHttps < next)) { + next = nextHttps; + linkType = LinkType.HTTPS; + } + if ((nextSone > -1) && (nextSone < next)) { + next = nextSone; + linkType = LinkType.SONE; + } + if ((nextPost > -1) && (nextPost < next)) { + next = nextPost; + linkType = LinkType.POST; } - break; - } - int next = Integer.MAX_VALUE; - LinkType linkType = null; - if ((nextKsk > -1) && (nextKsk < next)) { - next = nextKsk; - linkType = LinkType.KSK; - } - if ((nextChk > -1) && (nextChk < next)) { - next = nextChk; - linkType = LinkType.CHK; - } - if ((nextSsk > -1) && (nextSsk < next)) { - next = nextSsk; - linkType = LinkType.SSK; - } - if ((nextUsk > -1) && (nextUsk < next)) { - next = nextUsk; - linkType = LinkType.USK; - } - if ((nextHttp > -1) && (nextHttp < next)) { - next = nextHttp; - linkType = LinkType.HTTP; - } - if ((nextHttps > -1) && (nextHttps < next)) { - next = nextHttps; - linkType = LinkType.HTTPS; - } - if ((nextSone > -1) && (nextSone < next)) { - next = nextSone; - linkType = LinkType.SONE; - } - if ((nextPost > -1) && (nextPost < next)) { - next = nextPost; - linkType = LinkType.POST; - } - /* cut off “freenet:” from before keys. */ - if (((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (next >= 8) && (line.substring(next - 8, next).equals("freenet:"))) { - next -= 8; - line = line.substring(0, next) + line.substring(next + 8); - } + /* cut off “freenet:” from before keys. */ + if (((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (next >= 8) && (line.substring(next - 8, next).equals("freenet:"))) { + next -= 8; + line = line.substring(0, next) + line.substring(next + 8); + } - /* if there is text before the next item, write it out. */ - if (lineComplete && !lastLineEmpty) { - parts.add(new PlainTextPart("\n")); - } - if (next > 0) { - parts.add(new PlainTextPart(line.substring(0, next))); - line = line.substring(next); - next = 0; - } - lineComplete = false; + /* if there is text before the next item, write it out. */ + if (lineComplete && !lastLineEmpty) { + parts.add(new PlainTextPart("\n")); + } + if (next > 0) { + parts.add(new PlainTextPart(line.substring(0, next))); + line = line.substring(next); + next = 0; + } + lineComplete = false; - if (linkType == LinkType.SONE) { - if (line.length() >= (7 + 43)) { - String soneId = line.substring(7, 50); - Sone sone = soneProvider.getSone(soneId, false); - if (sone == null) { - /* - * don’t use create=true above, we don’t want the - * empty shell. - */ - sone = new MemorySone(soneId, false); + if (linkType == LinkType.SONE) { + if (line.length() >= (7 + 43)) { + String soneId = line.substring(7, 50); + Sone sone = soneProvider.getSone(soneId, false); + if (sone == null) { + /* + * don’t use create=true above, we don’t want + * the empty shell. + */ - sone = new Sone(soneId); ++ sone = new MemorySone(soneId, false); + } + parts.add(new SonePart(sone)); + line = line.substring(50); + } else { + parts.add(new PlainTextPart(line)); + line = ""; } - parts.add(new SonePart(sone)); - line = line.substring(50); - } else { - parts.add(new PlainTextPart(line)); - line = ""; + continue; } - continue; - } - if (linkType == LinkType.POST) { - if (line.length() >= (7 + 36)) { - String postId = line.substring(7, 43); - Post post = postProvider.getPost(postId, false); - if ((post != null) && (post.getSone() != null)) { - parts.add(new PostPart(post)); + if (linkType == LinkType.POST) { + if (line.length() >= (7 + 36)) { + String postId = line.substring(7, 43); + Post post = postProvider.getPost(postId, false); + if ((post != null) && (post.getSone() != null)) { + parts.add(new PostPart(post)); + } else { + parts.add(new PlainTextPart(line.substring(0, 43))); + } + line = line.substring(43); } else { - parts.add(new PlainTextPart(line.substring(0, 43))); + parts.add(new PlainTextPart(line)); + line = ""; } - line = line.substring(43); - } else { - parts.add(new PlainTextPart(line)); - line = ""; + continue; } - continue; - } - Matcher matcher = whitespacePattern.matcher(line); - int nextSpace = matcher.find(0) ? matcher.start() : line.length(); - String link = line.substring(0, nextSpace); - String name = link; - logger.log(Level.FINER, String.format("Found link: %s", link)); - logger.log(Level.FINEST, String.format("CHK: %d, SSK: %d, USK: %d", nextChk, nextSsk, nextUsk)); + Matcher matcher = whitespacePattern.matcher(line); + int nextSpace = matcher.find(0) ? matcher.start() : line.length(); + String link = line.substring(0, nextSpace); + String name = link; + logger.log(Level.FINER, String.format("Found link: %s", link)); + logger.log(Level.FINEST, String.format("CHK: %d, SSK: %d, USK: %d", nextChk, nextSsk, nextUsk)); - if ((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) { - FreenetURI uri; - if (name.indexOf('?') > -1) { - name = name.substring(0, name.indexOf('?')); - } - if (name.endsWith("/")) { - name = name.substring(0, name.length() - 1); - } - try { - uri = new FreenetURI(name); - name = uri.lastMetaString(); - if (name == null) { - name = uri.getDocName(); + if ((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) { + FreenetURI uri; + if (name.indexOf('?') > -1) { + name = name.substring(0, name.indexOf('?')); } - if (name == null) { - name = link.substring(0, Math.min(9, link.length())); + if (name.endsWith("/")) { + name = name.substring(0, name.length() - 1); } - boolean fromPostingSone = ((linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (context != null) && (context.getPostingSone() != null) && link.substring(4, Math.min(link.length(), 47)).equals(context.getPostingSone().getId()); - parts.add(new FreenetLinkPart(link, name, fromPostingSone)); - } catch (MalformedURLException mue1) { - /* not a valid link, insert as plain text. */ - parts.add(new PlainTextPart(link)); - } catch (NullPointerException npe1) { - /* FreenetURI sometimes throws these, too. */ - parts.add(new PlainTextPart(link)); - } catch (ArrayIndexOutOfBoundsException aioobe1) { - /* oh, and these, too. */ - parts.add(new PlainTextPart(link)); - } - } else if ((linkType == LinkType.HTTP) || (linkType == LinkType.HTTPS)) { - name = link.substring(linkType == LinkType.HTTP ? 7 : 8); - int firstSlash = name.indexOf('/'); - int lastSlash = name.lastIndexOf('/'); - if ((lastSlash - firstSlash) > 3) { - name = name.substring(0, firstSlash + 1) + "…" + name.substring(lastSlash); - } - if (name.endsWith("/")) { - name = name.substring(0, name.length() - 1); - } - if (((name.indexOf('/') > -1) && (name.indexOf('.') < name.lastIndexOf('.', name.indexOf('/'))) || ((name.indexOf('/') == -1) && (name.indexOf('.') < name.lastIndexOf('.')))) && name.startsWith("www.")) { - name = name.substring(4); - } - if (name.indexOf('?') > -1) { - name = name.substring(0, name.indexOf('?')); + try { + uri = new FreenetURI(name); + name = uri.lastMetaString(); + if (name == null) { + name = uri.getDocName(); + } + if (name == null) { + name = link.substring(0, Math.min(9, link.length())); + } + boolean fromPostingSone = ((linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (context != null) && (context.getPostingSone() != null) && link.substring(4, Math.min(link.length(), 47)).equals(context.getPostingSone().getId()); + parts.add(new FreenetLinkPart(link, name, fromPostingSone)); + } catch (MalformedURLException mue1) { + /* not a valid link, insert as plain text. */ + parts.add(new PlainTextPart(link)); + } catch (NullPointerException npe1) { + /* FreenetURI sometimes throws these, too. */ + parts.add(new PlainTextPart(link)); + } catch (ArrayIndexOutOfBoundsException aioobe1) { + /* oh, and these, too. */ + parts.add(new PlainTextPart(link)); + } + } else if ((linkType == LinkType.HTTP) || (linkType == LinkType.HTTPS)) { + name = link.substring(linkType == LinkType.HTTP ? 7 : 8); + int firstSlash = name.indexOf('/'); + int lastSlash = name.lastIndexOf('/'); + if ((lastSlash - firstSlash) > 3) { + name = name.substring(0, firstSlash + 1) + "…" + name.substring(lastSlash); + } + if (name.endsWith("/")) { + name = name.substring(0, name.length() - 1); + } + if (((name.indexOf('/') > -1) && (name.indexOf('.') < name.lastIndexOf('.', name.indexOf('/'))) || ((name.indexOf('/') == -1) && (name.indexOf('.') < name.lastIndexOf('.')))) && name.startsWith("www.")) { + name = name.substring(4); + } + if (name.indexOf('?') > -1) { + name = name.substring(0, name.indexOf('?')); + } + parts.add(new LinkPart(link, name)); } - parts.add(new LinkPart(link, name)); + line = line.substring(nextSpace); } - line = line.substring(nextSpace); + lastLineEmpty = false; + } + } finally { + if (bufferedReader != source) { + Closer.close(bufferedReader); } - lastLineEmpty = false; } for (int partIndex = parts.size() - 1; partIndex >= 0; --partIndex) { Part part = parts.getPart(partIndex);