Skip to content

Commit 2838a08

Browse files
Move token substitution in generic TokenReplace class.
1 parent 185e38a commit 2838a08

File tree

2 files changed

+487
-0
lines changed

2 files changed

+487
-0
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package jdk.jpackage.internal.util;
26+
27+
import static java.util.stream.Collectors.joining;
28+
29+
import java.util.ArrayList;
30+
import java.util.Arrays;
31+
import java.util.Comparator;
32+
import java.util.HashMap;
33+
import java.util.List;
34+
import java.util.Map;
35+
import java.util.Objects;
36+
import java.util.Optional;
37+
import java.util.function.Function;
38+
import java.util.function.Predicate;
39+
import java.util.function.Supplier;
40+
import java.util.regex.Matcher;
41+
import java.util.regex.Pattern;
42+
import java.util.stream.IntStream;
43+
import java.util.stream.Stream;
44+
45+
public final class TokenReplace {
46+
47+
private record TokenCut(String[] main, String[] sub) {
48+
static String[] orderTokens(String... tokens) {
49+
if (tokens.length == 0) {
50+
throw new IllegalArgumentException("Empty token list");
51+
}
52+
53+
final var orderedTokens = Stream.of(tokens)
54+
.sorted(Comparator.<String>naturalOrder().thenComparing(Comparator.comparingInt(String::length)))
55+
.distinct()
56+
.toArray(String[]::new);
57+
58+
if (orderedTokens[0].isEmpty()) {
59+
throw new IllegalArgumentException("Empty token in the list of tokens");
60+
}
61+
62+
return orderedTokens;
63+
}
64+
65+
static TokenCut createFromOrderedTokens(String... tokens) {
66+
final List<Integer> subTokens = new ArrayList<>();
67+
68+
for (var i = 0; i < tokens.length - 1; ++i) {
69+
final var x = tokens[i];
70+
for (var j = i + 1; j < tokens.length; ++j) {
71+
final var y = tokens[j];
72+
if (y.contains(x)) {
73+
subTokens.add(i);
74+
}
75+
}
76+
}
77+
78+
if (subTokens.isEmpty()) {
79+
return new TokenCut(tokens, null);
80+
} else {
81+
final var main = IntStream.range(0, tokens.length)
82+
.mapToObj(Integer::valueOf)
83+
.filter(Predicate.not(subTokens::contains))
84+
.map(i -> {
85+
return tokens[i];
86+
}).toArray(String[]::new);
87+
final var sub = subTokens.stream().map(i -> {
88+
return tokens[i];
89+
}).toArray(String[]::new);
90+
return new TokenCut(main, sub);
91+
}
92+
}
93+
94+
@Override
95+
public String toString() {
96+
return String.format("TokenCut(main=%s, sub=%s)", Arrays.toString(main), Arrays.toString(sub));
97+
}
98+
}
99+
100+
public TokenReplace(String... tokens) {
101+
tokens = TokenCut.orderTokens(tokens);
102+
103+
this.tokens = tokens;
104+
regexps = new ArrayList<>();
105+
106+
for(;;) {
107+
final var tokenCut = TokenCut.createFromOrderedTokens(tokens);
108+
regexps.add(Pattern.compile(Stream.of(tokenCut.main()).map(Pattern::quote).collect(joining("|", "(", ")"))));
109+
110+
if (tokenCut.sub() == null) {
111+
break;
112+
}
113+
114+
tokens = tokenCut.sub();
115+
}
116+
}
117+
118+
public String applyTo(String str, Function<String, Object> tokenValueSupplier) {
119+
Objects.requireNonNull(str);
120+
Objects.requireNonNull(tokenValueSupplier);
121+
for (final var regexp : regexps) {
122+
str = regexp.matcher(str).replaceAll(mr -> {
123+
final var token = mr.group();
124+
return Matcher.quoteReplacement(Objects.requireNonNull(tokenValueSupplier.apply(token), () -> {
125+
return String.format("Null value for token [%s]", token);
126+
}).toString());
127+
});
128+
}
129+
return str;
130+
}
131+
132+
public String recursiveApplyTo(String str, Function<String, Object> tokenValueSupplier) {
133+
String newStr;
134+
int counter = tokens.length + 1;
135+
while (!(newStr = applyTo(str, tokenValueSupplier)).equals(str)) {
136+
str = newStr;
137+
if (counter-- == 0) {
138+
throw new IllegalStateException("Infinite recursion");
139+
}
140+
}
141+
return newStr;
142+
}
143+
144+
@Override
145+
public int hashCode() {
146+
// Auto generated code
147+
final int prime = 31;
148+
int result = 1;
149+
result = prime * result + Arrays.hashCode(tokens);
150+
return result;
151+
}
152+
153+
@Override
154+
public boolean equals(Object obj) {
155+
// Auto generated code
156+
if (this == obj)
157+
return true;
158+
if (obj == null)
159+
return false;
160+
if (getClass() != obj.getClass())
161+
return false;
162+
TokenReplace other = (TokenReplace) obj;
163+
return Arrays.equals(tokens, other.tokens);
164+
}
165+
166+
@Override
167+
public String toString() {
168+
return "TokenReplace(" + String.join("|", tokens) + ")";
169+
}
170+
171+
public static TokenReplace combine(TokenReplace x, TokenReplace y) {
172+
return new TokenReplace(Stream.of(x.tokens, y.tokens).flatMap(Stream::of).toArray(String[]::new));
173+
}
174+
175+
public static Function<String, Object> createCachingTokenValueSupplier(Map<String, Supplier<Object>> tokenValueSuppliers) {
176+
Objects.requireNonNull(tokenValueSuppliers);
177+
final Map<String, Object> cache = new HashMap<>();
178+
return token -> {
179+
final var value = cache.computeIfAbsent(token, k -> {
180+
final var tokenValueSupplier = Objects.requireNonNull(tokenValueSuppliers.get(token), () -> {
181+
return String.format("No token value supplier for token [%s]", token);
182+
});
183+
return Optional.ofNullable(tokenValueSupplier.get()).orElse(NULL_SUPPLIED);
184+
});
185+
186+
if (value == NULL_SUPPLIED) {
187+
throw new NullPointerException(String.format("Null value for token [%s]", token));
188+
}
189+
190+
return value;
191+
};
192+
}
193+
194+
private final String[] tokens;
195+
private final transient List<Pattern> regexps;
196+
private final static Object NULL_SUPPLIED = new Object();
197+
}

0 commit comments

Comments
 (0)