google-nomulus/java/google/registry/util/SqlTemplate.java
mcilwain 2aa897e698 Remove unnecessary generic type arguments
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=175155365
2017-11-21 18:17:31 -05:00

103 lines
4.3 KiB
Java

// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Sets.difference;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.re2j.Matcher;
import com.google.re2j.Pattern;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.concurrent.Immutable;
/** SQL template variable substitution. */
@Immutable
public final class SqlTemplate {
private static final Pattern KEY_PATTERN = Pattern.compile("[A-Z][_A-Z0-9]*");
private static final Pattern SEARCH_PATTERN =
Pattern.compile("(['\"]?)%(" + KEY_PATTERN + ")%(['\"]?)");
private static final CharMatcher LEGAL_SUBSTITUTIONS =
CharMatcher.javaLetterOrDigit().or(CharMatcher.anyOf("-_.,: "));
/** Returns a new immutable SQL template builder object, for query parameter substitution. */
public static SqlTemplate create(String template) {
return new SqlTemplate(template, ImmutableMap.of());
}
/**
* Adds a key/value that should be substituted an individual variable in the template.
*
* <p>Your template variables should appear as follows: {@code WHERE foo = '%BAR%'} and you
* would call {@code .put("BAR", "some value"} to safely substitute it with a value. Only
* whitelisted characters (as defined by {@link #LEGAL_SUBSTITUTIONS}) are allowed in values.
*
* @param key uppercase string that can have digits and underscores
* @param value substitution value, comprised of whitelisted characters
* @throws IllegalArgumentException if key or value has bad chars or duplicate keys were added
*/
public SqlTemplate put(String key, String value) {
checkArgument(KEY_PATTERN.matcher(key).matches(), "Bad substitution key: %s", key);
checkArgument(LEGAL_SUBSTITUTIONS.matchesAllOf(value), "Illegal characters in %s", value);
return new SqlTemplate(template, new ImmutableMap.Builder<String, String>()
.putAll(substitutions)
.put(key, value)
.build());
}
/**
* Returns the freshly substituted SQL code.
*
* @throws IllegalArgumentException if any substitution variable is not found in the template,
* or if there are any variable-like strings (%something%) left after substitution.
*/
public String build() {
StringBuffer result = new StringBuffer(template.length());
Set<String> found = new HashSet<>();
Matcher matcher = SEARCH_PATTERN.matcher(template);
while (matcher.find()) {
String wholeMatch = matcher.group(0);
String leftQuote = matcher.group(1);
String key = matcher.group(2);
String rightQuote = matcher.group(3);
String value = substitutions.get(key);
checkArgumentNotNull(value, "%%s% found in template but no substitution specified", key);
checkArgument(leftQuote.equals(rightQuote), "Quote mismatch: %s", wholeMatch);
matcher.appendReplacement(result, String.format("%s%s%s", leftQuote, value, rightQuote));
found.add(key);
}
matcher.appendTail(result);
Set<String> remaining = difference(substitutions.keySet(), found);
checkArgument(remaining.isEmpty(),
"Not found in template: %s", Joiner.on(", ").join(remaining));
return result.toString();
}
private final String template;
private final ImmutableMap<String, String> substitutions;
private SqlTemplate(String template, ImmutableMap<String, String> substitutions) {
this.template = checkNotNull(template);
this.substitutions = checkNotNull(substitutions);
}
}