Merge branch 'subtree'
[fanfix.git] / src / be / nikiroo / fanfix / searchable / SearchableTag.java
CommitLineData
fd69647f
NR
1package be.nikiroo.fanfix.searchable;
2
3import java.util.ArrayList;
4import java.util.List;
5
6/**
7 * This class represents a tag that can be searched on a supported website.
8 *
9 * @author niki
10 */
11public class SearchableTag {
12 private String id;
13 private String name;
14 private boolean complete;
15 private long count;
e66c9078
NR
16
17 private SearchableTag parent;
fd69647f
NR
18 private List<SearchableTag> children;
19
76ec935e
NR
20 /**
21 * The number of stories result pages this tag can get.
22 * <p>
23 * We keep more information than what the getter/setter returns/accepts.
24 * <ul>
25 * <li>-2: this tag does not support stories results (not a leaf tag)</li>
26 * <li>-1: the number is not yet known, but will be known after a
27 * {@link BasicSearchable#fillTag(SearchableTag)} operation</li>
28 * <li>X: the number of pages</li>
29 * </ul>
30 */
31 private int pages;
32
fd69647f
NR
33 /**
34 * Create a new {@link SearchableTag}.
76ec935e
NR
35 * <p>
36 * Note that tags are complete by default.
fd69647f
NR
37 *
38 * @param id
39 * the ID (usually a way to find the linked stories later on)
40 * @param name
41 * the tag name, which can be displayed to the user
76ec935e
NR
42 * @param leaf
43 * the tag is a leaf tag, that is, it will not return subtags
44 * with {@link BasicSearchable#fillTag(SearchableTag)} but will
45 * return stories with
aaeabf3a 46 * {@link BasicSearchable#search(SearchableTag, int)}
76ec935e
NR
47 */
48 public SearchableTag(String id, String name, boolean leaf) {
49 this(id, name, leaf, true);
50 }
51
52 /**
53 * Create a new {@link SearchableTag}.
54 *
55 * @param id
56 * the ID (usually a way to find the linked stories later on)
57 * @param name
58 * the tag name, which can be displayed to the user
59 * @param leaf
60 * the tag is a leaf tag, that is, it will not return subtags
61 * with {@link BasicSearchable#fillTag(SearchableTag)} but will
62 * return stories with
aaeabf3a 63 * {@link BasicSearchable#search(SearchableTag, int)}
fd69647f 64 * @param complete
76ec935e 65 * the tag {@link SearchableTag#isComplete()} or not
fd69647f 66 */
76ec935e 67 public SearchableTag(String id, String name, boolean leaf, boolean complete) {
fd69647f
NR
68 this.id = id;
69 this.name = name;
bf2b37b0 70 this.complete = leaf || complete;
fd69647f 71
76ec935e
NR
72 setLeaf(leaf);
73
fd69647f
NR
74 children = new ArrayList<SearchableTag>();
75 }
76
76ec935e
NR
77 /**
78 * The ID (usually a way to find the linked stories later on).
79 *
80 * @return the ID
81 */
fd69647f
NR
82 public String getId() {
83 return id;
84 }
85
76ec935e
NR
86 /**
87 * The tag name, which can be displayed to the user.
88 *
89 * @return then name
90 */
fd69647f
NR
91 public String getName() {
92 return name;
93 }
94
aaeabf3a
NR
95 /**
96 * The fully qualified tag name, which can be displayed to the user.
97 * <p>
98 * It will display all the tags that lead to this one as well as this one.
99 *
100 * @return the fully qualified name
101 */
102 public String getFqName() {
103 if (parent != null) {
104 return parent.getFqName() + " / " + name;
105 }
bf2b37b0 106
a0337269 107 return "" + name;
aaeabf3a
NR
108 }
109
fd69647f 110 /**
76ec935e
NR
111 * Non-complete, non-leaf tags can still be completed via a
112 * {@link BasicSearchable#fillTag(SearchableTag)} operation from a
fd69647f 113 * {@link BasicSearchable}, in order to gain (more?) subtag children.
76ec935e 114 * <p>
bf2b37b0 115 * Leaf tags are always considered complete.
fd69647f 116 *
76ec935e 117 * @return TRUE if it is complete
fd69647f
NR
118 */
119 public boolean isComplete() {
120 return complete;
121 }
122
123 /**
76ec935e
NR
124 * Non-complete, non-leaf tags can still be completed via a
125 * {@link BasicSearchable#fillTag(SearchableTag)} operation from a
fd69647f 126 * {@link BasicSearchable}, in order to gain (more?) subtag children.
76ec935e 127 * <p>
bf2b37b0 128 * Leaf tags are always considered complete.
fd69647f
NR
129 *
130 * @param complete
76ec935e 131 * TRUE if it is complete
fd69647f
NR
132 */
133 public void setComplete(boolean complete) {
bf2b37b0 134 this.complete = isLeaf() || complete;
fd69647f
NR
135 }
136
137 /**
138 * The number of items that can be found with this tag if it is searched.
139 * <p>
140 * Will report the number of subtags by default.
141 *
142 * @return the number of items
143 */
144 public long getCount() {
145 long count = this.count;
146 if (count <= 0) {
147 count = children.size();
148 }
149
150 return count;
151 }
152
fd69647f
NR
153 /**
154 * The number of items that can be found with this tag if it is searched.
155 *
156 * @param count
157 * the new count
158 */
159 public void setCount(long count) {
160 this.count = count;
161 }
162
76ec935e
NR
163 /**
164 * The number of stories result pages this tag contains, only make sense if
165 * {@link SearchableTag#isLeaf()} returns TRUE.
166 * <p>
167 * Will return -1 if the number is not yet known.
168 *
169 * @return the number of pages, or -1
170 */
171 public int getPages() {
172 return Math.max(-1, pages);
173 }
174
175 /**
176 * The number of stories result pages this tag contains, only make sense if
177 * {@link SearchableTag#isLeaf()} returns TRUE.
178 *
179 * @param pages
180 * the (positive or 0) number of pages
181 */
182 public void setPages(int pages) {
183 this.pages = Math.max(-1, pages);
184 }
185
186 /**
187 * This tag is a leaf tag, that is, it will not return other subtags with
188 * {@link BasicSearchable#fillTag(SearchableTag)} but will return stories
aaeabf3a 189 * with {@link BasicSearchable#search(SearchableTag, int)}.
76ec935e
NR
190 *
191 * @return TRUE if it is
192 */
193 public boolean isLeaf() {
194 return pages > -2;
195 }
196
197 /**
198 * This tag is a leaf tag, that is, it will not return other subtags with
199 * {@link BasicSearchable#fillTag(SearchableTag)} but will return stories
aaeabf3a 200 * with {@link BasicSearchable#search(SearchableTag, int)}.
76ec935e
NR
201 * <p>
202 * Will reset the number of pages to -1.
203 *
204 * @param leaf
205 * TRUE if it is
206 */
207 public void setLeaf(boolean leaf) {
208 pages = leaf ? -1 : -2;
bf2b37b0
NR
209 if (leaf) {
210 complete = true;
211 }
76ec935e
NR
212 }
213
fd69647f
NR
214 /**
215 * The subtag children of this {@link SearchableTag}.
216 * <p>
217 * Never NULL.
218 * <p>
219 * Note that if {@link SearchableTag#isComplete()} returns false, you can
220 * still fill (more?) subtag children with a {@link BasicSearchable}.
221 *
222 * @return the subtag children, never NULL
223 */
224 public List<SearchableTag> getChildren() {
225 return children;
226 }
227
228 /**
229 * Add the given {@link SearchableTag} as a subtag child.
230 *
231 * @param tag
232 * the tag to add
233 */
234 public void add(SearchableTag tag) {
a0337269
NR
235 if (tag == null) {
236 throw new NullPointerException("tag");
237 }
238
239 for (SearchableTag p = this; p != null; p = p.parent) {
240 if (p.equals(tag)) {
241 throw new IllegalArgumentException(
242 "Tags do not allow recursion");
243 }
244 }
245 for (SearchableTag p = tag; p != null; p = p.parent) {
246 if (p.equals(this)) {
247 throw new IllegalArgumentException(
248 "Tags do not allow recursion");
249 }
250 }
251
fd69647f 252 children.add(tag);
e66c9078
NR
253 tag.parent = this;
254 }
255
256 /**
257 * This {@link SearchableTag} parent tag, or NULL if none.
258 *
259 * @return the parent or NULL
260 */
261 public SearchableTag getParent() {
262 return parent;
fd69647f
NR
263 }
264
265 /**
266 * Display a DEBUG {@link String} representation of this object.
267 */
268 @Override
269 public String toString() {
270 String rep = name + " [" + id + "]";
271 if (!complete) {
272 rep += "*";
273 }
274
275 if (getCount() > 0) {
596ed3d6 276 rep += " (" + getCount() + ")";
fd69647f
NR
277 }
278
279 if (!children.isEmpty()) {
280 String tags = "";
281 int i = 1;
282 for (SearchableTag tag : children) {
283 if (!tags.isEmpty()) {
284 tags += ", ";
285 }
286
287 if (i > 10) {
288 tags += "...";
289 break;
290 }
291
292 tags += tag;
293 i++;
294 }
295
296 rep += ": " + tags;
297 }
298
299 return rep;
300 }
a0337269
NR
301
302 @Override
303 public int hashCode() {
304 return getFqName().hashCode();
305 }
306
307 @Override
308 public boolean equals(Object otherObj) {
309 if (otherObj instanceof SearchableTag) {
310 SearchableTag other = (SearchableTag) otherObj;
311 if ((id == null && other.id == null)
312 || (id != null && id.equals(other.id))) {
313 if (getFqName().equals(other.getFqName())) {
314 if ((parent == null && other.parent == null)
315 || (parent != null && parent.equals(other.parent))) {
316 return true;
317 }
318 }
319 }
320 }
321
322 return false;
323 }
fd69647f 324}