001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.doxia.index; 020 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.List; 025import java.util.Objects; 026 027import org.apache.maven.doxia.markup.Markup; 028import org.apache.maven.doxia.sink.Sink; 029 030/** 031 * Representing the index tree within a document with the most important metadata per entry. 032 * Currently this only contains entries for sections, but in the future may be extended, therefore it 033 * is recommended to use {@link #getType()} to filter out irrelevant entries. 034 * 035 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a> 036 */ 037public class IndexEntry { 038 /** 039 * The parent entry. 040 */ 041 private final IndexEntry parent; 042 043 /** 044 * The id of the entry. 045 */ 046 private String id; 047 048 /** 049 * true if there is already an anchor for this 050 */ 051 private boolean hasAnchor; 052 053 /** 054 * The entry title. 055 */ 056 private String title; 057 058 /** 059 * The child entries. 060 */ 061 private List<IndexEntry> childEntries = new ArrayList<>(); 062 063 public enum Type { 064 /** 065 * Used for unknown types but also for the root entry 066 */ 067 UNKNOWN(), 068 SECTION_1(Sink.SECTION_LEVEL_1), 069 SECTION_2(Sink.SECTION_LEVEL_2), 070 SECTION_3(Sink.SECTION_LEVEL_3), 071 SECTION_4(Sink.SECTION_LEVEL_4), 072 SECTION_5(Sink.SECTION_LEVEL_5), 073 SECTION_6(Sink.SECTION_LEVEL_6), 074 DEFINED_TERM(), 075 FIGURE(), 076 TABLE(); 077 078 private final int sectionLevel; 079 080 Type() { 081 this(-1); 082 } 083 084 Type(int sectionLevel) { 085 this.sectionLevel = sectionLevel; 086 } 087 088 static Type fromSectionLevel(int level) { 089 if (level < Sink.SECTION_LEVEL_1 || level > Sink.SECTION_LEVEL_6) { 090 throw new IllegalArgumentException("Level must be between " + Sink.SECTION_LEVEL_1 + " and " 091 + Sink.SECTION_LEVEL_6 + " but is " + level); 092 } 093 return Arrays.stream(Type.values()) 094 .filter(t -> level == t.sectionLevel) 095 .findAny() 096 .orElseThrow(() -> new IllegalStateException("Could not find enum for sectionLevel " + level)); 097 } 098 }; 099 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}