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  
100     /**
101      * The type of the entry, one of the types defined by {@link IndexingSink}
102      */
103     private final Type type;
104 
105     /**
106      * Constructor for root entry.
107      *
108      * @param newId The id. May be null.
109      */
110     public IndexEntry(String newId) {
111         this(null, newId);
112     }
113 
114     /**
115      * Constructor.
116      *
117      * @param newParent The parent. May be null.
118      * @param newId     The id. May be null.
119      */
120     public IndexEntry(IndexEntry newParent, String newId) {
121         this(newParent, newId, Type.UNKNOWN);
122     }
123 
124     /**
125      * Constructor.
126      *
127      * @param newParent The parent. May be null.
128      * @param newId     The id. May be null.
129      * @param type      The type. Cannot be null.
130      */
131     public IndexEntry(IndexEntry newParent, String newId, Type type) {
132         this.parent = newParent;
133         this.id = newId;
134 
135         if (parent != null) {
136             parent.childEntries.add(this);
137         }
138         this.type = type;
139     }
140 
141     /**
142      * Returns the parent entry.
143      *
144      * @return the parent entry.
145      */
146     public IndexEntry getParent() {
147         return parent;
148     }
149 
150     /**
151      * Returns the id.
152      *
153      * @return the id.
154      */
155     public String getId() {
156         return id;
157     }
158 
159     /**
160      * Set the id.
161      *
162      * @param id the id
163      * @since 1.1.2
164      */
165     protected void setId(String id) {
166         this.id = id;
167     }
168 
169     /**
170      * Returns the type of this entry. Is one of the types defined by {@link IndexingSink}.
171      * @return the type of this entry
172      * @since 2.0.0
173      */
174     public Type getType() {
175         return type;
176     }
177 
178     /** Set if the entry's id already has an anchor in the underlying document.
179      *
180      * @param hasAnchor {@code true} if the id already has an anchor.
181      * @since 2.0.0
182      */
183     public void setAnchor(boolean hasAnchor) {
184         this.hasAnchor = hasAnchor;
185     }
186 
187     /**
188      * Returns if the entry's id already has an anchor in the underlying document.
189      * @return {@code true} if the id already has an anchor otherwise {@code false}.
190      *
191      * @since 2.0.0
192      */
193     public boolean hasAnchor() {
194         return hasAnchor;
195     }
196 
197     /**
198      * Returns the title.
199      *
200      * @return the title.
201      */
202     public String getTitle() {
203         return title;
204     }
205 
206     /**
207      * Sets the title.
208      *
209      * @param newTitle the title.
210      */
211     public void setTitle(String newTitle) {
212         this.title = newTitle;
213     }
214 
215     /**
216      * Returns an unmodifiableList of the child entries.
217      *
218      * @return child entries.
219      */
220     public List<IndexEntry> getChildEntries() {
221         return Collections.unmodifiableList(childEntries);
222     }
223 
224     /**
225      * Sets the child entries or creates a new ArrayList if entries == null.
226      *
227      * @param entries the entries.
228      */
229     public void setChildEntries(List<IndexEntry> entries) {
230         if (entries == null) {
231             childEntries = new ArrayList<>();
232         }
233 
234         this.childEntries = entries;
235     }
236 
237     // -----------------------------------------------------------------------
238     // Utils
239     // -----------------------------------------------------------------------
240 
241     /**
242      * Returns the next entry.
243      *
244      * @return the next entry, or null if there is none.
245      */
246     public IndexEntry getNextEntry() {
247         if (parent == null) {
248             return null;
249         }
250 
251         List<IndexEntry> entries = parent.getChildEntries();
252 
253         int index = entries.indexOf(this);
254 
255         if (index + 1 >= entries.size()) {
256             return null;
257         }
258 
259         return entries.get(index + 1);
260     }
261 
262     /**
263      * Returns the previous entry.
264      *
265      * @return the previous entry, or null if there is none.
266      */
267     public IndexEntry getPrevEntry() {
268         if (parent == null) {
269             return null;
270         }
271 
272         List<IndexEntry> entries = parent.getChildEntries();
273 
274         int index = entries.indexOf(this);
275 
276         if (index == 0) {
277             return null;
278         }
279 
280         return entries.get(index - 1);
281     }
282 
283     /**
284      * Returns the first entry.
285      *
286      * @return the first entry, or null if there is none.
287      */
288     public IndexEntry getFirstEntry() {
289         List<IndexEntry> entries = getChildEntries();
290 
291         if (entries.size() == 0) {
292             return null;
293         }
294 
295         return entries.get(0);
296     }
297 
298     /**
299      * Returns the last entry.
300      *
301      * @return the last entry, or null if there is none.
302      */
303     public IndexEntry getLastEntry() {
304         List<IndexEntry> entries = getChildEntries();
305 
306         if (entries.size() == 0) {
307             return null;
308         }
309 
310         return entries.get(entries.size() - 1);
311     }
312 
313     /**
314      * Returns the root entry.
315      *
316      * @return the root entry, or null if there is none.
317      */
318     public IndexEntry getRootEntry() {
319         List<IndexEntry> entries = getChildEntries();
320 
321         if (entries.size() == 0) {
322             return null;
323         } else if (entries.size() > 1) {
324             throw new IllegalStateException("This index has more than one root entry");
325         } else {
326             return entries.get(0);
327         }
328     }
329 
330     // -----------------------------------------------------------------------
331     // Object Overrides
332     // -----------------------------------------------------------------------
333 
334     /**
335      * Returns a string representation of the object.
336      *
337      * @return Returns a string representation of all objects
338      */
339     public String toString() {
340         return toString(0);
341     }
342 
343     /**
344      * Returns a string representation of all objects to the given depth.
345      *
346      * @param depth The depth to descent to.
347      * @return A string.
348      */
349     public String toString(int depth) {
350         StringBuilder message = new StringBuilder();
351 
352         message.append("Id: ").append(id);
353 
354         if (title != null && !title.isEmpty()) {
355             message.append(", title: ").append(title);
356         }
357 
358         message.append(Markup.EOL);
359 
360         StringBuilder indent = new StringBuilder();
361 
362         for (int i = 0; i < depth; i++) {
363             indent.append(" ");
364         }
365 
366         for (IndexEntry entry : getChildEntries()) {
367             message.append(indent).append(entry.toString(depth + 1));
368         }
369 
370         return message.toString();
371     }
372 
373     @Override
374     public int hashCode() {
375         return Objects.hash(childEntries, hasAnchor, id, parent, title, type);
376     }
377 
378     @Override
379     public boolean equals(Object obj) {
380         if (this == obj) {
381             return true;
382         }
383         if (obj == null) {
384             return false;
385         }
386         if (getClass() != obj.getClass()) {
387             return false;
388         }
389         IndexEntry other = (IndexEntry) obj;
390         return Objects.equals(childEntries, other.childEntries)
391                 && hasAnchor == other.hasAnchor
392                 && Objects.equals(id, other.id)
393                 && Objects.equals(parent, other.parent)
394                 && Objects.equals(title, other.title)
395                 && type == other.type;
396     }
397 }