Use custom Matcher to find and replace properties The simplest way to find and replace properties in Strings is using a Pattern Matcher. However, implementing a specialized Matcher using simpler String primitives is faster, so do so using an API that looks and feels as simple as the Pattern Matcher API. In a sample walking ancestors use case, this caching and saves a small but measurable amount of the total time. In the case of a task.config which walks all dependencies for a change when run with status:open --no-limit --task--applicable the gain can be seen below. Before this change: 8m7s 3m16s 3m26s 3m16s 3m21s After this change: 2m34s 2m28s 2m40s 2m34s 2m39s Change-Id: Ief2d6a24f6952828214f55a4a4cf9ea0f3768f42
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/properties/AbstractExpander.java b/src/main/java/com/googlesource/gerrit/plugins/task/properties/AbstractExpander.java index bccf03c..3813319 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/task/properties/AbstractExpander.java +++ b/src/main/java/com/googlesource/gerrit/plugins/task/properties/AbstractExpander.java
@@ -20,8 +20,6 @@ import java.util.List; import java.util.Set; import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Use to expand properties like ${property} in Strings into their values. @@ -41,9 +39,6 @@ * the name/value associations via the getValueForName() method. */ public abstract class AbstractExpander { - // "${_name}" -> group(1) = "_name" - protected static final Pattern PATTERN = Pattern.compile("\\$\\{([^}]+)\\}"); - /** * Returns expanded object if property found in the Strings in the object's Fields (except the * excluded ones). Returns same object if no expansions occurred. @@ -138,13 +133,13 @@ if (text == null) { return null; } - Matcher m = PATTERN.matcher(text); + Matcher m = new Matcher(text); if (!m.find()) { return text; } StringBuffer out = new StringBuffer(); do { - m.appendReplacement(out, Matcher.quoteReplacement(getValueForName(m.group(1)))); + m.appendValue(out, getValueForName(m.getName())); } while (m.find()); m.appendTail(out); return out.toString();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/properties/Matcher.java b/src/main/java/com/googlesource/gerrit/plugins/task/properties/Matcher.java new file mode 100644 index 0000000..abe203d --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/task/properties/Matcher.java
@@ -0,0 +1,57 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed 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 com.googlesource.gerrit.plugins.task.properties; + +/** A handcrafted properties Matcher which has an API similar to an RE Matcher, but is faster. */ +public class Matcher { + String text; + int start; + int nameStart; + int end; + int cursor; + + public Matcher(String text) { + this.text = text; + } + + public boolean find() { + start = text.indexOf("${", cursor); + nameStart = start + 2; + if (start < 0 || text.length() < nameStart + 1) { + return false; + } + end = text.indexOf('}', nameStart); + return end >= 0; + } + + public String getName() { + return text.substring(nameStart, end); + } + + public void appendValue(StringBuffer buffer, String value) { + if (start > cursor) { + buffer.append(text.substring(cursor, start)); + } + buffer.append(value); + cursor = end + 1; + } + + public void appendTail(StringBuffer buffer) { + if (cursor < text.length()) { + buffer.append(text.substring(cursor)); + cursor = text.length(); + } + } +}