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 * Small collection helpers: {@code list} and {@code take}. 025 * 026 * <p> 027 * {@code list(a, b, ...)} builds a list from its arguments, which makes it possible to produce the rows for 028 * {@code table(header, rows)}, for example {@code rows.![list(a, b)]}. {@code take(list, n)} returns the first {@code n} 029 * elements, which together with {@code size} expresses "first N, then 'and M more'" without a slice syntax. 030 * 031 * <p> 032 * Both functions are side-effect free. Neither can grow a collection beyond its inputs: {@code list} is bounded by the 033 * number of arguments written in the template and {@code take} returns a prefix of its argument. 034 */ 035public final class CollectionFunctions { 036 037 /** 038 * Applies the session's collection limit to a collection a function is about to copy. 039 * 040 * <p> 041 * Within the limit the elements are returned as a list. Beyond it, strict mode throws and lenient mode keeps the 042 * first {@code maxCollectionSize} elements. 043 * 044 * @param session The session. 045 * @param fn The function name, for the message. 046 * @param c The collection. 047 * @return The elements, at most {@code maxCollectionSize} of them. 048 */ 049 static List<Object> bounded(VarResolverSession session, String fn, Collection<?> c) { 050 var max = session.maxCollectionSize(); 051 if (c.size() <= max) 052 return new ArrayList<>(c); 053 if (session.isStrict()) 054 throw new VarResolverException("Function '%s' collection of %s elements exceeds the limit of %s", fn, c.size(), max); 055 var out = new ArrayList<Object>(max); 056 for (var o : c) { 057 if (out.size() >= max) 058 break; 059 out.add(o); 060 } 061 return out; 062 } 063 064 private CollectionFunctions() {} 065 066 /** All function classes in this category. */ 067 @SuppressWarnings({ 068 "java:S2386", // ALL is an immutable compile-time registry; exposed as an array for the cross-package/varargs functions(...) API. 069 "unchecked" // Cast is safe: type verified by caller context. 070 }) 071 public static final Class<? extends VarFunction>[] ALL = new Class[] { 072 ListFn.class, Take.class 073 }; 074 075 /** {@code #{list(a, b, ...)}} — a list of the arguments; {@code list()} is empty. */ 076 public static class ListFn implements VarFunction { 077 @Override public String name() { return "list"; } 078 @Override public int minArity() { return 0; } 079 @Override public int maxArity() { return Integer.MAX_VALUE; } 080 @Override public String invoke(VarResolverSession session, List<Object> args) { return String.valueOf(invokeValue(session, args)); } 081 @Override public Object invokeValue(VarResolverSession session, List<Object> args) { return new ArrayList<>(args); } 082 } 083 084 /** 085 * {@code #{take(list, n)}} — the first {@code n} elements of {@code list} (all of them if it is shorter). 086 * 087 * <p> 088 * {@code n} must be a non-negative whole number; a {@code null} list yields an empty list. 089 */ 090 // A range or slice syntax would supersede this if the SVL grammar ever adds one (see take()). 091 public static class Take implements VarFunction { 092 @Override public String name() { return "take"; } 093 @Override public int minArity() { return 2; } 094 @Override public int maxArity() { return 2; } 095 @Override public String invoke(VarResolverSession session, List<Object> args) { return String.valueOf(invokeValue(session, args)); } 096 @Override public Object invokeValue(VarResolverSession session, List<Object> args) { 097 var src = args.get(0); 098 var n = count(args.get(1)); 099 if (src == null) 100 return new ArrayList<>(); 101 if (! (src instanceof Collection<?> c)) 102 throw new VarResolverException("Function 'take' argument 1 must be a list but got %s", src.getClass().getSimpleName()); 103 var out = new ArrayList<Object>(Math.min(n, c.size())); 104 for (var o : bounded(session, "take", c)) { 105 if (out.size() >= n) 106 break; 107 out.add(o); 108 } 109 return out; 110 } 111 112 private static int count(Object o) { 113 if (o instanceof Number x && x.doubleValue() == Math.rint(x.doubleValue()) && x.doubleValue() >= 0 && x.doubleValue() <= Integer.MAX_VALUE) 114 return x.intValue(); 115 if (o instanceof Number) 116 throw new VarResolverException("Function 'take' argument 2 must be a non-negative whole number but got %s", o); 117 throw new VarResolverException("Function 'take' argument 2 must be a number but got %s", o == null ? "null" : o.getClass().getSimpleName()); 118 } 119 } 120}