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 java.util.*;
022import java.util.concurrent.*;
023
024import org.apache.juneau.commons.svl.*;
025
026/**
027 * Random / UUID functions for the {@code #{...}} script catalog.
028 *
029 * <p>
030 * All {@code rand*} functions evaluate per-resolve and are NOT folded by stable-value folding
031 * (stable folding is {@link Var}-side only — function evaluation always runs). Use a
032 * {@code Supplier<String>} field to get fresh-per-{@code .get()} semantics, or a bare
033 * {@code String} field to capture once at bean construction.
034 *
035 * <h5 class='section'>Cryptographic strength:</h5>
036 * <ul>
037 *    <li>{@link Rand}, {@link RandInt}, {@link RandLong}, {@link RandString}, {@link RandChoice}
038 *       are backed by {@link ThreadLocalRandom}. Threadsafe; <b>NOT</b> cryptographically secure.
039 *       Do not use for tokens, secrets, or session ids.
040 *    <li>{@link Uuid} is backed by {@link UUID#randomUUID()} which uses {@link java.security.SecureRandom}
041 *       — cryptographically strong.
042 * </ul>
043 */
044public final class RandomFunctions {
045
046   private RandomFunctions() {}
047
048   /** All function classes in this category. */
049   @SuppressWarnings({
050      "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.
051      "unchecked" // Cast is safe: type verified by caller context.
052   })
053   public static final Class<? extends VarFunction>[] ALL = new Class[] {
054      Rand.class, RandInt.class, RandLong.class, RandString.class, RandChoice.class, Uuid.class
055   };
056
057   /** Default character set for {@link RandString} when the user supplies no explicit set. */
058   private static final String ALPHA_NUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
059
060   /** {@code #{rand()}} — random double in {@code [0.0, 1.0)}. */
061   public static class Rand extends TypedFunction {
062      @Override public String name() { return "rand"; }
063      public String invoke() { return String.valueOf(ThreadLocalRandom.current().nextDouble()); }
064   }
065
066   /**
067    * {@code #{randInt(max)}} — random int in {@code [0, max)}.
068    *
069    * <p>
070    * {@code #{randInt(min, max)}} — random int in {@code [min, max]} (inclusive both ends).
071    */
072   public static class RandInt extends TypedFunction {
073      @Override public String name() { return "randInt"; }
074      public String invoke(int max) {
075         if (max <= 0) throw iaex("randInt: max must be > 0");
076         return String.valueOf(ThreadLocalRandom.current().nextInt(max));
077      }
078      public String invoke(int min, int max) {
079         if (min > max) throw iaex("randInt: min (%s) must be <= max (%s)", min, max);
080         return String.valueOf(ThreadLocalRandom.current().nextLong(min, (long) max + 1));
081      }
082   }
083
084   /** {@code #{randLong(min, max)}} — random long in {@code [min, max]} (inclusive both ends). */
085   public static class RandLong extends TypedFunction {
086      @Override public String name() { return "randLong"; }
087      public String invoke(long min, long max) {
088         if (min > max) throw iaex("randLong: min (%s) must be <= max (%s)", min, max);
089         if (max == Long.MAX_VALUE)
090            return String.valueOf(ThreadLocalRandom.current().nextLong(min, max));
091         return String.valueOf(ThreadLocalRandom.current().nextLong(min, max + 1));
092      }
093   }
094
095   /**
096    * {@code #{randString(length)}} — random alphanumeric {@code [A-Za-z0-9]} string of
097    * {@code length} characters.
098    *
099    * <p>
100    * {@code #{randString(length, charset)}} — same, but characters drawn uniformly from the
101    * supplied {@code charset} string.
102    */
103   public static class RandString extends TypedFunction {
104      @Override public String name() { return "randString"; }
105      public String invoke(int length) { return invoke(length, ALPHA_NUM); }
106      public String invoke(int length, String charset) {
107         if (length < 0) throw iaex("randString: length must be >= 0");
108         if (ie(charset)) charset = ALPHA_NUM;
109         var rnd = ThreadLocalRandom.current();
110         var sb = new StringBuilder(length);
111         for (var i = 0; i < length; i++)
112            sb.append(charset.charAt(rnd.nextInt(charset.length())));
113         return sb.toString();
114      }
115   }
116
117   /** {@code #{randChoice(option1, option2, ...)}} — variadic. Returns one arg chosen uniformly. */
118   public static class RandChoice extends TypedFunction {
119      @Override public String name() { return "randChoice"; }
120      public String invoke(String[] options) {
121         if (options.length == 0) throw iaex("randChoice: at least one option required");
122         return options[ThreadLocalRandom.current().nextInt(options.length)];
123      }
124   }
125
126   /** {@code #{uuid()}} — random UUID via {@link UUID#randomUUID()}. Cryptographically strong. */
127   public static class Uuid extends TypedFunction {
128      @Override public String name() { return "uuid"; }
129      public String invoke() { return UUID.randomUUID().toString(); }
130   }
131}