private String name;
private boolean complete;
private long count;
+
+ private SearchableTag parent;
private List<SearchableTag> children;
+ /**
+ * The number of stories result pages this tag can get.
+ * <p>
+ * We keep more information than what the getter/setter returns/accepts.
+ * <ul>
+ * <li>-2: this tag does not support stories results (not a leaf tag)</li>
+ * <li>-1: the number is not yet known, but will be known after a
+ * {@link BasicSearchable#fillTag(SearchableTag)} operation</li>
+ * <li>X: the number of pages</li>
+ * </ul>
+ */
+ private int pages;
+
/**
* Create a new {@link SearchableTag}.
+ * <p>
+ * Note that tags are complete by default.
*
* @param id
* the ID (usually a way to find the linked stories later on)
* @param name
* the tag name, which can be displayed to the user
+ * @param leaf
+ * the tag is a leaf tag, that is, it will not return subtags
+ * with {@link BasicSearchable#fillTag(SearchableTag)} but will
+ * return stories with
+ * {@link BasicSearchable#search(SearchableTag, int)}
+ */
+ public SearchableTag(String id, String name, boolean leaf) {
+ this(id, name, leaf, true);
+ }
+
+ /**
+ * Create a new {@link SearchableTag}.
+ *
+ * @param id
+ * the ID (usually a way to find the linked stories later on)
+ * @param name
+ * the tag name, which can be displayed to the user
+ * @param leaf
+ * the tag is a leaf tag, that is, it will not return subtags
+ * with {@link BasicSearchable#fillTag(SearchableTag)} but will
+ * return stories with
+ * {@link BasicSearchable#search(SearchableTag, int)}
* @param complete
- * TRUE for a {@link SearchableTag} that cannot be "filled" by
- * the {@link BasicSearchable} in order to get (more?) subtag
- * children
+ * the tag {@link SearchableTag#isComplete()} or not
*/
- public SearchableTag(String id, String name, boolean complete) {
+ public SearchableTag(String id, String name, boolean leaf, boolean complete) {
this.id = id;
this.name = name;
- this.complete = complete;
+ this.complete = leaf || complete;
+
+ setLeaf(leaf);
children = new ArrayList<SearchableTag>();
}
+ /**
+ * The ID (usually a way to find the linked stories later on).
+ *
+ * @return the ID
+ */
public String getId() {
return id;
}
+ /**
+ * The tag name, which can be displayed to the user.
+ *
+ * @return then name
+ */
public String getName() {
return name;
}
/**
- * This tag can still be completed via a "fill" tag operation from a
+ * The fully qualified tag name, which can be displayed to the user.
+ * <p>
+ * It will display all the tags that lead to this one as well as this one.
+ *
+ * @return the fully qualified name
+ */
+ public String getFqName() {
+ if (parent != null) {
+ return parent.getFqName() + " / " + name;
+ }
+
+ return "" + name;
+ }
+
+ /**
+ * Non-complete, non-leaf tags can still be completed via a
+ * {@link BasicSearchable#fillTag(SearchableTag)} operation from a
* {@link BasicSearchable}, in order to gain (more?) subtag children.
+ * <p>
+ * Leaf tags are always considered complete.
*
- * @return TRUE if it can
+ * @return TRUE if it is complete
*/
public boolean isComplete() {
return complete;
}
/**
- * This tag can still be completed via a "fill" tag operation from a
+ * Non-complete, non-leaf tags can still be completed via a
+ * {@link BasicSearchable#fillTag(SearchableTag)} operation from a
* {@link BasicSearchable}, in order to gain (more?) subtag children.
+ * <p>
+ * Leaf tags are always considered complete.
*
* @param complete
- * TRUE if it can
+ * TRUE if it is complete
*/
public void setComplete(boolean complete) {
- this.complete = complete;
+ this.complete = isLeaf() || complete;
}
/**
}
/**
- * The number of items that can be found with this tag if it is searched,
- * displayable format.
- * <p>
- * Will report the number of subtags by default.
+ * The number of items that can be found with this tag if it is searched.
*
- * @return the number of items
+ * @param count
+ * the new count
*/
- public String getCountDisplay() {
- long count = this.count;
- if (count <= 0) {
- count = children.size();
- }
+ public void setCount(long count) {
+ this.count = count;
+ }
- if (count > 999999) {
- return count / 1000000 + "M";
- }
+ /**
+ * The number of stories result pages this tag contains, only make sense if
+ * {@link SearchableTag#isLeaf()} returns TRUE.
+ * <p>
+ * Will return -1 if the number is not yet known.
+ *
+ * @return the number of pages, or -1
+ */
+ public int getPages() {
+ return Math.max(-1, pages);
+ }
- if (count > 2000) {
- return count / 1000 + "k";
- }
+ /**
+ * The number of stories result pages this tag contains, only make sense if
+ * {@link SearchableTag#isLeaf()} returns TRUE.
+ *
+ * @param pages
+ * the (positive or 0) number of pages
+ */
+ public void setPages(int pages) {
+ this.pages = Math.max(-1, pages);
+ }
- return Long.toString(count);
+ /**
+ * This tag is a leaf tag, that is, it will not return other subtags with
+ * {@link BasicSearchable#fillTag(SearchableTag)} but will return stories
+ * with {@link BasicSearchable#search(SearchableTag, int)}.
+ *
+ * @return TRUE if it is
+ */
+ public boolean isLeaf() {
+ return pages > -2;
}
/**
- * The number of items that can be found with this tag if it is searched.
+ * This tag is a leaf tag, that is, it will not return other subtags with
+ * {@link BasicSearchable#fillTag(SearchableTag)} but will return stories
+ * with {@link BasicSearchable#search(SearchableTag, int)}.
+ * <p>
+ * Will reset the number of pages to -1.
*
- * @param count
- * the new count
+ * @param leaf
+ * TRUE if it is
*/
- public void setCount(long count) {
- this.count = count;
+ public void setLeaf(boolean leaf) {
+ pages = leaf ? -1 : -2;
+ if (leaf) {
+ complete = true;
+ }
}
/**
* the tag to add
*/
public void add(SearchableTag tag) {
+ if (tag == null) {
+ throw new NullPointerException("tag");
+ }
+
+ for (SearchableTag p = this; p != null; p = p.parent) {
+ if (p.equals(tag)) {
+ throw new IllegalArgumentException(
+ "Tags do not allow recursion");
+ }
+ }
+ for (SearchableTag p = tag; p != null; p = p.parent) {
+ if (p.equals(this)) {
+ throw new IllegalArgumentException(
+ "Tags do not allow recursion");
+ }
+ }
+
children.add(tag);
+ tag.parent = this;
+ }
+
+ /**
+ * This {@link SearchableTag} parent tag, or NULL if none.
+ *
+ * @return the parent or NULL
+ */
+ public SearchableTag getParent() {
+ return parent;
}
/**
}
if (getCount() > 0) {
- rep += " (" + getCountDisplay() + ")";
+ rep += " (" + getCount() + ")";
}
if (!children.isEmpty()) {
return rep;
}
+
+ @Override
+ public int hashCode() {
+ return getFqName().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object otherObj) {
+ if (otherObj instanceof SearchableTag) {
+ SearchableTag other = (SearchableTag) otherObj;
+ if ((id == null && other.id == null)
+ || (id != null && id.equals(other.id))) {
+ if (getFqName().equals(other.getFqName())) {
+ if ((parent == null && other.parent == null)
+ || (parent != null && parent.equals(other.parent))) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
}