1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache license, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the license for the specific language governing permissions and
15 * limitations under the license.
16 */
17 package org.apache.commons.text.lookup;
18
19 import java.util.Collections;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.stream.Collectors;
23
24 /**
25 * Proxies other {@link StringLookup}s using a keys within ${} markers using the format "${StringLookup:Key}".
26 * <p>
27 * Uses the {@link StringLookupFactory default lookups}.
28 * </p>
29 */
30 final class InterpolatorStringLookup extends AbstractStringLookup {
31
32 /**
33 * Defines the singleton for this class.
34 *
35 * @since 1.6
36 */
37 static final AbstractStringLookup INSTANCE = new InterpolatorStringLookup();
38
39 /** Constant for the prefix separator. */
40 private static final char PREFIX_SEPARATOR = ':';
41
42 /** The default string lookup. */
43 private final StringLookup defaultStringLookup;
44
45 /** The map of String lookups keyed by prefix. */
46 private final Map<String, StringLookup> stringLookupMap;
47
48 /**
49 * Constructs an instance using only lookups that work without initial properties and are stateless.
50 * <p>
51 * Uses the {@link StringLookupFactory default lookups}.
52 * </p>
53 */
54 InterpolatorStringLookup() {
55 this((Map<String, String>) null);
56 }
57
58 /**
59 * Constructs a fully customized instance.
60 *
61 * @param stringLookupMap the map of string lookups.
62 * @param defaultStringLookup the default string lookup.
63 * @param addDefaultLookups whether the default lookups should be used.
64 */
65 InterpolatorStringLookup(final Map<String, StringLookup> stringLookupMap, final StringLookup defaultStringLookup,
66 final boolean addDefaultLookups) {
67 this.defaultStringLookup = defaultStringLookup;
68 this.stringLookupMap = stringLookupMap.entrySet().stream().collect(Collectors.toMap(e -> StringLookupFactory.toKey(e.getKey()), Entry::getValue));
69 if (addDefaultLookups) {
70 StringLookupFactory.INSTANCE.addDefaultStringLookups(this.stringLookupMap);
71 }
72 }
73
74 /**
75 * Constructs an instance using only lookups that work without initial properties and are stateless.
76 * <p>
77 * Uses the {@link StringLookupFactory default lookups}.
78 * </p>
79 *
80 * @param <V> the map's value type.
81 * @param defaultMap the default map for string lookups.
82 */
83 <V> InterpolatorStringLookup(final Map<String, V> defaultMap) {
84 this(StringLookupFactory.INSTANCE.mapStringLookup(defaultMap));
85 }
86
87 /**
88 * Constructs an instance with the given lookup.
89 *
90 * @param defaultStringLookup the default lookup.
91 */
92 InterpolatorStringLookup(final StringLookup defaultStringLookup) {
93 this(Collections.emptyMap(), defaultStringLookup, true);
94 }
95
96 /**
97 * Gets the lookup map.
98 *
99 * @return The lookup map.
100 */
101 public Map<String, StringLookup> getStringLookupMap() {
102 return stringLookupMap;
103 }
104
105 /**
106 * Resolves the specified variable. This implementation will try to extract a variable prefix from the given
107 * variable name (the first colon (':') is used as prefix separator). It then passes the name of the variable with
108 * the prefix stripped to the lookup object registered for this prefix. If no prefix can be found or if the
109 * associated lookup object cannot resolve this variable, the default lookup object will be used.
110 *
111 * @param key the name of the variable whose value is to be looked up
112 * @return The value of this variable or <strong>null</strong> if it cannot be resolved
113 */
114 @Override
115 public String lookup(String key) {
116 if (key == null) {
117 return null;
118 }
119
120 final int prefixPos = key.indexOf(PREFIX_SEPARATOR);
121 if (prefixPos >= 0) {
122 final String prefix = StringLookupFactory.toKey(key.substring(0, prefixPos));
123 final String name = key.substring(prefixPos + 1);
124 final StringLookup lookup = stringLookupMap.get(prefix);
125 String value = null;
126 if (lookup != null) {
127 value = lookup.apply(name);
128 }
129
130 if (value != null) {
131 return value;
132 }
133 key = key.substring(prefixPos + 1);
134 }
135 if (defaultStringLookup != null) {
136 return defaultStringLookup.apply(key);
137 }
138 return null;
139 }
140
141 @Override
142 public String toString() {
143 return super.toString() + " [stringLookupMap=" + stringLookupMap + ", defaultStringLookup="
144 + defaultStringLookup + "]";
145 }
146 }