/* * Released into the public domain * with no warranty of any kind, either expressed or implied. */ package org.klomp.snark.comments; import java.util.concurrent.atomic.AtomicInteger; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; /** * Store comments * * @since 0.9.31 */ public class Comment implements Comparable<Comment> { private final String text, name; // seconds since 1/1/2005 private final int time; private final byte rating; private final boolean byMe; private boolean hidden; private static final AtomicInteger _id = new AtomicInteger(); private final int id = _id.incrementAndGet(); public static final int MAX_NAME_LEN = 32; // same as IRC, more or less private static final int MAX_TEXT_LEN = 512; private static final int BUCKET_SIZE = 10*60*1000; private static final long TIME_SHRINK = 1000L; // 1/1/2005 private static final long TIME_OFFSET = 1104537600000L; /** * My comment, now * * @param text may be null, will be truncated to max length, newlines replaced with spaces * @param name may be null, will be truncated to max length, newlines and commas removed * @param rating 0-5 */ public Comment(String text, String name, int rating) { this(text, name, rating, I2PAppContext.getGlobalContext().clock().now(), true); } /** * @param text may be null, will be truncated to max length, newlines replaced with spaces * @param name may be null, will be truncated to max length, newlines and commas removed * @param time java time (ms) * @param rating 0-5 */ public Comment(String text, String name, int rating, long time, boolean isMine) { if (text != null) { text = text.trim(); text = text.replaceAll("[\r\n]", " "); if (text.length() == 0) text = null; else if (text.length() > MAX_TEXT_LEN) text = text.substring(0, MAX_TEXT_LEN); } this.text = text; if (name != null) { name = name.trim(); // comma because it's not last in the persistent string name = name.replaceAll("[,\r\n]", ""); if (name.length() == 0) name = null; else if (name.length() > MAX_NAME_LEN) name = name.substring(0, MAX_NAME_LEN); } this.name = name; if (rating < 0 || rating > 5) rating = 0; else if (rating > 5) rating = 5; this.rating = (byte) rating; if (time < TIME_OFFSET) { time = TIME_OFFSET; } else { long now = I2PAppContext.getGlobalContext().clock().now(); if (time > now) time = now; } this.time = (int) ((time - TIME_OFFSET) / TIME_SHRINK); this.byMe = isMine; } public String getText() { return text; } public String getName() { return name; } public int getRating() { return rating; } /** java time (ms) */ public long getTime() { return (time * TIME_SHRINK) + TIME_OFFSET; } public boolean isMine() { return byMe; } public boolean isHidden() { return hidden; } void setHidden() { hidden = true; } /** * A unique ID that may be used to delete this comment from * the CommentSet via remove(int). NOT persisted across restarts. */ public int getID() { return id; } /** * reverse */ public int compareTo(Comment c) { if (time > c.time) return -1; if (time < c.time) return 1; // arbitrary sort below here if (rating != c.rating) return c.rating - rating; if (name != null || c.name != null) { if (name == null) return 1; if (c.name == null) return -1; int rv = name.compareTo(c.name); if (rv != 0) return rv; } if (text != null || c.text != null) { if (text == null) return 1; if (c.text == null) return -1; int rv = text.compareTo(c.text); if (rv != 0) return rv; } return 0; } /** * @return time,rating,mine,hidden,name,text */ public String toPersistentString() { StringBuilder buf = new StringBuilder(); buf.append(getTime()); buf.append(','); buf.append(Byte.toString(rating)); buf.append(','); buf.append(byMe ? "1" : "0"); buf.append(','); buf.append(hidden ? "1" : "0"); buf.append(','); if (name != null) buf.append(name); buf.append(','); if (text != null) buf.append(text); return buf.toString(); } /** * @return null if can't be parsed */ public static Comment fromPersistentString(String s) { String[] ss = DataHelper.split(s, ",", 6); if (ss.length != 6) return null; try { long t = Long.parseLong(ss[0]); int r = Integer.parseInt(ss[1]); boolean m = !ss[2].equals("0"); boolean h = !ss[3].equals("0"); Comment rv = new Comment(ss[5], ss[4], r, t, m); if (h) rv.setHidden(); return rv; } catch (NumberFormatException nfe) { return null; } } @Override public int hashCode() { return time / (BUCKET_SIZE / (int) TIME_SHRINK); } /** * Comments in the same 10-minute bucket and otherwise equal * are considered equal. This will result in duplicates * near the border. */ @Override public boolean equals(Object o) { if (o == null) return false; if (!(o instanceof Comment)) return false; Comment c = (Comment) o; return rating == c.rating && eq(text, c.text) && eq(name, c.name) && hashCode() == c.hashCode(); } /** * Ignores timestamp * @param c non-null */ public boolean equalsIgnoreTimestamp(Comment c) { return rating == c.rating && eq(text, c.text) && eq(name, c.name); } private static boolean eq(String lhs, String rhs) { return (lhs == null && rhs == null) || (lhs != null && lhs.equals(rhs)); } }