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 static org.apache.juneau.commons.utils.Shorts.*;
020
021import org.apache.juneau.commons.svl.*;
022
023/**
024 * Boolean-logic and comparison functions for the {@code #{...}} script catalog.
025 *
026 * <p>
027 * Boolean coercion is explicit (no implicit truthiness). Comparison functions ({@code lt},
028 * {@code gt}, etc.) compare numerically; {@code eq} / {@code neq} compare strings.
029 */
030@SuppressWarnings({
031   "java:S1192" // Duplicate string literals are SVL operator names used in annotation/config keys; intentional
032})
033public final class BooleanFunctions {
034
035   private BooleanFunctions() {}
036
037   /** All function classes in this category. */
038   @SuppressWarnings({
039      "java:S2386", // ALL is an immutable compile-time registry; exposed as an array for the cross-package/varargs functions(...) API, so visibility cannot be reduced.
040      "unchecked" // Cast is safe: type verified by caller context.
041   })
042   public static final Class<? extends VarFunction>[] ALL = new Class[] {
043      And.class, Or.class, Not.class, Xor.class,
044      Eq.class, Neq.class, Lt.class, Lte.class, Gt.class, Gte.class
045   };
046
047   /** {@code #{and(a, b, ...)}} — returns {@code "true"} only if all args are true. Variadic. */
048   public static class And extends TypedFunction {
049      @Override public String name() { return "and"; }
050      public String invoke(String[] args) {
051         for (var a : args)
052            if (!parseBool(a)) return "false";
053         return "true";
054      }
055   }
056
057   /** {@code #{or(a, b, ...)}} — returns {@code "true"} if any arg is true. Variadic. */
058   public static class Or extends TypedFunction {
059      @Override public String name() { return "or"; }
060      public String invoke(String[] args) {
061         for (var a : args)
062            if (parseBool(a)) return "true";
063         return "false";
064      }
065   }
066
067   /** {@code #{not(a)}} — returns the boolean negation of {@code a}. */
068   public static class Not extends TypedFunction {
069      @Override public String name() { return "not"; }
070      public String invoke(boolean a) { return String.valueOf(!a); }
071   }
072
073   /** {@code #{xor(a, b)}} — returns {@code a XOR b}. */
074   public static class Xor extends TypedFunction {
075      @Override public String name() { return "xor"; }
076      public String invoke(boolean a, boolean b) { return String.valueOf(a ^ b); }
077   }
078
079   /** {@code #{eq(a, b)}} — string-equality. Returns {@code "true"} or {@code "false"}. */
080   public static class Eq extends TypedFunction {
081      @Override public String name() { return "eq"; }
082      public String invoke(String a, String b) { return String.valueOf(a == null ? b == null : a.equals(b)); }
083   }
084
085   /** {@code #{neq(a, b)}} — string-inequality. */
086   public static class Neq extends TypedFunction {
087      @Override public String name() { return "neq"; }
088      public String invoke(String a, String b) { return String.valueOf(!(a == null ? b == null : a.equals(b))); }
089   }
090
091   /** {@code #{lt(a, b)}} — numeric less-than. */
092   public static class Lt extends TypedFunction {
093      @Override public String name() { return "lt"; }
094      public String invoke(double a, double b) { return String.valueOf(a < b); }
095   }
096
097   /** {@code #{lte(a, b)}} — numeric less-than-or-equal. */
098   public static class Lte extends TypedFunction {
099      @Override public String name() { return "lte"; }
100      public String invoke(double a, double b) { return String.valueOf(a <= b); }
101   }
102
103   /** {@code #{gt(a, b)}} — numeric greater-than. */
104   public static class Gt extends TypedFunction {
105      @Override public String name() { return "gt"; }
106      public String invoke(double a, double b) { return String.valueOf(a > b); }
107   }
108
109   /** {@code #{gte(a, b)}} — numeric greater-than-or-equal. */
110   public static class Gte extends TypedFunction {
111      @Override public String name() { return "gte"; }
112      public String invoke(double a, double b) { return String.valueOf(a >= b); }
113   }
114
115   /** Same truthiness table as {@link ArgCoercer}, exposed for variadic use in {@link And}/{@link Or}. */
116   @SuppressWarnings({
117      "javabugs:S2259" // The trimmed value is never null; a null input is mapped to the empty string first.
118   })
119   private static boolean parseBool(String s) {
120      var t = s == null ? "" : s.trim();
121      if (eqic(t, "true") || eq(t, "1") || eqic(t, "yes") || eqic(t, "on"))
122         return true;
123      if (t.isEmpty() || eqic(t, "false") || eq(t, "0") || eqic(t, "no") || eqic(t, "off"))
124         return false;
125      throw iaex("cannot coerce '%s' to boolean (accepted: true/1/yes/on, false/0/no/off, empty)", s);
126   }
127}