• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aspectran / aspectran / #4075

19 Feb 2025 12:58PM CUT coverage: 35.181% (-0.004%) from 35.185%
#4075

push

github

topframe
Update

20 of 155 new or added lines in 9 files covered. (12.9%)

5 existing lines in 3 files now uncovered.

14252 of 40510 relevant lines covered (35.18%)

0.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.98
/core/src/main/java/com/aspectran/core/context/asel/token/Token.java
1
/*
2
 * Copyright (c) 2008-2025 The Aspectran Project
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
 *     http://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 com.aspectran.core.context.asel.token;
17

18
import com.aspectran.core.context.rule.ability.BeanReferenceable;
19
import com.aspectran.core.context.rule.ability.Replicable;
20
import com.aspectran.core.context.rule.type.BeanRefererType;
21
import com.aspectran.core.context.rule.type.TokenDirectiveType;
22
import com.aspectran.core.context.rule.type.TokenType;
23
import com.aspectran.utils.ToStringBuilder;
24
import com.aspectran.utils.annotation.jsr305.NonNull;
25

26
import java.lang.reflect.Field;
27
import java.lang.reflect.Method;
28

29
/**
30
 * A token has a string value of its own or contains information
31
 * for fetching a specific value from another provider.
32
 *
33
 * <p>The following symbols are used to distinguish the providers of values:</p>
34
 * <dl>
35
 *     <dt>#</dt>
36
 *     <dd><p>Refers to a bean specified by the Bean Registry.</p>
37
 *         ex)
38
 *         <ul>
39
 *         <li>#{beanId}
40
 *         <li>#{beanId^getterName}
41
 *         <li>#{beanId^getterName:defaultString}
42
 *         <li>#{class:className}
43
 *         <li>#{class:className^getterName}
44
 *         <li>#{class:className^getterName:defaultString}
45
 *         </ul>
46
 *     </dd>
47
 *     <dt>~</dt>
48
 *     <dd><p>Refers to a string formatted from the Template Rule Registry.</p>
49
 *         ex)
50
 *         <ul>
51
 *         <li>~{templateId}
52
 *         <li>~{templateId:defaultString}
53
 *         </ul>
54
 *     </dd>
55
 *     <dt>$</dt>
56
 *     <dd><p>Refers to a parameter value.</p>
57
 *         ex)
58
 *         <ul>
59
 *         <li>${parameterName}
60
 *         <li>${parameterName:defaultString}
61
 *         </ul>
62
 *     </dd>
63
 *     <dt>@</dt>
64
 *     <dd><p>Refers to an attribute value.</p>
65
 *         ex)
66
 *         <ul>
67
 *         <li>{@literal @}{attributeName}
68
 *         <li>{@literal @}{attributeName:defaultString}
69
 *         <li>{@literal @}{attributeName^getterName:defaultString}
70
 *         </ul>
71
 *     </dd>
72
 *     <dt>%</dt>
73
 *     <dd><p>Refers to a property from the specified Properties file or environment variables.</p>
74
 *         ex)
75
 *         <ul>
76
 *         <li>%{environmentPropertyName}
77
 *         <li>%{classpath:propertiesPath^getterName}
78
 *         <li>%{classpath:propertiesPath^getterName:defaultString}
79
 *         </ul>
80
 *     </dd>
81
 * </dl>
82
 *
83
 * <p>Created: 2008. 03. 27 PM 10:20:06</p>
84
 */
