blob: 4ed296fc7e64443e9fd51242c7490105789718ba [file] [log] [blame]
// Copyright (C) 2009 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.google.gerrit.common.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Performs replacements on strings such as {@code Hello ${user}}. */
public class ParameterizedString {
/** Obtain a string which has no parameters and always produces the value. */
public static ParameterizedString asis(final String constant) {
return new ParameterizedString(new Constant(constant));
}
private final String pattern;
private final String rawPattern;
private final List<Format> patternOps;
private final List<Parameter> parameters;
protected ParameterizedString() {
this(new Constant(""));
}
private ParameterizedString(final Constant c) {
pattern = c.text;
rawPattern = c.text;
patternOps = Collections.<Format> singletonList(c);
parameters = Collections.emptyList();
}
public ParameterizedString(final String pattern) {
final StringBuilder raw = new StringBuilder();
final List<Parameter> prs = new ArrayList<>(4);
final List<Format> ops = new ArrayList<>(4);
int i = 0;
while (i < pattern.length()) {
final int b = pattern.indexOf("${", i);
if (b < 0) {
break;
}
final int e = pattern.indexOf("}", b + 2);
if (e < 0) {
break;
}
raw.append(pattern.substring(i, b));
ops.add(new Constant(pattern.substring(i, b)));
// "${parameter[.functions...]}" -> "parameter[.functions...]"
final Parameter p = new Parameter(pattern.substring(b + 2, e));
raw.append("{").append(prs.size()).append("}");
prs.add(p);
ops.add(p);
i = e + 1;
}
if (i < pattern.length()) {
raw.append(pattern.substring(i));
ops.add(new Constant(pattern.substring(i)));
}
this.pattern = pattern;
this.rawPattern = raw.toString();
this.patternOps = Collections.unmodifiableList(ops);
this.parameters = Collections.unmodifiableList(prs);
}
/** Get the original pattern given to the constructor. */
public String getPattern() {
return pattern;
}
/** Get the pattern with variables replaced with {0}, {1}, ... */
public String getRawPattern() {
return rawPattern;
}
/** Get the list of parameter names, ordered by appearance in the pattern. */
public List<String> getParameterNames() {
final ArrayList<String> r = new ArrayList<>(parameters.size());
for (Parameter p : parameters) {
r.add(p.name);
}
return Collections.unmodifiableList(r);
}
/** Convert a map of parameters into a value array for binding. */
public String[] bind(final Map<String, String> params) {
final String[] r = new String[parameters.size()];
for (int i = 0; i < r.length; i++) {
final StringBuilder b = new StringBuilder();
parameters.get(i).format(b, params);
r[i] = b.toString();
}
return r;
}
/** Format this string by performing the variable replacements. */
public String replace(final Map<String, String> params) {
final StringBuilder r = new StringBuilder();
for (final Format f : patternOps) {
f.format(r, params);
}
return r.toString();
}
public Builder replace(final String name, final String value) {
return new Builder().replace(name, value);
}
@Override
public String toString() {
return getPattern();
}
public final class Builder {
private final Map<String, String> params = new HashMap<>();
public Builder replace(final String name, final String value) {
params.put(name, value);
return this;
}
@Override
public String toString() {
return ParameterizedString.this.replace(params);
}
}
private abstract static class Format {
abstract void format(StringBuilder b, Map<String, String> p);
}
private static class Constant extends Format {
private final String text;
Constant(final String text) {
this.text = text;
}
@Override
void format(StringBuilder b, Map<String, String> p) {
b.append(text);
}
}
private static class Parameter extends Format {
private final String name;
private final List<Function> functions;
Parameter(final String parameter) {
// "parameter[.functions...]" -> (parameter, functions...)
final List<String> names = Arrays.asList(parameter.split("\\."));
final List<Function> functs = new ArrayList<>(names.size());
if (names.isEmpty()) {
name = "";
} else {
name = names.get(0);
for (String fname : names.subList(1, names.size())) {
final Function function = FUNCTIONS.get(fname);
if (function != null) {
functs.add(function);
}
}
}
functions = Collections.unmodifiableList(functs);
}
@Override
void format(StringBuilder b, Map<String, String> p) {
String v = p.get(name);
if (v == null) {
v = "";
}
for (Function function : functions) {
v = function.apply(v);
}
b.append(v);
}
}
private abstract static class Function {
abstract String apply(String a);
}
private static final Map<String, Function> FUNCTIONS = initFunctions();
private static Map<String, Function> initFunctions() {
HashMap<String, Function> m = new HashMap<>();
m.put("toLowerCase", new Function() {
@Override
String apply(String a) {
return a.toLowerCase();
}
});
m.put("toUpperCase", new Function() {
@Override
String apply(String a) {
return a.toUpperCase();
}
});
m.put("localPart", new Function() {
@Override
String apply(String a) {
int at = a.indexOf('@');
return at < 0 ? a : a.substring(0, at);
}
});
return Collections.unmodifiableMap(m);
}
}