View Javadoc
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&oslash;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 }