/*
* 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));
}
}