blob: 050bcf50e3289f62c58d545fa9c703d6587fe07a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.servicecomb.config.inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.servicecomb.config.ConfigUtil;
/**
* <pre>
* not care for performance
*
* behavior of multiple list if defined:
* org.apache.servicecomb.config.inject.TestPlaceholderResolver#multi_list()
* behavior of nested and multiple list variable is undefined
* org.apache.servicecomb.config.inject.TestPlaceholderResolver#mixed()
* </pre>
*/
public class PlaceholderResolver {
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("(?<escape>\\\\)?\\$\\{(?<name>[^{}]+)\\}");
static class SplitPart {
boolean var;
String fullName;
Object value;
public SplitPart(boolean var, String fullName) {
this.var = var;
this.fullName = fullName;
}
@Override
public String toString() {
return "SplitPart{" +
"var=" + var +
", fullName='" + fullName + '\'' +
", value=" + value +
'}';
}
}
static class Row {
List<SplitPart> parts = new ArrayList<>();
int cartesianProductCount = 1;
int varCount = 0;
}
public String replaceFirst(String str) {
return replace(str, Collections.emptyMap()).get(0);
}
public String replaceFirst(String str, Map<String, Object> parameters) {
return replace(str, parameters).get(0);
}
public List<String> replace(String str, Map<String, Object> parameters) {
List<Row> finalRows = replaceToRows(str, parameters);
List<String> replaced = new ArrayList<>();
for (Row row : finalRows) {
resolve(row, replaced);
}
replaced.replaceAll(s -> s.replace("\\$", "$"));
return replaced;
}
private List<Row> replaceToRows(String str, Map<String, Object> parameters) {
List<Row> finalRows = new ArrayList<>();
List<String> remainRows = new ArrayList<>();
replaceToRows(str, parameters, remainRows, finalRows);
for (String row : remainRows) {
List<Row> nestedRows = replaceToRows(row, parameters);
finalRows.addAll(nestedRows);
}
return finalRows;
}
private void replaceToRows(String str, Map<String, Object> parameters, List<String> remainRows,
List<Row> finalRows) {
Row row = parseToRow(str, parameters);
if (row.varCount == 0 && row.cartesianProductCount == 1) {
finalRows.add(row);
return;
}
resolve(row, remainRows);
}
private Row parseToRow(String str, Map<String, Object> parameters) {
Matcher matcher = PLACEHOLDER_PATTERN.matcher(str);
Row row = new Row();
int last = 0;
while (matcher.find()) {
row.parts.add(new SplitPart(false, str.substring(last, matcher.start())));
last = matcher.end();
if (matcher.group("escape") != null) {
row.parts.add(new SplitPart(false, matcher.group().substring(1)));
continue;
}
String name = matcher.group("name");
Object value = findValue(parameters, name);
if (value instanceof Collection) {
row.cartesianProductCount *= ((Collection) value).size();
}
if (value != null) {
row.varCount++;
}
SplitPart splitPart = new SplitPart(value != null, matcher.group());
splitPart.value = value;
row.parts.add(splitPart);
}
row.parts.add(new SplitPart(false, str.substring(last)));
return row;
}
// resolve placeholder and execute cartesian product
@SuppressWarnings("unchecked")
private void resolve(Row row, List<String> resolvedRows) {
List<StringBuilder> stringBuilders = new ArrayList<>();
for (int idx = 0; idx < row.cartesianProductCount; idx++) {
stringBuilders.add(new StringBuilder());
}
int collectionRepeatCount = 1;
for (SplitPart part : row.parts) {
if (!part.var) {
for (int idx = 0; idx < row.cartesianProductCount; idx++) {
StringBuilder sb = stringBuilders.get(idx);
if (part.fullName.startsWith("$")) {
sb.append("\\" + part.fullName);
continue;
}
sb.append(part.fullName);
}
continue;
}
if (part.value instanceof Collection) {
int size = ((Collection<String>) part.value).size();
int rowRepeatCount = row.cartesianProductCount / size / collectionRepeatCount;
int valueIdx = 0;
for (int collectionRepeatIdx = 0; collectionRepeatIdx < collectionRepeatCount; collectionRepeatIdx++) {
for (String value : (Collection<String>) part.value) {
for (int repeatIdx = 0; repeatIdx < rowRepeatCount; repeatIdx++) {
StringBuilder sb = stringBuilders.get(valueIdx);
valueIdx++;
sb.append(value);
}
}
}
collectionRepeatCount *= size;
continue;
}
// normal var
for (int idx = 0; idx < row.cartesianProductCount; idx++) {
StringBuilder sb = stringBuilders.get(idx);
sb.append(part.value);
}
}
for (StringBuilder sb : stringBuilders) {
resolvedRows.add(sb.toString());
}
}
private Object findValue(Map<String, Object> parameters, String key) {
Object value = parameters.get(key);
if (value == null) {
value = ConfigUtil.getProperty(key);
}
return value;
}
}