X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=searchable%2FSearchableTag.java;fp=searchable%2FSearchableTag.java;h=de8679834f5fa20a72826fd6deb89cf405613de9;hb=0fc81e6465aa9c1f1dfc19b532082220d609768a;hp=0000000000000000000000000000000000000000;hpb=505be508ae7d3fb48122be548b310a238cfb91eb;p=fanfix.git diff --git a/searchable/SearchableTag.java b/searchable/SearchableTag.java new file mode 100644 index 0000000..de86798 --- /dev/null +++ b/searchable/SearchableTag.java @@ -0,0 +1,324 @@ +package be.nikiroo.fanfix.searchable; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents a tag that can be searched on a supported website. + * + * @author niki + */ +public class SearchableTag { + private String id; + private String name; + private boolean complete; + private long count; + + private SearchableTag parent; + private List children; + + /** + * The number of stories result pages this tag can get. + *

+ * We keep more information than what the getter/setter returns/accepts. + *

+ */ + private int pages; + + /** + * Create a new {@link SearchableTag}. + *

+ * 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 + * the tag {@link SearchableTag#isComplete()} or not + */ + public SearchableTag(String id, String name, boolean leaf, boolean complete) { + this.id = id; + this.name = name; + this.complete = leaf || complete; + + setLeaf(leaf); + + children = new ArrayList(); + } + + /** + * 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; + } + + /** + * The fully qualified tag name, which can be displayed to the user. + *

+ * 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. + *

+ * Leaf tags are always considered complete. + * + * @return TRUE if it is complete + */ + public boolean isComplete() { + return complete; + } + + /** + * 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. + *

+ * Leaf tags are always considered complete. + * + * @param complete + * TRUE if it is complete + */ + public void setComplete(boolean complete) { + this.complete = isLeaf() || complete; + } + + /** + * The number of items that can be found with this tag if it is searched. + *

+ * Will report the number of subtags by default. + * + * @return the number of items + */ + public long getCount() { + long count = this.count; + if (count <= 0) { + count = children.size(); + } + + return count; + } + + /** + * The number of items that can be found with this tag if it is searched. + * + * @param count + * the new count + */ + public void setCount(long count) { + this.count = count; + } + + /** + * The number of stories result pages this tag contains, only make sense if + * {@link SearchableTag#isLeaf()} returns TRUE. + *

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

+ * Will reset the number of pages to -1. + * + * @param leaf + * TRUE if it is + */ + public void setLeaf(boolean leaf) { + pages = leaf ? -1 : -2; + if (leaf) { + complete = true; + } + } + + /** + * The subtag children of this {@link SearchableTag}. + *

+ * Never NULL. + *

+ * Note that if {@link SearchableTag#isComplete()} returns false, you can + * still fill (more?) subtag children with a {@link BasicSearchable}. + * + * @return the subtag children, never NULL + */ + public List getChildren() { + return children; + } + + /** + * Add the given {@link SearchableTag} as a subtag child. + * + * @param tag + * 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; + } + + /** + * Display a DEBUG {@link String} representation of this object. + */ + @Override + public String toString() { + String rep = name + " [" + id + "]"; + if (!complete) { + rep += "*"; + } + + if (getCount() > 0) { + rep += " (" + getCount() + ")"; + } + + if (!children.isEmpty()) { + String tags = ""; + int i = 1; + for (SearchableTag tag : children) { + if (!tags.isEmpty()) { + tags += ", "; + } + + if (i > 10) { + tags += "..."; + break; + } + + tags += tag; + i++; + } + + rep += ": " + tags; + } + + 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; + } +}