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.functions; 018 019import java.util.*; 020 021import org.apache.juneau.commons.svl.*; 022 023/** 024 * Structure-producing and collection functions: {@code ul}, {@code ol}, {@code block}, {@code table}. 025 * 026 * <p> 027 * Each returns a {@link Structure} from {@link VarFunction#invokeValue}; the string form is {@link Structure#toString()}. 028 * {@code ul}/{@code ol} treat a single {@link Collection} argument as the items list, otherwise the arguments are the items. 029 * Consequently {@code ul(list1)} cannot express a single item that is itself a list; use {@code ul(x, y)} or wrap the 030 * list in another list. 031 * 032 * <p> 033 * A collection longer than the session's {@code maxCollectionSize} (items of {@code ul}/{@code ol}, header, rows and each 034 * row of {@code table}) is cut to its first {@code maxCollectionSize} elements in lenient mode and throws in strict mode. 035 * 036 * <p> 037 * {@code table(header, rows)} requires every row to have exactly as many cells as the header has columns, and no 038 * null header element; otherwise a {@link VarResolverException} is thrown. 039 */ 040@SuppressWarnings({ 041 "java:S1192" // Duplicated function-name literals read more clearly inline than as constants 042}) 043public final class StructureFunctions { 044 045 private StructureFunctions() {} 046 047 /** All function classes in this category. */ 048 @SuppressWarnings({ 049 "java:S2386", // ALL is an immutable compile-time registry; exposed as an array for the cross-package/varargs functions(...) API. 050 "unchecked" // Cast is safe: type verified by caller context. 051 }) 052 public static final Class<? extends VarFunction>[] ALL = new Class[] { 053 Ul.class, Ol.class, Block.class, Table.class 054 }; 055 056 /** Base class: string form is the structure's text. */ 057 abstract static class Base implements VarFunction { 058 private final String name; 059 private final int min; 060 private final int max; 061 062 Base(String name, int min, int max) { 063 this.name = name; 064 this.min = min; 065 this.max = max; 066 } 067 068 @Override public String name() { return name; } 069 @Override public int minArity() { return min; } 070 @Override public int maxArity() { return max; } 071 072 @Override 073 public String invoke(VarResolverSession session, List<Object> args) { 074 return String.valueOf(invokeValue(session, args)); 075 } 076 077 @Override 078 public abstract Object invokeValue(VarResolverSession session, List<Object> args); 079 } 080 081 private static List<?> itemsOf(VarResolverSession session, String fn, List<Object> args) { 082 if (args.size() == 1 && args.get(0) instanceof Collection<?> c) 083 return CollectionFunctions.bounded(session, fn, c); 084 return CollectionFunctions.bounded(session, fn, args); 085 } 086 087 /** {@code #{ul(items...)}} or {@code #{ul(list)}}. */ 088 public static class Ul extends Base { 089 /** Constructor. */ 090 public Ul() { super("ul", 1, Integer.MAX_VALUE); } 091 @Override public Object invokeValue(VarResolverSession session, List<Object> args) { return Structure.ul(itemsOf(session, "ul", args)); } 092 } 093 094 /** {@code #{ol(items...)}} or {@code #{ol(list)}}. */ 095 public static class Ol extends Base { 096 /** Constructor. */ 097 public Ol() { super("ol", 1, Integer.MAX_VALUE); } 098 @Override public Object invokeValue(VarResolverSession session, List<Object> args) { return Structure.ol(itemsOf(session, "ol", args)); } 099 } 100 101 /** {@code #{block(x)}}. */ 102 public static class Block extends Base { 103 /** Constructor. */ 104 public Block() { super("block", 1, 1); } 105 @Override public Object invokeValue(VarResolverSession session, List<Object> args) { return Structure.block(args.get(0)); } 106 } 107 108 /** {@code #{table(headerList, rowsList)}}; each row is a list (or collection). */ 109 public static class Table extends Base { 110 /** Constructor. */ 111 public Table() { super("table", 2, 2); } 112 private static List<?> listArg(VarResolverSession session, String fn, int idx, Object o) { 113 if (o instanceof Collection<?> c) 114 return CollectionFunctions.bounded(session, fn, c); 115 throw new VarResolverException("Function '%s' argument %s must be a list but got %s", fn, idx + 1, o == null ? "null" : o.getClass().getSimpleName()); 116 } 117 @Override public Object invokeValue(VarResolverSession session, List<Object> args) { 118 var cols = new ArrayList<String>(); 119 for (var o : listArg(session, "table", 0, args.get(0))) { 120 if (o == null) 121 throw new VarResolverException("Function 'table' header must not contain null"); 122 cols.add(String.valueOf(o)); 123 } 124 var rows = new ArrayList<List<?>>(); 125 int n = 0; 126 for (var r : listArg(session, "table", 1, args.get(1))) { 127 var row = listArg(session, "table", 1, r); 128 if (row.size() != cols.size()) 129 throw new VarResolverException("Function 'table' row %s has %s cells but the header has %s columns", n, row.size(), cols.size()); 130 rows.add(row); 131 n++; 132 } 133 return Structure.table(cols, rows); 134 } 135 } 136}