/
ArrayELResolver.java
320 lines (292 loc) · 14.4 KB
/
ArrayELResolver.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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
/*
* Copyright (c) 1997, 2024 Oracle and/or its affiliates and others.
* All rights reserved.
* Copyright 2004 The Apache Software Foundation
*
* 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 jakarta.el;
import java.lang.reflect.Array;
/**
* Defines property resolution behavior on arrays.
*
* <p>
* This resolver handles base objects that are Java language arrays. It accepts the case sensitive string {@code
* "length"} or any other object as a property and coerces that object into an integer index into the array. The
* resulting value is the value in the array at that index.
* </p>
*
* <p>
* This resolver can be constructed in read-only mode, which means that {@link #isReadOnly} will always return
* <code>true</code> and {@link #setValue} will always throw <code>PropertyNotWritableException</code>.
* </p>
*
* <p>
* <code>ELResolver</code>s are combined together using {@link CompositeELResolver}s, to define rich semantics for
* evaluating an expression. See the javadocs for {@link ELResolver} for details.
* </p>
*
* @see CompositeELResolver
* @see ELResolver
*
* @since Jakarta Server Pages 2.1
*/
public class ArrayELResolver extends ELResolver {
private static final String LENGTH_PROPERTY_NAME = "length";
/**
* Creates a new read/write <code>ArrayELResolver</code>.
*/
public ArrayELResolver() {
this.isReadOnly = false;
}
/**
* Creates a new <code>ArrayELResolver</code> whose read-only status is determined by the given parameter.
*
* @param isReadOnly <code>true</code> if this resolver cannot modify arrays; <code>false</code> otherwise.
*/
public ArrayELResolver(boolean isReadOnly) {
this.isReadOnly = isReadOnly;
}
/**
* If the base object is an array, returns the most general acceptable type for a value in this array.
*
* <p>
* If the base is a <code>array</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
* must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
* this method is called, the caller should ignore the return value.
* </p>
*
* <p>
* Assuming the base is an <code>array</code>, that this resolver was not constructed in read-only mode, and that
* the provided property can be coerced to a valid index for base array, this method will return
* <code>base.getClass().getComponentType()</code>, which is the most general type of component that can be stored
* at any given index in the array.
* </p>
*
* @param context The context of this evaluation.
* @param base The array to analyze. Only bases that are Java language arrays are handled by this resolver.
* @param property The case sensitive string {@code "length"} or the index of the element in the array to return the
* acceptable type for. If not the case sensitive string {@code "length"}, will be coerced into an integer and
* checked if it is a valid index for the array, but otherwise ignored by this resolver.
* @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
* the most general acceptable type which must be {@code null} if the either the property or the resolver is
* read-only; otherwise undefined
* @throws PropertyNotFoundException if the given index is out of bounds for this array.
* @throws NullPointerException if context is <code>null</code>
* @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
* exception must be included as the cause property of this exception, if available.
*/
@Override
public Class<?> getType(ELContext context, Object base, Object property) {
if (context == null) {
throw new NullPointerException();
}
if (base != null && base.getClass().isArray()) {
context.setPropertyResolved(true);
if (LENGTH_PROPERTY_NAME.equals(property)) {
return null;
}
int index = toInteger(property);
if (index < 0 || index >= Array.getLength(base)) {
throw new PropertyNotFoundException();
}
/*
* The resolver may have been created in read-only mode but the
* array and its elements will always be read-write.
*/
if (isReadOnly) {
return null;
}
return base.getClass().getComponentType();
}
return null;
}
/**
* If the base object is a Java language array, returns the length of the array or the value at the given index. If
* the {@code property} argument is the case sensitive string {@code "length"}, then the length of the array is
* returned. Otherwise, the {@code property} argument is coerced into an integer and used as the index of the value
* to be returned. If the coercion could not be performed, an {@code IllegalArgumentException} is thrown. If the
* index is out of bounds, {@code null} is returned.
*
* <p>
* If the base is a Java language array, the {@code propertyResolved} property of the {@code ELContext} object must
* be set to {@code true} by this resolver, before returning. If this property is not {@code true} after this method
* is called, the caller should ignore the return value.
* </p>
*
* @param context The context of this evaluation.
* @param base The array to analyze. Only bases that are Java language arrays are handled by this resolver.
* @param property Either the string {@code "length"} or the index of the value to be returned. An index value will
* be coerced into an integer.
* @return If the {@code propertyResolved} property of {@code ELContext} was set to {@code true}, then the length of
* the array, the value at the given index or {@code null} if the index was out of bounds. Otherwise, undefined.
* @throws IllegalArgumentException if the property was not the string {@code "length"} and could not be coerced
* into an integer.
* @throws NullPointerException if context is {@code null}.
* @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
* exception must be included as the cause property of this exception, if available.
*/
@Override
public Object getValue(ELContext context, Object base, Object property) {
if (context == null) {
throw new NullPointerException();
}
if (base != null && base.getClass().isArray()) {
context.setPropertyResolved(base, property);
if (LENGTH_PROPERTY_NAME.equals(property)) {
return Integer.valueOf(Array.getLength(base));
}
int index = toInteger(property);
if (index >= 0 && index < Array.getLength(base)) {
return Array.get(base, index);
}
}
return null;
}
/**
* If the base object is a Java language array and the property is not the case sensitive string {@code length},
* attempts to set the value at the given index with the given value. The index is specified by the
* <code>property</code> argument, and coerced into an integer. If the coercion could not be performed, an
* <code>IllegalArgumentException</code> is thrown. If the index is out of bounds, a
* <code>PropertyNotFoundException</code> is thrown.
*
* <p>
* If the base is a Java language array, the <code>propertyResolved</code> property of the <code>ELContext</code> object
* must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
* this method is called, the caller can safely assume no value was set.
* </p>
*
* <p>
* If this resolver was constructed in read-only mode or the property is the case sensitive string {@code length},
* this method will always throw <code>PropertyNotWritableException</code>.
* </p>
*
* @param context The context of this evaluation.
* @param base The array to be modified. Only bases that are Java language arrays are handled by this resolver.
* @param property The case sensitive string {@code length} or an object to coerce to an integer to provide the
* index of the value to be set.
* @param val The value to be set at the given index.
* @throws ClassCastException if the class of the specified element prevents it from being added to this array.
* @throws NullPointerException if context is <code>null</code>.
* @throws IllegalArgumentException if the property could not be coerced into an integer, or if some aspect of the
* specified element prevents it from being added to this array.
* @throws PropertyNotWritableException if this resolver was constructed in read-only mode or the property was the
* case sensitive string {@code length}.
* @throws PropertyNotFoundException if the given index is out of bounds for this array.
* @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
* exception must be included as the cause property of this exception, if available.
*/
@Override
public void setValue(ELContext context, Object base, Object property, Object val) {
if (context == null) {
throw new NullPointerException();
}
if (base != null && base.getClass().isArray()) {
context.setPropertyResolved(base, property);
if (isReadOnly || LENGTH_PROPERTY_NAME.equals(property)) {
throw new PropertyNotWritableException();
}
Class<?> type = base.getClass().getComponentType();
if (val != null && !type.isAssignableFrom(val.getClass())) {
throw new ClassCastException();
}
int index = toInteger(property);
if (index < 0 || index >= Array.getLength(base)) {
throw new PropertyNotFoundException();
}
Array.set(base, index, val);
}
}
/**
* If the base object is a Java language array, returns whether a call to {@link #setValue} will always fail.
*
* <p>
* If the base is a Java language array, the <code>propertyResolved</code> property of the <code>ELContext</code> object
* must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
* this method is called, the caller should ignore the return value.
* </p>
*
* <p>
* If this resolver was constructed in read-only mode or the property is the case sensitive string {@code length},
* this method will always return <code>true</code>. Otherwise, it returns <code>false</code>.
* </p>
*
* @param context The context of this evaluation.
* @param base The array to analyze. Only bases that are a Java language array are handled by this resolver.
* @param property The case sensitive string {@code length} or an object to coerce to an integer to provide the
* index to check if an attempt to call {@link #setValue} with that index will always fail.
* @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
* <code>true</code> if calling the <code>setValue</code> method will always fail or <code>false</code> if it is
* possible that such a call may succeed; otherwise undefined.
* @throws PropertyNotFoundException if the given index is out of bounds for this array.
* @throws NullPointerException if context is <code>null</code>
* @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
* exception must be included as the cause property of this exception, if available.
*/
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
if (context == null) {
throw new NullPointerException();
}
if (base != null && base.getClass().isArray()) {
context.setPropertyResolved(true);
if (LENGTH_PROPERTY_NAME.equals(property)) {
return true;
}
int index = toInteger(property);
if (index < 0 || index >= Array.getLength(base)) {
throw new PropertyNotFoundException();
}
}
return isReadOnly;
}
/**
* If the base object is a Java language array, returns the most general type that this resolver accepts for the
* <code>property</code> argument. Otherwise, returns <code>null</code>.
*
* <p>
* Assuming the base is an array, this method will always return <code>Integer.class</code>. This is because arrays
* accept integers for their index.
* </p>
*
* @param context The context of this evaluation.
* @param base The array to analyze. Only bases that are a Java language array are handled by this resolver.
* @return <code>null</code> if base is not a Java language array; otherwise <code>Integer.class</code>.
*/
@Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
if (base != null && base.getClass().isArray()) {
return Integer.class;
}
return null;
}
private int toInteger(Object p) {
if (p instanceof Integer) {
return ((Integer) p).intValue();
}
if (p instanceof Character) {
return ((Character) p).charValue();
}
if (p instanceof Boolean) {
return ((Boolean) p).booleanValue() ? 1 : 0;
}
if (p instanceof Number) {
return ((Number) p).intValue();
}
if (p instanceof String) {
return Integer.parseInt((String) p);
}
throw new IllegalArgumentException();
}
private boolean isReadOnly;
}