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}