/
ImportConstants.java
213 lines (179 loc) · 7.56 KB
/
ImportConstants.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*
* Copyright 2019 OmniFaces
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.taghandler;
import static java.lang.Math.max;
import static java.lang.String.format;
import static org.omnifaces.taghandler.ImportFunctions.toClass;
import static org.omnifaces.util.Facelets.getStringLiteral;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.faces.component.UIComponent;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagConfig;
import javax.faces.view.facelets.TagHandler;
import org.omnifaces.util.MapWrapper;
/**
* <p>
* The <code><o:importConstants></code> taghandler allows the developer to have a mapping of all constant field
* values of the given fully qualified name of a type in the request scope. The constant field values are those public
* static final fields. This works for classes, interfaces and enums.
*
* <h3>Usage</h3>
* <p>
* For example:
* <pre>
* public class Foo {
* public static final String FOO1 = "foo1";
* public static final String FOO2 = "foo2";
* }
*
* public interface Bar {
* public String BAR1 = "bar1";
* public String BAR2 = "bar2";
* }
*
* public enum Baz {
* BAZ1, BAZ2;
* }
* </pre>
* <p>The constant field values of the above types can be mapped into the request scope as follows:
* <pre>
* <o:importConstants type="com.example.Foo" />
* <o:importConstants type="com.example.Bar" />
* <o:importConstants type="com.example.Baz" var="Bazzz" />
* ...
* #{Foo.FOO1}, #{Foo.FOO2}, #{Bar.BAR1}, #{Bar.BAR2}, #{Bazzz.BAZ1}, #{Bazzz.BAZ2}
* </pre>
* <p>The map is by default stored in the request scope by the simple name of the type as variable name. You can override
* this by explicitly specifying the <code>var</code> attribute, as demonstrated for <code>com.example.Baz</code> in
* the above example.
* <p>
* The resolved constants are by reference stored in the cache to improve retrieving performance. There is also a
* runtime (no, not compiletime as that's just not possible in EL) check during retrieving the constant value.
* If a constant value doesn't exist, then an <code>IllegalArgumentException</code> will be thrown.
*
* <h3>JSF 2.3</h3>
* <p>
* JSF 2.3 also offers a <code><f:importConstants></code>, however it requires being placed in
* <code><f:metadata></code> which may not be appropriate when you intend to import constants only from
* a include, tagfile or a composite component.
*
* @author Bauke Scholtz
*/
public class ImportConstants extends TagHandler {
// Constants ------------------------------------------------------------------------------------------------------
private static final Map<String, Map<String, Object>> CONSTANTS_CACHE = new ConcurrentHashMap<>();
private static final String ERROR_FIELD_ACCESS = "Cannot access constant field '%s' of type '%s'.";
private static final String ERROR_INVALID_CONSTANT = "Type '%s' does not have the constant '%s'.";
// Variables ------------------------------------------------------------------------------------------------------
private String varValue;
private TagAttribute typeAttribute;
// Constructors ---------------------------------------------------------------------------------------------------
/**
* The tag constructor.
* @param config The tag config.
*/
public ImportConstants(TagConfig config) {
super(config);
varValue = getStringLiteral(getAttribute("var"), "var");
typeAttribute = getRequiredAttribute("type");
}
// Actions --------------------------------------------------------------------------------------------------------
/**
* First obtain the constants of the class by its fully qualified name as specified in the <code>type</code>
* attribute from the cache. If it hasn't been collected yet and is thus not present in the cache, then collect
* them and store in cache. Finally set the constants in the request scope by the simple name of the type, or by the
* name as specified in the <code>var</code> attribute, if any.
*/
@Override
public void apply(FaceletContext context, UIComponent parent) throws IOException {
String type = typeAttribute.getValue(context);
Map<String, Object> constants = CONSTANTS_CACHE.get(type);
if (constants == null) {
constants = collectConstants(type);
CONSTANTS_CACHE.put(type, constants);
}
String var = varValue;
if (var == null) {
int innerClass = type.lastIndexOf('$');
int outerClass = type.lastIndexOf('.');
var = type.substring(max(innerClass, outerClass) + 1);
}
context.setAttribute(var, constants);
}
// Helpers --------------------------------------------------------------------------------------------------------
/**
* Collect constants of the given type. That are, all public static final fields of the given type.
* @param type The fully qualified name of the type to collect constants for.
* @return Constants of the given type.
*/
private static Map<String, Object> collectConstants(String type) {
Map<String, Object> constants = new LinkedHashMap<>();
for (Field field : toClass(type).getFields()) {
if (isPublicStaticFinal(field)) {
try {
constants.put(field.getName(), field.get(null));
}
catch (Exception e) {
throw new IllegalArgumentException(format(ERROR_FIELD_ACCESS, type, field.getName()), e);
}
}
}
return new ConstantsMap(constants, type);
}
/**
* Returns whether the given field is a constant field, that is when it is public, static and final.
* @param field The field to be checked.
* @return <code>true</code> if the given field is a constant field, otherwise <code>false</code>.
*/
private static boolean isPublicStaticFinal(Field field) {
int modifiers = field.getModifiers();
return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers);
}
// Nested classes -------------------------------------------------------------------------------------------------
/**
* Specific map implementation which wraps the given map in {@link Collections#unmodifiableMap(Map)} and throws an
* {@link IllegalArgumentException} in {@link ConstantsMap#get(Object)} method when the key doesn't exist at all.
*
* @author Bauke Scholtz
*/
private static class ConstantsMap extends MapWrapper<String, Object> {
private static final long serialVersionUID = 1L;
private String type;
public ConstantsMap(Map<String, Object> map, String type) {
super(Collections.unmodifiableMap(map));
this.type = type;
}
@Override
public Object get(Object key) {
if (!containsKey(key)) {
throw new IllegalArgumentException(format(ERROR_INVALID_CONSTANT, type, key));
}
return super.get(key);
}
@Override
public boolean equals(Object object) {
return super.equals(object) && type.equals(((ConstantsMap) object).type);
}
@Override
public int hashCode() {
return super.hashCode() + type.hashCode();
}
}
}