View Javadoc
1   /*
2    *    Copyright 2010-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.migration;
17  
18  import java.util.Arrays;
19  import java.util.Collections;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Objects;
23  import java.util.stream.Collectors;
24  
25  /**
26   * @author Clinton Begin
27   */
28  public class VariableReplacer {
29  
30    private static final String OPEN_TOKEN = "${";
31    private static final String CLOSE_TOKEN = "}";
32    private final List<Map<? extends Object, ? extends Object>> variablesList;
33  
34    public VariableReplacer(Map<? extends Object, ? extends Object> variablesList) {
35      this(Arrays.asList(variablesList));
36    }
37  
38    public VariableReplacer(List<Map<? extends Object, ? extends Object>> variablesList) {
39      this.variablesList = variablesList == null ? Collections.emptyList()
40          : variablesList.stream().filter(Objects::nonNull).collect(Collectors.toList());
41    }
42  
43    public String replace(String text) {
44      if (text == null || text.isEmpty()) {
45        return "";
46      }
47      // search open token
48      int start = text.indexOf(OPEN_TOKEN);
49      if (start == -1) {
50        return text;
51      }
52      char[] src = text.toCharArray();
53      int offset = 0;
54      final StringBuilder builder = new StringBuilder();
55      StringBuilder expression = null;
56      while (start > -1) {
57        if (start > 0 && src[start - 1] == '\\') {
58          // this open token is escaped. remove the backslash and continue.
59          builder.append(src, offset, start - offset - 1).append(OPEN_TOKEN);
60          offset = start + OPEN_TOKEN.length();
61        } else {
62          // found open token. let's search close token.
63          if (expression == null) {
64            expression = new StringBuilder();
65          } else {
66            expression.setLength(0);
67          }
68          builder.append(src, offset, start - offset);
69          offset = start + OPEN_TOKEN.length();
70          int end = text.indexOf(CLOSE_TOKEN, offset);
71          while (end > -1) {
72            if (end <= offset || src[end - 1] != '\\') {
73              expression.append(src, offset, end - offset);
74              break;
75            }
76            // this close token is escaped. remove the backslash and continue.
77            expression.append(src, offset, end - offset - 1).append(CLOSE_TOKEN);
78            offset = end + CLOSE_TOKEN.length();
79            end = text.indexOf(CLOSE_TOKEN, offset);
80          }
81          if (end == -1) {
82            // close token was not found.
83            builder.append(src, start, src.length - start);
84            offset = src.length;
85          } else {
86            appendWithReplace(builder, expression.toString());
87            offset = end + CLOSE_TOKEN.length();
88          }
89        }
90        start = text.indexOf(OPEN_TOKEN, offset);
91      }
92      if (offset < src.length) {
93        builder.append(src, offset, src.length - offset);
94      }
95      return builder.toString();
96    }
97  
98    private StringBuilder appendWithReplace(StringBuilder builder, String key) {
99      String value = null;
100     for (Map<? extends Object, ? extends Object> variables : variablesList) {
101       value = (String) variables.get(key);
102       if (value != null) {
103         builder.append(value);
104         break;
105       }
106     }
107     if (value == null) {
108       builder.append(OPEN_TOKEN).append(key).append(CLOSE_TOKEN);
109     }
110     return builder;
111   }
112 }