Line data Source code
1 : // Copyright (C) 2009 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.common.data; 16 : 17 : import java.util.ArrayList; 18 : import java.util.Arrays; 19 : import java.util.Collections; 20 : import java.util.HashMap; 21 : import java.util.List; 22 : import java.util.Map; 23 : 24 : /** Performs replacements on strings such as <code>Hello ${user}</code>. */ 25 : public class ParameterizedString { 26 : /** Obtain a string which has no parameters and always produces the value. */ 27 : public static ParameterizedString asis(String constant) { 28 1 : return new ParameterizedString(new Constant(constant)); 29 : } 30 : 31 : private final String pattern; 32 : private final String rawPattern; 33 : private final List<Format> patternOps; 34 : private final List<Parameter> parameters; 35 : 36 : protected ParameterizedString() { 37 0 : this(new Constant("")); 38 0 : } 39 : 40 1 : private ParameterizedString(Constant c) { 41 1 : pattern = c.text; 42 1 : rawPattern = c.text; 43 1 : patternOps = Collections.singletonList(c); 44 1 : parameters = Collections.emptyList(); 45 1 : } 46 : 47 147 : public ParameterizedString(String pattern) { 48 147 : final StringBuilder raw = new StringBuilder(); 49 147 : final List<Parameter> prs = new ArrayList<>(4); 50 147 : final List<Format> ops = new ArrayList<>(4); 51 : 52 147 : int i = 0; 53 147 : while (i < pattern.length()) { 54 147 : final int b = pattern.indexOf("${", i); 55 147 : if (b < 0) { 56 147 : break; 57 : } 58 147 : final int e = pattern.indexOf("}", b + 2); 59 147 : if (e < 0) { 60 0 : break; 61 : } 62 : 63 147 : raw.append(pattern, i, b); 64 147 : ops.add(new Constant(pattern.substring(i, b))); 65 : 66 : // "${parameter[.functions...]}" -> "parameter[.functions...]" 67 147 : final Parameter p = new Parameter(pattern.substring(b + 2, e)); 68 : 69 147 : raw.append("{").append(prs.size()).append("}"); 70 147 : prs.add(p); 71 147 : ops.add(p); 72 : 73 147 : i = e + 1; 74 147 : } 75 147 : if (i < pattern.length()) { 76 147 : raw.append(pattern.substring(i)); 77 147 : ops.add(new Constant(pattern.substring(i))); 78 : } 79 : 80 147 : this.pattern = pattern; 81 147 : this.rawPattern = raw.toString(); 82 147 : this.patternOps = Collections.unmodifiableList(ops); 83 147 : this.parameters = Collections.unmodifiableList(prs); 84 147 : } 85 : 86 : /** Get the original pattern given to the constructor. */ 87 : public String getPattern() { 88 48 : return pattern; 89 : } 90 : 91 : /** Get the pattern with variables replaced with {0}, {1}, ... */ 92 : public String getRawPattern() { 93 107 : return rawPattern; 94 : } 95 : 96 : /** Get the list of parameter names, ordered by appearance in the pattern. */ 97 : public List<String> getParameterNames() { 98 107 : final ArrayList<String> r = new ArrayList<>(parameters.size()); 99 107 : for (Parameter p : parameters) { 100 2 : r.add(p.name); 101 2 : } 102 107 : return Collections.unmodifiableList(r); 103 : } 104 : 105 : /** Convert a map of parameters into a value array for binding. */ 106 : public String[] bind(Map<String, String> params) { 107 1 : final String[] r = new String[parameters.size()]; 108 1 : for (int i = 0; i < r.length; i++) { 109 1 : final StringBuilder b = new StringBuilder(); 110 1 : parameters.get(i).format(b, params); 111 1 : r[i] = b.toString(); 112 : } 113 1 : return r; 114 : } 115 : 116 : /** Format this string by performing the variable replacements. */ 117 : public String replace(Map<String, String> params) { 118 113 : final StringBuilder r = new StringBuilder(); 119 113 : for (Format f : patternOps) { 120 113 : f.format(r, params); 121 113 : } 122 113 : return r.toString(); 123 : } 124 : 125 : public Builder replace(String name, String value) { 126 103 : return new Builder().replace(name, value); 127 : } 128 : 129 : @Override 130 : public String toString() { 131 0 : return getPattern(); 132 : } 133 : 134 103 : public final class Builder { 135 103 : private final Map<String, String> params = new HashMap<>(); 136 : 137 : public Builder replace(String name, String value) { 138 103 : params.put(name, value); 139 103 : return this; 140 : } 141 : 142 : @Override 143 : public String toString() { 144 103 : return ParameterizedString.this.replace(params); 145 : } 146 : } 147 : 148 : private abstract static class Format { 149 : abstract void format(StringBuilder b, Map<String, String> p); 150 : } 151 : 152 : private static class Constant extends Format { 153 : private final String text; 154 : 155 147 : Constant(String text) { 156 147 : this.text = text; 157 147 : } 158 : 159 : @Override 160 : void format(StringBuilder b, Map<String, String> p) { 161 113 : b.append(text); 162 113 : } 163 : } 164 : 165 : private static class Parameter extends Format { 166 : private final String name; 167 : private final List<Function> functions; 168 : 169 147 : Parameter(String parameter) { 170 : // "parameter[.functions...]" -> (parameter, functions...) 171 147 : final List<String> names = Arrays.asList(parameter.split("\\.")); 172 147 : final List<Function> functs = new ArrayList<>(names.size()); 173 : 174 147 : if (names.isEmpty()) { 175 0 : name = ""; 176 : } else { 177 147 : name = names.get(0); 178 : 179 147 : for (String fname : names.subList(1, names.size())) { 180 1 : final Function function = FUNCTIONS.get(fname); 181 1 : if (function != null) { 182 1 : functs.add(function); 183 : } 184 1 : } 185 : } 186 : 187 147 : functions = Collections.unmodifiableList(functs); 188 147 : } 189 : 190 : @Override 191 : void format(StringBuilder b, Map<String, String> p) { 192 111 : String v = p.get(name); 193 111 : if (v == null) { 194 1 : v = ""; 195 : } 196 111 : for (Function function : functions) { 197 1 : v = function.apply(v); 198 1 : } 199 111 : b.append(v); 200 111 : } 201 : } 202 : 203 : private abstract static class Function { 204 : abstract String apply(String a); 205 : } 206 : 207 147 : private static final Map<String, Function> FUNCTIONS = initFunctions(); 208 : 209 : private static Map<String, Function> initFunctions() { 210 147 : HashMap<String, Function> m = new HashMap<>(); 211 147 : m.put( 212 : "toLowerCase", 213 147 : new Function() { 214 : @Override 215 : String apply(String a) { 216 1 : return a.toLowerCase(); 217 : } 218 : }); 219 147 : m.put( 220 : "toUpperCase", 221 147 : new Function() { 222 : @Override 223 : String apply(String a) { 224 1 : return a.toUpperCase(); 225 : } 226 : }); 227 147 : m.put( 228 : "localPart", 229 147 : new Function() { 230 : @Override 231 : String apply(String a) { 232 1 : int at = a.indexOf('@'); 233 1 : return at < 0 ? a : a.substring(0, at); 234 : } 235 : }); 236 147 : return Collections.unmodifiableMap(m); 237 : } 238 : }