| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package freemarker.core; |
| |
| import java.util.regex.Pattern; |
| import java.util.regex.PatternSyntaxException; |
| |
| import freemarker.cache.MruCacheStorage; |
| import freemarker.log.Logger; |
| import freemarker.template.TemplateModelException; |
| import freemarker.template.utility.StringUtil; |
| |
| /** |
| * Helper for language features (like built-ins) that use regular expressions. |
| */ |
| final class RegexpHelper { |
| |
| private static final Logger LOG = Logger.getLogger("freemarker.runtime"); |
| |
| private static volatile boolean flagWarningsEnabled = LOG.isWarnEnabled(); |
| private static final int MAX_FLAG_WARNINGS_LOGGED = 25; |
| private static final Object flagWarningsCntSync = new Object(); |
| private static int flagWarningsCnt; |
| |
| private static final MruCacheStorage patternCache = new MruCacheStorage(50, 150); |
| |
| static private long intFlagToLong(int flag) { |
| return flag & 0x0000FFFFL; |
| } |
| |
| // Standard regular expression flags converted to long: |
| static final long RE_FLAG_CASE_INSENSITIVE = intFlagToLong(Pattern.CASE_INSENSITIVE); |
| |
| static final long RE_FLAG_MULTILINE = intFlagToLong(Pattern.MULTILINE); |
| |
| static final long RE_FLAG_COMMENTS = intFlagToLong(Pattern.COMMENTS); |
| |
| static final long RE_FLAG_DOTALL = intFlagToLong(Pattern.DOTALL); |
| |
| // FreeMarker-specific regular expression flags (using the higher 32 bits): |
| static final long RE_FLAG_REGEXP = 0x100000000L; |
| |
| static final long RE_FLAG_FIRST_ONLY = 0x200000000L; |
| |
| // Can't be instantiated |
| private RegexpHelper() { } |
| |
| static Pattern getPattern(String patternString, int flags) |
| throws TemplateModelException { |
| PatternCacheKey patternKey = new PatternCacheKey(patternString, flags); |
| |
| Pattern result; |
| |
| synchronized (patternCache) { |
| result = (Pattern) patternCache.get(patternKey); |
| } |
| if (result != null) { |
| return result; |
| } |
| |
| try { |
| result = Pattern.compile(patternString, flags); |
| } catch (PatternSyntaxException e) { |
| throw new _TemplateModelException(e, |
| "Malformed regular expression: ", new _DelayedGetMessage(e)); |
| } |
| synchronized (patternCache) { |
| patternCache.put(patternKey, result); |
| } |
| return result; |
| }; |
| |
| private static class PatternCacheKey { |
| private final String patternString; |
| private final int flags; |
| private final int hashCode; |
| |
| public PatternCacheKey(String patternString, int flags) { |
| this.patternString = patternString; |
| this.flags = flags; |
| hashCode = patternString.hashCode() + 31 * flags; |
| } |
| |
| @Override |
| public boolean equals(Object that) { |
| if (that instanceof PatternCacheKey) { |
| PatternCacheKey thatPCK = (PatternCacheKey) that; |
| return thatPCK.flags == flags |
| && thatPCK.patternString.equals(patternString); |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| return hashCode; |
| } |
| |
| } |
| |
| static long parseFlagString(String flagString) { |
| long flags = 0; |
| for (int i = 0; i < flagString.length(); i++) { |
| char c = flagString.charAt(i); |
| switch (c) { |
| case 'i': |
| flags |= RE_FLAG_CASE_INSENSITIVE; |
| break; |
| case 'm': |
| flags |= RE_FLAG_MULTILINE; |
| break; |
| case 'c': |
| flags |= RE_FLAG_COMMENTS; |
| break; |
| case 's': |
| flags |= RE_FLAG_DOTALL; |
| break; |
| case 'r': |
| flags |= RE_FLAG_REGEXP; |
| break; |
| case 'f': |
| flags |= RE_FLAG_FIRST_ONLY; |
| break; |
| default: |
| if (flagWarningsEnabled) { |
| RegexpHelper.logFlagWarning( |
| "Unrecognized regular expression flag: " |
| + StringUtil.jQuote(String.valueOf(c)) + "."); |
| } |
| } // switch |
| } |
| return flags; |
| } |
| |
| /** |
| * Logs flag warning for a limited number of times. This is used to prevent |
| * log flooding. |
| */ |
| static void logFlagWarning(String message) { |
| if (!flagWarningsEnabled) return; |
| |
| int cnt; |
| synchronized (flagWarningsCntSync) { |
| cnt = flagWarningsCnt; |
| if (cnt < MAX_FLAG_WARNINGS_LOGGED) { |
| flagWarningsCnt++; |
| } else { |
| flagWarningsEnabled = false; |
| return; |
| } |
| } |
| message += " This will be an error in some later FreeMarker version!"; |
| if (cnt + 1 == MAX_FLAG_WARNINGS_LOGGED) { |
| message += " [Will not log more regular expression flag problems until restart!]"; |
| } |
| LOG.warn(message); |
| } |
| |
| static void checkNonRegexpFlags(String biName, long flags) throws _TemplateModelException { |
| checkOnlyHasNonRegexpFlags(biName, flags, false); |
| } |
| |
| static void checkOnlyHasNonRegexpFlags(String biName, long flags, boolean strict) |
| throws _TemplateModelException { |
| if (!strict && !flagWarningsEnabled) return; |
| |
| String flag; |
| if ((flags & RE_FLAG_MULTILINE) != 0) { |
| flag = "m"; |
| } else if ((flags & RE_FLAG_DOTALL) != 0) { |
| flag = "s"; |
| } else if ((flags & RE_FLAG_COMMENTS) != 0) { |
| flag = "c"; |
| } else { |
| return; |
| } |
| |
| final Object[] msg = { "?", biName ," doesn't support the \"", flag, "\" flag " |
| + "without the \"r\" flag." }; |
| if (strict) { |
| throw new _TemplateModelException(msg); |
| } else { |
| // Suppress error for backward compatibility |
| logFlagWarning(new _ErrorDescriptionBuilder(msg).toString()); |
| } |
| } |
| |
| } |