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.regex.*; 020 021import org.apache.juneau.commons.collections.*; 022import org.apache.juneau.commons.svl.*; 023 024/** 025 * Regex functions for the {@code #{...}} script catalog. 026 * 027 * <p> 028 * Replacements for {@code PatternMatchVar} ({@code $PM}), {@code PatternExtractVar} 029 * ({@code $PE}), and {@code PatternReplaceVar} ({@code $PR}) per the migration inventory. 030 * 031 * <p> 032 * Note that {@link StringFunctions.Replace} is the literal-string replace; {@link ReplaceRegex} 033 * is the regex form. 034 */ 035public final class RegexFunctions { 036 037 private RegexFunctions() {} 038 039 // User-supplied regexes are compiled once and reused. Bounded (entire cache cleared past maxSize) to guard 040 // against unbounded growth from arbitrary user input. Pattern is immutable/thread-safe; a fresh Matcher is 041 // created per invocation, so behavior is identical to compiling on every call. 042 private static final Cache<String,Pattern> PATTERN_CACHE = Cache.of(String.class, Pattern.class).maxSize(1000).supplier(Pattern::compile).build(); 043 044 /** All function classes in this category. */ 045 @SuppressWarnings({ 046 "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. 047 "unchecked" // Cast is safe: type verified by caller context. 048 }) 049 public static final Class<? extends VarFunction>[] ALL = new Class[] { 050 Match.class, Extract.class, ReplaceRegex.class 051 }; 052 053 /** {@code #{match(s, regex)}} — replacement for the legacy {@code $PM{...}}. Returns {@code "true"} or {@code "false"}. */ 054 public static class Match extends TypedFunction { 055 @Override public String name() { return "match"; } 056 public String invoke(String s, String regex) { 057 if (s == null || regex == null) return "false"; 058 return String.valueOf(PATTERN_CACHE.get(regex).matcher(s).find()); 059 } 060 } 061 062 /** 063 * {@code #{extract(s, regex)}} / {@code #{extract(s, regex, group)}} — replacement for the 064 * legacy {@code $PE{...}}. Returns the first match; if {@code group} is supplied, returns 065 * that capture group from the first match. 066 */ 067 public static class Extract extends TypedFunction { 068 @Override public String name() { return "extract"; } 069 public String invoke(String s, String regex) { return invoke(s, regex, 0); } 070 public String invoke(String s, String regex, int group) { 071 if (s == null || regex == null) return ""; 072 var m = PATTERN_CACHE.get(regex).matcher(s); 073 if (!m.find()) return ""; 074 if (group < 0 || group > m.groupCount()) return ""; 075 var v = m.group(group); 076 return v == null ? "" : v; 077 } 078 } 079 080 /** {@code #{replaceRegex(s, regex, replacement)}} — replacement for the legacy {@code $PR{...}}. */ 081 public static class ReplaceRegex extends TypedFunction { 082 @Override public String name() { return "replaceRegex"; } 083 public String invoke(String s, String regex, String replacement) { 084 if (s == null) return ""; 085 if (regex == null) return s; 086 return PATTERN_CACHE.get(regex).matcher(s).replaceAll(replacement == null ? "" : replacement); 087 } 088 } 089}