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 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}