001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.console.freemarker; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022 023import freemarker.template.*; 024 025/** 026 * One node in the console navigation tree. Nodes nest to any depth; {@code <@node>} directives append 027 * to the node returned by {@link PageCapture#navRoot()}. 028 * 029 * <h5 class='section'>Example:</h5> 030 * <p class='bjava'> 031 * NavNode <jv>root</jv> = PageCapture.<jsm>of</jsm>(<jv>env</jv>).navRoot(); 032 * NavNode <jv>home</jv> = <jv>root</jv>.add(<js>"home"</js>, <js>"Home"</js>, <js>"/home"</js>); 033 * <jv>home</jv>.add(<js>"about"</js>, <js>"About"</js>, <js>"/home/about"</js>); 034 * <jv>root</jv>.find(List.<jsm>of</jsm>(<js>"home"</js>, <js>"about"</js>)).map(NavNode::label); <jc>// Optional[About]</jc> 035 * </p> 036 * 037 * @since 10.0.0 038 */ 039public final class NavNode { 040 041 private final NavNode parent; 042 private final String id; 043 private final String label; 044 private final String href; 045 private final List<NavNode> children = new ArrayList<>(); 046 047 private NavNode(NavNode parent, String id, String label, String href) { 048 this.parent = parent; 049 this.id = id; 050 this.label = label; 051 this.href = href; 052 } 053 054 static NavNode root() { 055 return new NavNode(null, null, null, null); 056 } 057 058 /** 059 * Appends a child node. 060 * 061 * @param nodeId The node id, unique among its siblings. 062 * @param label The visible label. 063 * @param hrefOrNull The link target, or <jk>null</jk> for a parent-only node. 064 * @return The new child. 065 * @throws TemplateModelException If a sibling already has this id (E-3). 066 */ 067 public NavNode add(String nodeId, String label, String hrefOrNull) throws TemplateModelException { 068 for (var c : children) 069 if (c.id.equals(nodeId)) 070 throw FtlAttrLists.reject(f("<@node id='%s'> duplicates a sibling id under '%s'.", nodeId, 071 n(this.id) ? "(root)" : String.join("/", idPath()))); 072 var n = new NavNode(this, nodeId, label, hrefOrNull); 073 children.add(n); 074 return n; 075 } 076 077 /** 078 * Walks a root-first id path from this node. 079 * 080 * @param idPath The ids, root first. 081 * @return The node at the end of the path, or empty. 082 */ 083 public Optional<NavNode> find(List<String> idPath) { 084 var n = this; 085 for (var step : idPath) { 086 NavNode next = null; 087 for (var c : n.children) 088 if (c.id.equals(step)) 089 next = c; 090 if (n(next)) 091 return Optional.empty(); 092 n = next; 093 } 094 return Optional.of(n); 095 } 096 097 /** @return The id, or <jk>null</jk> for the synthetic root. */ 098 public String id() { return id; } 099 100 /** @return The label. */ 101 public String label() { return label; } 102 103 /** @return The href, or <jk>null</jk>. */ 104 public String href() { return href; } 105 106 /** @return The children, unmodifiable. */ 107 public List<NavNode> children() { return u(children); } 108 109 List<String> idPath() { 110 var out = new LinkedList<String>(); 111 for (var n = this; nn(n) && nn(n.id); n = n.parent) 112 out.addFirst(n.id); 113 return out; 114 } 115 116 List<String> paths() { 117 var out = new ArrayList<String>(); 118 for (var c : children) { 119 out.add(String.join("/", c.idPath())); 120 out.addAll(c.paths()); 121 } 122 return out; 123 } 124 125 List<Map<String,Object>> toList() { 126 var out = new ArrayList<Map<String,Object>>(); 127 for (var c : children) { 128 var m = new LinkedHashMap<String,Object>(); 129 m.put("id", c.id); 130 m.put("label", c.label); 131 if (nn(c.href)) 132 m.put("href", c.href); 133 if (! c.children.isEmpty()) 134 m.put("children", c.toList()); 135 out.add(m); 136 } 137 return out; 138 } 139}