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();
+ }
+ }
+}