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.commons.svl; 018 019import java.util.*; 020 021/** 022 * A typed, structured value (list, block or table) that an SVL expression function can return. 023 * 024 * <p> 025 * The core never emits markup. {@link #toString()} gives a plain-text fallback; a layer that understands richer 026 * output (such as the J0439 template layer) interprets {@link #kind()}, {@link #items()} / {@link #rows()} and 027 * {@link #options()} itself. 028 * 029 * <p> 030 * Instances are immutable; all inputs are defensively copied (shallow: the elements themselves are not copied). 031 */ 032public final class Structure { 033 034 /** The kind of structure. */ 035 public enum Kind { 036 /** Unordered list. */ UL, 037 /** Ordered list. */ OL, 038 /** A single block of content. */ BLOCK, 039 /** A table with column headers and rows. */ TABLE 040 } 041 042 private final Kind kind; 043 private final List<?> items; 044 private final List<String> columns; 045 private final List<List<?>> rows; 046 private final Map<String,Object> options; 047 048 private Structure(Kind kind, List<?> items, List<String> columns, List<List<?>> rows, Map<String,Object> options) { 049 this.kind = kind; 050 this.items = items; 051 this.columns = columns; 052 this.rows = rows; 053 this.options = options == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(options)); 054 } 055 056 /** 057 * Creates an unordered list. 058 * 059 * @param items The items. Must not be <jk>null</jk>. 060 * @return A new structure. 061 */ 062 public static Structure ul(List<?> items) { return ul(items, null); } 063 064 /** 065 * Creates an unordered list with options. 066 * 067 * @param items The items. Must not be <jk>null</jk>. 068 * @param options Layer-defined options. May be <jk>null</jk>. 069 * @return A new structure. 070 */ 071 public static Structure ul(List<?> items, Map<String,Object> options) { 072 return new Structure(Kind.UL, copy(items), null, null, options); 073 } 074 075 /** 076 * Creates an ordered list. 077 * 078 * @param items The items. Must not be <jk>null</jk>. 079 * @return A new structure. 080 */ 081 public static Structure ol(List<?> items) { return ol(items, null); } 082 083 /** 084 * Creates an ordered list with options. 085 * 086 * @param items The items. Must not be <jk>null</jk>. 087 * @param options Layer-defined options. May be <jk>null</jk>. 088 * @return A new structure. 089 */ 090 public static Structure ol(List<?> items, Map<String,Object> options) { 091 return new Structure(Kind.OL, copy(items), null, null, options); 092 } 093 094 /** 095 * Creates a block holding a single content item. 096 * 097 * @param content The content. May be <jk>null</jk>. 098 * @return A new structure. 099 */ 100 public static Structure block(Object content) { return block(content, null); } 101 102 /** 103 * Creates a block with options. 104 * 105 * @param content The content. May be <jk>null</jk>. 106 * @param options Layer-defined options. May be <jk>null</jk>. 107 * @return A new structure. 108 */ 109 public static Structure block(Object content, Map<String,Object> options) { 110 var l = new ArrayList<Object>(1); 111 l.add(content); 112 return new Structure(Kind.BLOCK, Collections.unmodifiableList(l), null, null, options); 113 } 114 115 /** 116 * Creates a table. 117 * 118 * @param columns The column headers. Must not be <jk>null</jk>. 119 * @param rows The rows. Must not be <jk>null</jk>. 120 * @return A new structure. 121 */ 122 public static Structure table(List<String> columns, List<List<?>> rows) { return table(columns, rows, null); } 123 124 /** 125 * Creates a table with options. 126 * 127 * @param columns The column headers. Must not be <jk>null</jk>. 128 * @param rows The rows. Must not be <jk>null</jk>. 129 * @param options Layer-defined options. May be <jk>null</jk>. 130 * @return A new structure. 131 */ 132 public static Structure table(List<String> columns, List<List<?>> rows, Map<String,Object> options) { 133 Objects.requireNonNull(rows, "rows"); 134 var r = new ArrayList<List<?>>(rows.size()); 135 for (var row : rows) 136 r.add(copy(Objects.requireNonNull(row, "rows must not contain a null row"))); 137 return new Structure(Kind.TABLE, null, Collections.unmodifiableList(new ArrayList<>(Objects.requireNonNull(columns, "columns"))), Collections.unmodifiableList(r), options); 138 } 139 140 private static List<?> copy(List<?> l) { 141 return Collections.unmodifiableList(new ArrayList<>(Objects.requireNonNull(l, "items"))); 142 } 143 144 /** 145 * Returns the kind of this structure. 146 * 147 * @return The kind. 148 */ 149 public Kind kind() { return kind; } 150 151 /** 152 * Returns the items of a list or block. 153 * 154 * @return An unmodifiable list, or an empty list for a table. 155 */ 156 @SuppressWarnings({ 157 "java:S1452" // Items are heterogeneous by design; the wildcard is the public contract 158 }) 159 public List<?> items() { return items == null ? List.of() : items; } 160 161 /** 162 * Returns the column headers of a table. 163 * 164 * @return An unmodifiable list, or an empty list for a non-table. 165 */ 166 public List<String> columns() { return columns == null ? List.of() : columns; } 167 168 /** 169 * Returns the rows of a table. 170 * 171 * @return An unmodifiable list, or an empty list for a non-table. 172 */ 173 public List<List<?>> rows() { return rows == null ? List.of() : rows; } 174 175 /** 176 * Returns the layer-defined options. 177 * 178 * @return An unmodifiable map; never <jk>null</jk>. 179 */ 180 public Map<String,Object> options() { return options; } 181 182 /** 183 * Plain-text fallback. 184 * 185 * <p> 186 * Lists render one item per line prefixed with <c>- </c> or <c>N. </c>; a block renders its content; a table renders 187 * the header and then each row as tab-separated values, one per line. 188 */ 189 @Override 190 public String toString() { 191 var sb = new StringBuilder(); 192 switch (kind) { 193 case UL -> { 194 for (var o : items) 195 line(sb, "- " + o); 196 } 197 case OL -> { 198 var n = 1; 199 for (var o : items) 200 line(sb, (n++) + ". " + o); 201 } 202 case BLOCK -> sb.append(items.get(0)); 203 case TABLE -> { 204 line(sb, String.join("\t", columns)); 205 for (var r : rows) { 206 var j = new StringJoiner("\t"); 207 for (var o : r) 208 j.add(String.valueOf(o)); 209 line(sb, j.toString()); 210 } 211 } 212 } 213 return sb.toString(); 214 } 215 216 private static void line(StringBuilder sb, String s) { 217 if (! sb.isEmpty()) 218 sb.append('\n'); 219 sb.append(s); 220 } 221}