1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.maven.doxia.index;
20
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Objects;
26
27 import org.apache.maven.doxia.markup.Markup;
28 import org.apache.maven.doxia.sink.Sink;
29
30 /**
31 * Representing the index tree within a document with the most important metadata per entry.
32 * Currently this only contains entries for sections, but in the future may be extended, therefore it
33 * is recommended to use {@link #getType()} to filter out irrelevant entries.
34 *
35 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a>
36 */
37 public class IndexEntry {
38 /**
39 * The parent entry.
40 */
41 private final IndexEntry parent;
42
43 /**
44 * The id of the entry.
45 */
46 private String id;
47
48 /**
49 * true if there is already an anchor for this
50 */
51 private boolean hasAnchor;
52
53 /**
54 * The entry title.
55 */
56 private String title;
57
58 /**
59 * The child entries.
60 */
61 private List<IndexEntry> childEntries = new ArrayList<>();
62
63 public enum Type {
64 /**
65 * Used for unknown types but also for the root entry
66 */
67 UNKNOWN(),
68 SECTION_1(Sink.SECTION_LEVEL_1),
69 SECTION_2(Sink.SECTION_LEVEL_2),
70 SECTION_3(Sink.SECTION_LEVEL_3),
71 SECTION_4(Sink.SECTION_LEVEL_4),
72 SECTION_5(Sink.SECTION_LEVEL_5),
73 SECTION_6(Sink.SECTION_LEVEL_6),
74 DEFINED_TERM(),
75 FIGURE(),
76 TABLE();
77
78 private final int sectionLevel;
79
80 Type() {
81 this(-1);
82 }
83
84 Type(int sectionLevel) {
85 this.sectionLevel = sectionLevel;
86 }
87
88 static Type fromSectionLevel(int level) {
89 if (level < Sink.SECTION_LEVEL_1 || level > Sink.SECTION_LEVEL_6) {
90 throw new IllegalArgumentException("Level must be between " + Sink.SECTION_LEVEL_1 + " and "
91 + Sink.SECTION_LEVEL_6 + " but is " + level);
92 }
93 return Arrays.stream(Type.values())
94 .filter(t -> level == t.sectionLevel)
95 .findAny()
96 .orElseThrow(() -> new IllegalStateException("Could not find enum for sectionLevel " + level));
97 }
98
99 public boolean isSection() {
100 return sectionLevel >= 1;
101 }
102 }
103
104 /**
105 * The type of the entry, one of the types defined by {@link IndexingSink}
106 */
107 private final Type type;
108
109 /**
110 * Constructor for root entry.
111 *
112 * @param newId The id. May be null.
113 */
114 public IndexEntry(String newId) {
115 this(null, newId);
116 }
117
118 /**
119 * Constructor.
120 *
121 * @param newParent The parent. May be null.
122 * @param newId The id. May be null.
123 */
124 public IndexEntry(IndexEntry newParent, String newId) {
125 this(newParent, newId, Type.UNKNOWN);
126 }
127
128 /**
129 * Constructor.
130 *
131 * @param newParent The parent. May be null.
132 * @param newId The id. May be null.
133 * @param type The type. Cannot be null.
134 */
135 public IndexEntry(IndexEntry newParent, String newId, Type type) {
136 this.parent = newParent;
137 this.id = newId;
138
139 if (parent != null) {
140 parent.childEntries.add(this);
141 }
142 this.type = type;
143 }
144
145 /**
146 * Returns the parent entry.
147 *
148 * @return the parent entry.
149 */
150 public IndexEntry getParent() {
151 return parent;
152 }
153
154 /**
155 * Returns the id.
156 *
157 * @return the id.
158 */
159 public String getId() {
160 return id;
161 }
162
163 /**
164 * Returns if the entry has an id.
165 * @return {@code true} if the entry has a valid id, otherwise it can be considered invalid/empty.
166 */
167 public boolean hasId() {
168 return id != null;
169 }
170
171 /**
172 * Set the id.
173 *
174 * @param id the id
175 * @since 1.1.2
176 */
177 protected void setId(String id) {
178 this.id = id;
179 }
180
181 /**
182 * Returns the type of this entry. Is one of the types defined by {@link IndexingSink}.
183 * @return the type of this entry
184 * @since 2.0.0
185 */
186 public Type getType() {
187 return type;
188 }
189
190 /** Set if the entry's id already has an anchor in the underlying document.
191 *
192 * @param hasAnchor {@code true} if the id already has an anchor.
193 * @since 2.0.0
194 */
195 public void setAnchor(boolean hasAnchor) {
196 this.hasAnchor = hasAnchor;
197 }
198
199 /**
200 * Returns if the entry's id already has an anchor in the underlying document.
201 * @return {@code true} if the id already has an anchor otherwise {@code false}.
202 *
203 * @since 2.0.0
204 */
205 public boolean hasAnchor() {
206 return hasAnchor;
207 }
208
209 /**
210 * Returns the title.
211 *
212 * @return the title (may be {@code null}).
213 */
214 public String getTitle() {
215 return title;
216 }
217
218 /**
219 * Sets the title.
220 *
221 * @param newTitle the title.
222 */
223 public void setTitle(String newTitle) {
224 this.title = newTitle;
225 }
226
227 /**
228 * Returns an unmodifiableList of the child entries.
229 *
230 * @return child entries.
231 */
232 public List<IndexEntry> getChildEntries() {
233 return Collections.unmodifiableList(childEntries);
234 }
235
236 /**
237 * Sets the child entries or creates a new ArrayList if entries == null.
238 *
239 * @param entries the entries.
240 */
241 public void setChildEntries(List<IndexEntry> entries) {
242 if (entries == null) {
243 childEntries = new ArrayList<>();
244 }
245
246 this.childEntries = entries;
247 }
248
249 // -----------------------------------------------------------------------
250 // Utils
251 // -----------------------------------------------------------------------
252
253 /**
254 * Returns the next entry.
255 *
256 * @return the next entry, or null if there is none.
257 */
258 public IndexEntry getNextEntry() {
259 if (parent == null) {
260 return null;
261 }
262
263 List<IndexEntry> entries = parent.getChildEntries();
264
265 int index = entries.indexOf(this);
266
267 if (index + 1 >= entries.size()) {
268 return null;
269 }
270
271 return entries.get(index + 1);
272 }
273
274 /**
275 * Returns the previous entry.
276 *
277 * @return the previous entry, or null if there is none.
278 */
279 public IndexEntry getPrevEntry() {
280 if (parent == null) {
281 return null;
282 }
283
284 List<IndexEntry> entries = parent.getChildEntries();
285
286 int index = entries.indexOf(this);
287
288 if (index == 0) {
289 return null;
290 }
291
292 return entries.get(index - 1);
293 }
294
295 /**
296 * Returns the first entry.
297 *
298 * @return the first entry, or null if there is none.
299 */
300 public IndexEntry getFirstEntry() {
301 List<IndexEntry> entries = getChildEntries();
302
303 if (entries.size() == 0) {
304 return null;
305 }
306
307 return entries.get(0);
308 }
309
310 /**
311 * Returns the last entry.
312 *
313 * @return the last entry, or null if there is none.
314 */
315 public IndexEntry getLastEntry() {
316 List<IndexEntry> entries = getChildEntries();
317
318 if (entries.size() == 0) {
319 return null;
320 }
321
322 return entries.get(entries.size() - 1);
323 }
324
325 /**
326 * Returns the root entry.
327 *
328 * @return the root entry, or null if there is none.
329 */
330 public IndexEntry getRootEntry() {
331 List<IndexEntry> entries = getChildEntries();
332
333 if (entries.size() == 0) {
334 return null;
335 } else if (entries.size() > 1) {
336 throw new IllegalStateException("This index has more than one root entry");
337 } else {
338 return entries.get(0);
339 }
340 }
341
342 // -----------------------------------------------------------------------
343 // Object Overrides
344 // -----------------------------------------------------------------------
345
346 /**
347 * Returns a string representation of the object.
348 *
349 * @return Returns a string representation of all objects
350 */
351 public String toString() {
352 return toString(0);
353 }
354
355 /**
356 * Returns a string representation of all objects to the given depth.
357 *
358 * @param depth The depth to descent to.
359 * @return A string.
360 */
361 public String toString(int depth) {
362 StringBuilder message = new StringBuilder();
363
364 message.append("Id: ").append(id);
365
366 if (title != null && !title.isEmpty()) {
367 message.append(", title: ").append(title);
368 }
369
370 message.append(Markup.EOL);
371
372 StringBuilder indent = new StringBuilder();
373
374 for (int i = 0; i < depth; i++) {
375 indent.append(" ");
376 }
377
378 for (IndexEntry entry : getChildEntries()) {
379 message.append(indent).append(entry.toString(depth + 1));
380 }
381
382 return message.toString();
383 }
384
385 @Override
386 public int hashCode() {
387 return Objects.hash(childEntries, hasAnchor, id, parent, title, type);
388 }
389
390 @Override
391 public boolean equals(Object obj) {
392 if (this == obj) {
393 return true;
394 }
395 if (obj == null) {
396 return false;
397 }
398 if (getClass() != obj.getClass()) {
399 return false;
400 }
401 IndexEntry other = (IndexEntry) obj;
402 return Objects.equals(childEntries, other.childEntries)
403 && hasAnchor == other.hasAnchor
404 && Objects.equals(id, other.id)
405 && Objects.equals(parent, other.parent)
406 && Objects.equals(title, other.title)
407 && type == other.type;
408 }
409 }