1+ /*
2+ * Copyright 2002-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+
17+ package io .jstach .ezkv .boot ;
18+
19+ import java .util .ArrayList ;
20+ import java .util .Arrays ;
21+ import java .util .Collections ;
22+ import java .util .LinkedHashSet ;
23+ import java .util .List ;
24+ import java .util .Set ;
25+ import java .util .StringTokenizer ;
26+ import java .util .function .Predicate ;
27+ import java .util .stream .Collectors ;
28+
29+ import org .jspecify .annotations .Nullable ;
30+
31+ /**
32+ * Internal parser used by {@link Profiles#of}.
33+ *
34+ * @author Phillip Webb
35+ * @author Sam Brannen
36+ * @since 5.1
37+ */
38+ final class ProfilesParser {
39+
40+ private ProfilesParser () {
41+ }
42+
43+ static Profiles parse (String ... expressions ) {
44+ if (expressions .length == 0 ) {
45+ throw new IllegalArgumentException ("Must specify at least one profile expression" );
46+ }
47+ Profiles [] parsed = new Profiles [expressions .length ];
48+ for (int i = 0 ; i < expressions .length ; i ++) {
49+ parsed [i ] = parseExpression (expressions [i ]);
50+ }
51+ return new ParsedProfiles (expressions , parsed );
52+ }
53+
54+ private static Profiles parseExpression (String expression ) {
55+ if (expression .isBlank ()) {
56+ throw new IllegalArgumentException ("Invalid profile expression [" + expression + "]: must contain text" );
57+ }
58+ StringTokenizer tokens = new StringTokenizer (expression , "()&|!" , true );
59+ return parseTokens (expression , tokens );
60+ }
61+
62+ private static Profiles parseTokens (String expression , StringTokenizer tokens ) {
63+ return parseTokens (expression , tokens , Context .NONE );
64+ }
65+
66+ private static Profiles parseTokens (String expression , StringTokenizer tokens , Context context ) {
67+ List <Profiles > elements = new ArrayList <>();
68+ Operator operator = null ;
69+ while (tokens .hasMoreTokens ()) {
70+ String token = tokens .nextToken ().trim ();
71+ if (token .isEmpty ()) {
72+ continue ;
73+ }
74+ switch (token ) {
75+ case "(" -> {
76+ Profiles contents = parseTokens (expression , tokens , Context .PARENTHESIS );
77+ if (context == Context .NEGATE ) {
78+ return contents ;
79+ }
80+ elements .add (contents );
81+ }
82+ case "&" -> {
83+ assertWellFormed (expression , operator == null || operator == Operator .AND );
84+ operator = Operator .AND ;
85+ }
86+ case "|" -> {
87+ assertWellFormed (expression , operator == null || operator == Operator .OR );
88+ operator = Operator .OR ;
89+ }
90+ case "!" -> elements .add (not (parseTokens (expression , tokens , Context .NEGATE )));
91+ case ")" -> {
92+ Profiles merged = merge (expression , elements , operator );
93+ if (context == Context .PARENTHESIS ) {
94+ return merged ;
95+ }
96+ elements .clear ();
97+ elements .add (merged );
98+ operator = null ;
99+ }
100+ default -> {
101+ Profiles value = equals (token );
102+ if (context == Context .NEGATE ) {
103+ return value ;
104+ }
105+ elements .add (value );
106+ }
107+ }
108+ }
109+ return merge (expression , elements , operator );
110+ }
111+
112+ private static Profiles merge (String expression , List <Profiles > elements , @ Nullable Operator operator ) {
113+ assertWellFormed (expression , !elements .isEmpty ());
114+ if (elements .size () == 1 ) {
115+ return elements .get (0 );
116+ }
117+ Profiles [] profiles = elements .toArray (new Profiles [0 ]);
118+ return (operator == Operator .AND ? and (profiles ) : or (profiles ));
119+ }
120+
121+ private static void assertWellFormed (String expression , boolean wellFormed ) {
122+ if (!wellFormed ) {
123+ throw new IllegalArgumentException ("Malformed profile expression [" + expression + "]" );
124+ }
125+ }
126+
127+ private static Profiles or (Profiles ... profiles ) {
128+ return activeProfile -> Arrays .stream (profiles ).anyMatch (isMatch (activeProfile ));
129+ }
130+
131+ private static Profiles and (Profiles ... profiles ) {
132+ return activeProfile -> Arrays .stream (profiles ).allMatch (isMatch (activeProfile ));
133+ }
134+
135+ private static Profiles not (Profiles profiles ) {
136+ return activeProfile -> !profiles .matches (activeProfile );
137+ }
138+
139+ private static Profiles equals (String profile ) {
140+ return activeProfile -> activeProfile .test (profile );
141+ }
142+
143+ private static Predicate <Profiles > isMatch (Predicate <String > activeProfiles ) {
144+ return profiles -> profiles .matches (activeProfiles );
145+ }
146+
147+ private enum Operator {
148+
149+ AND , OR
150+
151+ }
152+
153+ private enum Context {
154+
155+ NONE , NEGATE , PARENTHESIS
156+
157+ }
158+
159+ private static class ParsedProfiles implements Profiles {
160+
161+ private final Set <String > expressions = new LinkedHashSet <>();
162+
163+ private final Profiles [] parsed ;
164+
165+ ParsedProfiles (String [] expressions , Profiles [] parsed ) {
166+ Collections .addAll (this .expressions , expressions );
167+ this .parsed = parsed ;
168+ }
169+
170+ @ Override
171+ public boolean matches (Predicate <String > activeProfiles ) {
172+ for (Profiles candidate : this .parsed ) {
173+ if (candidate .matches (activeProfiles )) {
174+ return true ;
175+ }
176+ }
177+ return false ;
178+ }
179+
180+ @ Override
181+ public boolean equals (@ Nullable Object other ) {
182+ return (this == other
183+ || (other instanceof ParsedProfiles that && this .expressions .equals (that .expressions )));
184+ }
185+
186+ @ Override
187+ public int hashCode () {
188+ return this .expressions .hashCode ();
189+ }
190+
191+ @ Override
192+ public String toString () {
193+ if (this .expressions .size () == 1 ) {
194+ return this .expressions .iterator ().next ();
195+ }
196+ return this .expressions .stream ().map (this ::wrap ).collect (Collectors .joining (" | " ));
197+ }
198+
199+ private String wrap (String str ) {
200+ return "(" + str + ")" ;
201+ }
202+
203+ }
204+
205+ }
0 commit comments