// 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.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** Performs replacements on strings such as <code>Hello ${user}</code>. */ public class ParamertizedString { /** Obtain a string which has no parameters and always produces the value. */ public static ParamertizedString asis(final String constant) { return new ParamertizedString(new Constant(constant)); } private final String pattern; private final String rawPattern; private final List<Format> patternOps; private final List<Parameter> parameters; protected ParamertizedString() { this(new Constant("")); } private ParamertizedString(final Constant c) { pattern = c.text; rawPattern = c.text; patternOps = Collections.<Format> singletonList(c); parameters = Collections.emptyList(); } public ParamertizedString(final String pattern) { final StringBuilder raw = new StringBuilder(); final List<Parameter> prs = new ArrayList<Parameter>(4); final List<Format> ops = new ArrayList<Format>(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))); String expr = pattern.substring(b + 2, e); Function function; int lastDot = expr.lastIndexOf('.'); if (lastDot < 0) { function = NOOP; } else { function = FUNCTIONS.get(expr.substring(lastDot + 1)); if (function == null) { function = NOOP; } else { expr = expr.substring(0, lastDot); } } final Parameter p = new Parameter(expr, function); raw.append("{" + prs.size() + "}"); 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<String>(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<String, String>(); public Builder replace(final String name, final String value) { params.put(name, value); return this; } @Override public String toString() { return ParamertizedString.this.replace(params); } } private static abstract 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 Function function; Parameter(final String name, final Function function) { this.name = name; this.function = function; } @Override void format(StringBuilder b, Map<String, String> p) { String v = p.get(name); if (v == null) { v = ""; } b.append(function.apply(v)); } } private static abstract class Function { abstract String apply(String a); } private static final Map<String, Function> FUNCTIONS = initFunctions(); private static final Function NOOP = new Function() { @Override String apply(String a) { return a; } }; private static Map<String, Function> initFunctions() { final HashMap<String, Function> m = new HashMap<String, Function>(); 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); } }