85
public class Token implements BeanReferenceable, Replicable<Token> {
86

87
    private static final BeanRefererType BEAN_REFERER_TYPE = BeanRefererType.TOKEN;
1✔
88

89
    public static final char BEAN_SYMBOL = '#';
90

91
    public static final char PARAMETER_SYMBOL = '$';
92

93
    public static final char ATTRIBUTE_SYMBOL = '@';
94

95
    public static final char PROPERTY_SYMBOL = '%';
96

97
    public static final char TEMPLATE_SYMBOL = '~';
98

99
    public static final char BRACKET_OPEN = '{';
100

101
    public static final char BRACKET_CLOSE = '}';
102

103
    public static final char VALUE_DELIMITER = ':';
104

105
    public static final char GETTER_DELIMITER = '^';
106

107
    private final TokenType type;
108

109
    private TokenDirectiveType directiveType;
110

111
    private final String name;
112

113
    private String value;
114

115
    private Object valueProvider;
116

117
    private String getterName;
118

119
    private String defaultValue;
120

121
    /**
122
     * Instantiates a new Token.
123
     * @param defaultValue the default value
124
     */
125
    public Token(String defaultValue) {
1✔
126
        this.type = TokenType.TEXT;
1✔
127
        this.name = null;
1✔
128
        this.defaultValue = defaultValue;
1✔
129
    }
1✔
130

131
    /**
132
     * Instantiates a new Token.
133
     * @param type the token type
134
     * @param name the token name
135
     */
136
    public Token(TokenType type, String name) {
1✔
137
        if (type == TokenType.TEXT) {
1✔
138
            throw new UnsupportedOperationException();
×
139
        }
140
        if (type == null) {
1✔
141
            throw new IllegalArgumentException("type must not be null");
×
142
        }
143
        if (name == null) {
1✔
144
            throw new IllegalArgumentException("name must not be null");
×
145
        }
146
        this.type = type;
1✔
147
        this.name = name;
1✔
148
    }
1✔
149

150
    /**
151
     * Instantiates a new Token.
152
     * @param type the token type
153
     * @param directiveType the token directive type
154
     * @param value the token value
155
     */
156
    public Token(TokenType type, TokenDirectiveType directiveType, String value) {
1✔
157
        if (type == null) {
1✔
158
            throw new IllegalArgumentException("type must not be null");
×
159
        }
160
        if (directiveType == null) {
1✔
161
            throw new IllegalArgumentException("directiveType must not be null");
×
162
        }
163
        this.type = type;
1✔
164
        this.directiveType = directiveType;
1✔
165
        this.name = directiveType.toString();
1✔
166
        this.value = value;
1✔
167
    }
1✔
168

169
    /**
170
     * Gets the token type.
171
     * @return the token type
172
     */
173
    public TokenType getType() {
174
        return type;
1✔
175
    }
176

177
    /**
178
     * Gets the token directive type.
179
     * @return the token directive type
180
     * @see TokenDirectiveType
181
     */
182
    public TokenDirectiveType getDirectiveType() {
183
        return directiveType;
1✔
184
    }
185

186
    /**
187
     * Gets the token name.
188
     * @return the token name
189
     */
190
    public String getName() {
191
        return name;
1✔
192
    }
193

194
    /**
195
     * Returns the class name of the bean or the classpath of the Properties file,
196
     * depending on the type of token.
197
     * For example, if the token type is "bean" and the token name is "class",
198
     * the value of the token is the class name of the bean.
199
     * Also, if the token type is "property" and the token name is "classpath",
200
     * the value of the token is the path to reference in the Properties file.
201
     * @return the default value or bean's class name
202
     */
203
    public String getValue() {
204
        return value;
1✔
205
    }
206

207
    /**
208
     * Sets the class name of the bean or the classpath of the Properties file,
209
     * depending on the type of the token.
210
     * @param value the class name of the bean or the classpath of the Properties file
211
     */
212
    public void setValue(String value) {
213
        this.value = value;
×
214
    }
×
215

216
    /**
217
     * Gets the name of the property whose value is to be retrieved.
218
     * @return the name of the property whose value is to be retrieved
219
     */
220
    public String getGetterName() {
221
        return getterName;
1✔
222
    }
223

224
    /**
225
     * Gets an object that is able to provide the values of a token.
226
     * It could be a field, a method or a class, depending on the directive type.
227
     * @return the alternative value
228
     */
229
    public Object getValueProvider() {
230
        return valueProvider;
1✔
231
    }
232

233
    /**
234
     * Sets an object that is able to provide the values of a token.
235
     * It could be a field, a method or a class, depending on the directive type.
236
     * @param valueProvider the value provider
237
     */
238
    public void setValueProvider(Object valueProvider) {
239
        this.valueProvider = valueProvider;
1✔
240
    }
1✔
241

242
    /**
243
     * Sets the name of the property whose value is to be retrieved.
244
     * @param getterName the name of the property whose value is to be retrieved
245
     */
246
    public void setGetterName(String getterName) {
247
        this.getterName = getterName;
1✔
248
    }
1✔
249

250
    /**
251
     * Gets the default value.
252
     * @return the default value
253
     */
254
    public String getDefaultValue() {
255
        return defaultValue;
1✔
256
    }
257

258
    /**
259
     * Sets the default value.
260
     * @param defaultValue the new default value
261
     */
262
    public void setDefaultValue(String defaultValue) {
263
        this.defaultValue = defaultValue;
1✔
264
    }
1✔
265

266
    @Override
267
    public BeanRefererType getBeanRefererType() {
268
        return BEAN_REFERER_TYPE;
1✔
269
    }
270

271
    /**
272
     * Convert a Token object into a string.
273
     * @return a string representation of the token
274
     */
275
    public String stringify() {
276
        if (type == TokenType.TEXT) {
1✔
277
            return defaultValue;
1✔
278
        }
279
        StringBuilder sb = new StringBuilder();
1✔
280
        if (type == TokenType.BEAN) {
1✔
281
            sb.append(BEAN_SYMBOL);
1✔
282
            sb.append(BRACKET_OPEN);
1✔
283
            if (name != null) {
1✔
284
                sb.append(name);
1✔
285
            }
286
            if (value != null) {
1✔
287
                sb.append(VALUE_DELIMITER);
1✔
288
                sb.append(value);
1✔
289
            }
290
            if (getterName != null) {
1✔
291
                sb.append(GETTER_DELIMITER);
1✔
292
                sb.append(getterName);
1✔
293
            }
294
        } else if (type == TokenType.TEMPLATE) {
1✔
295
            sb.append(TEMPLATE_SYMBOL);
1✔
296
            sb.append(BRACKET_OPEN);
1✔
297
            if (name != null) {
1✔
298
                sb.append(name);
1✔
299
            }
300
        } else if (type == TokenType.PARAMETER) {
1✔
301
            sb.append(PARAMETER_SYMBOL);
1✔
302
            sb.append(BRACKET_OPEN);
1✔
303
            if (name != null) {
1✔
304
                sb.append(name);
1✔
305
            }
306
        } else if (type == TokenType.ATTRIBUTE) {
1✔
307
            sb.append(ATTRIBUTE_SYMBOL);
1✔
308
            sb.append(BRACKET_OPEN);
1✔
309
            if (name != null) {
1✔
310
                sb.append(name);
1✔
311
            }
312
            if (getterName != null) {
1✔
313
                sb.append(GETTER_DELIMITER);
1✔
314
                sb.append(getterName);
1✔
315
            }
316
        } else if (type == TokenType.PROPERTY) {
1✔
317
            sb.append(PROPERTY_SYMBOL);
1✔
318
            sb.append(BRACKET_OPEN);
1✔
319
            if (name != null) {
1✔
320
                sb.append(name);
1✔
321
            }
322
            if (value != null) {
1✔
323
                sb.append(VALUE_DELIMITER);
1✔
324
                sb.append(value);
1✔
325
            }
326
            if (getterName != null) {
1✔
327
                sb.append(GETTER_DELIMITER);
1✔
328
                sb.append(getterName);
1✔
329
            }
330
        } else {
331
            throw new InvalidTokenException("Unknown token type: " + type, this);
×
332
        }
333
        if (defaultValue != null) {
1✔
334
            sb.append(VALUE_DELIMITER);
1✔
335
            sb.append(defaultValue);
1✔
336
        }
337
        sb.append(BRACKET_CLOSE);
1✔
338
        return sb.toString();
1✔
339
    }
340

341
    @Override
342
    public boolean equals(Object token) {
343
        return (this == token || (token instanceof Token that && deepEquals(that)));
1✔
344
    }
345

346
    private boolean deepEquals(@NonNull Token token) {
347
        if (type != token.getType()) {
1✔
348
            return false;
×
349
        }
350
        if (name != null) {
1✔
351
            if (!name.equals(token.getName())) {
1✔
352
                return false;
×
353
            }
354
        } else if (token.getName() != null) {
×
355
            return false;
×
356
        }
357
        if (value != null) {
1✔
358
            if (!value.equals(token.getValue())) {
1✔
359
                return false;
×
360
            }
361
        } else if (token.getValue() != null) {
1✔
362
            return false;
×
363
        }
364
        if (getterName != null) {
1✔
365
            if (!getterName.equals(token.getGetterName())) {
1✔
366
                return false;
×
367
            }
368
        } else if (token.getGetterName() != null) {
1✔
369
            return false;
×
370
        }
371
        if (defaultValue == null) {
1✔
372
            return (token.getDefaultValue() == null);
1✔
373
        } else {
374
            return defaultValue.equals(token.getDefaultValue());
1✔
375
        }
376
    }
377

378
    @Override
379
    public int hashCode() {
380
        final int prime = 31;
1✔
381
        int result = 7;
1✔
382
        result = prime * result + type.hashCode();
1✔
383
        result = prime * result + (name != null ? name.hashCode() : 0);
1✔
384
        result = prime * result + (value != null ? value.hashCode() : 0);
1✔
385
        result = prime * result + (getterName != null ? getterName.hashCode() : 0);
1✔
386
        result = prime * result + (defaultValue != null ? defaultValue.hashCode() : 0);
1✔
387
        return result;
1✔
388
    }
389

390
    @Override
391
    public Token replicate() {
392
        Token token;
393
        if (directiveType != null) {
1✔
394
            token = new Token(type, directiveType, value);
1✔
395
            token.setValueProvider(valueProvider);
1✔
396
            token.setGetterName(getterName);
1✔
397
            token.setDefaultValue(defaultValue);
1✔
398
        } else {
399
            if (type == TokenType.TEXT) {
1✔
400
                token = new Token(defaultValue);
×
401
            } else {
402
                token = new Token(type, name);
1✔
403
                token.setGetterName(getterName);
1✔
404
                token.setDefaultValue(defaultValue);
1✔
405
            }
406
        }
407
        return token;
1✔
408
    }
409

410
    public Token[] replicate(Token[] tokens) {
411
        if (tokens == null) {
×
412
            return null;
×
413
        }
414
        Token[] newTokens = new Token[tokens.length];
×
415
        for (int i = 0; i < tokens.length; i++) {
×
416
            newTokens[i] = tokens[i].replicate();
×
417
        }
418
        return newTokens;
×
419
    }
420

421
    @Override
422
    public String toString() {
423
        ToStringBuilder tsb = new ToStringBuilder();
1✔
424
        tsb.append("type", type);
1✔
425
        tsb.append("name", name);
1✔
426
        tsb.append("value", value);
1✔
427
        tsb.append("getterName", getterName);
1✔
428
        tsb.append("defaultValue", defaultValue);
1✔
429
        return tsb.toString();
1✔
430
    }
431

432
    /**
433
     * Returns whether a specified character is the token symbol.
434
     * @param c a character
435
     * @return true, if a specified character is one of the token symbols
436
     */
437
    public static boolean isTokenSymbol(char c) {
438
        return (c == BEAN_SYMBOL
1✔
439
                || c == TEMPLATE_SYMBOL
440
                || c == PARAMETER_SYMBOL
441
                || c == ATTRIBUTE_SYMBOL
442
                || c == PROPERTY_SYMBOL);
443
    }
444

445
    public static boolean hasToken(@NonNull String expression) {
446
        char[] ca = expression.toCharArray();
1✔
447
        boolean open = false;
1✔
448
        for (int i = 1; i < ca.length; i++) {
1✔
449
            if (isTokenSymbol(ca[i - 1]) && ca[i] == BRACKET_OPEN) {
1✔
450
                i++;
1✔
451
                open = true;
1✔
452
            } else if (open && ca[i] == BRACKET_CLOSE) {
1✔
453
                return true;
1✔
454
            }
455
        }
456
        return false;
×
457
    }
458

459
    /**
460
     * Returns the token type for the specified character.
461
     * @param symbol the token symbol character
462
     * @return the token type
463
     */
464
    public static TokenType resolveTypeAsSymbol(char symbol) {
465
        TokenType type;
466
        if (symbol == Token.BEAN_SYMBOL) {
1✔
467
            type = TokenType.BEAN;
1✔
468
        } else if (symbol == Token.TEMPLATE_SYMBOL) {
1✔
469
            type = TokenType.TEMPLATE;
1✔
470
        } else if (symbol == Token.PARAMETER_SYMBOL) {
1✔
471
            type = TokenType.PARAMETER;
1✔
472
        } else if (symbol == Token.ATTRIBUTE_SYMBOL) {
1✔
473
            type = TokenType.ATTRIBUTE;
1✔
474
        } else if (symbol == Token.PROPERTY_SYMBOL) {
1✔
475
            type = TokenType.PROPERTY;
1✔
476
        } else {
477
            throw new IllegalArgumentException("Unknown token symbol: " + symbol);
×
478
        }
479
        return type;
1✔
480
    }
481

482
    public static void resolveValueProvider(Token token, ClassLoader classLoader) {
483
        if (token != null && token.getType() == TokenType.BEAN) {
×
484
            if (token.getDirectiveType() == TokenDirectiveType.FIELD) {
×
485
                if (token.getGetterName() == null) {
×
486
                    throw new InvalidTokenException("Target field name is unspecified token", token);
×
487
                }
488
                try {
489
                    Class<?> cls = classLoader.loadClass(token.getValue());
×
490
                    Field field = cls.getField(token.getGetterName());
×
NEW
491
                    token.setValueProvider(field);
×
492
                } catch (ClassNotFoundException e) {
×
493
                    throw new IllegalArgumentException("Unable to load class: " + token.getValue(), e);
×
494
                } catch (NoSuchFieldException e) {
×
495
                    throw new IllegalArgumentException("Could not access field: " + token.getGetterName(), e);
×
496
                }
×
497
            } else if (token.getDirectiveType() == TokenDirectiveType.METHOD) {
×
498
                if (token.getGetterName() == null) {
×
499
                    throw new InvalidTokenException("Target method name is unspecified token", token);
×
500
                }
501
                try {
502
                    Class<?> cls = classLoader.loadClass(token.getValue());
×
503
                    Method method = cls.getMethod(token.getGetterName());
×
NEW
504
                    token.setValueProvider(method);
×
505
                } catch (ClassNotFoundException e) {
×
506
                    throw new IllegalArgumentException("Unable to load class: " + token.getValue(), e);
×
507
                } catch (NoSuchMethodException e) {
×
508
                    throw new IllegalArgumentException("Could not access method: " + token.getGetterName(), e);
×
509
                }
×
510
            } else if (token.getDirectiveType() == TokenDirectiveType.CLASS) {
×
511
                try {
512
                    Class<?> cls = classLoader.loadClass(token.getValue());
×
NEW
513
                    token.setValueProvider(cls);
×
514
                } catch (ClassNotFoundException e) {
×
515
                    throw new IllegalArgumentException("Unable to load class: " + token.getValue(), e);
×
516
                }
×
517
            }
518
        }
519
    }
×
520

521
    public static String format(TokenType type, String expression) {
522
        if (type == null) {
×
523
            throw new IllegalArgumentException("Token type must not be null");
×
524
        }
525
        if (type == TokenType.TEXT) {
×
526
            return expression;
×
527
        }
528
        StringBuilder sb = new StringBuilder();
×
529
        switch (type) {
×
530
            case BEAN:
531
                sb.append(BEAN_SYMBOL);
×
532
                break;
×
533
            case PARAMETER:
534
                sb.append(PARAMETER_SYMBOL);
×
535
                break;
×
536
            case ATTRIBUTE:
537
                sb.append(ATTRIBUTE_SYMBOL);
×
538
                break;
×
539
            case PROPERTY:
540
                sb.append(PROPERTY_SYMBOL);
×
541
                break;
×
542
            case TEMPLATE:
543
                sb.append(TEMPLATE_SYMBOL);
×
544
                break;
×
545
            default:
546
                throw new IllegalArgumentException("Unknown token type: " + type);
×
547
        }
548
        sb.append(BRACKET_OPEN);
×
549
        if (expression != null) {
×
550
            sb.append(expression);
×
551
        }
552
        return sb.append(BRACKET_CLOSE).toString();
×
553
    }
554

555
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc