Package: BeanELResolver$SoftConcurrentHashMap
BeanELResolver$SoftConcurrentHashMap
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
BeanELResolver.SoftConcurrentHashMap() |
|
|
|
|
|
||||||||||||||||||||
cleanup() |
|
|
|
|
|
||||||||||||||||||||
get(Object) |
|
|
|
|
|
||||||||||||||||||||
put(Class, BeanELResolver.BeanProperties) |
|
|
|
|
|
||||||||||||||||||||
putIfAbsent(Class, BeanELResolver.BeanProperties) |
|
|
|
|
|
Coverage
1: /*
2: * Copyright (c) 1997, 2021 Oracle and/or its affiliates and others.
3: * All rights reserved.
4: * Copyright 2004 The Apache Software Foundation
5: *
6: * Licensed under the Apache License, Version 2.0 (the "License");
7: * you may not use this file except in compliance with the License.
8: * You may obtain a copy of the License at
9: *
10: * http://www.apache.org/licenses/LICENSE-2.0
11: *
12: * Unless required by applicable law or agreed to in writing, software
13: * distributed under the License is distributed on an "AS IS" BASIS,
14: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15: * See the License for the specific language governing permissions and
16: * limitations under the License.
17: */
18:
19: package jakarta.el;
20:
21: import static java.lang.Boolean.TRUE;
22: import static jakarta.el.ELUtil.getExceptionMessageString;
23:
24: import java.beans.BeanInfo;
25: import java.beans.FeatureDescriptor;
26: import java.beans.IntrospectionException;
27: import java.beans.Introspector;
28: import java.beans.PropertyDescriptor;
29: import java.lang.ref.ReferenceQueue;
30: import java.lang.ref.SoftReference;
31: import java.lang.reflect.InvocationTargetException;
32: import java.lang.reflect.Method;
33: import java.util.ArrayList;
34: import java.util.HashMap;
35: import java.util.Iterator;
36: import java.util.Map;
37: import java.util.concurrent.ConcurrentHashMap;
38:
39: /**
40: * Defines property resolution behavior on objects using the JavaBeans component architecture.
41: *
42: * <p>
43: * This resolver handles base objects of any type, as long as the base is not <code>null</code>. It accepts any object
44: * as a property or method, and coerces it to a string.
45: *
46: * <p>
47: * For property resolution, the property string is used to find a JavaBeans compliant property on the base object. The
48: * value is accessed using JavaBeans getters and setters.
49: * </p>
50: *
51: * <p>
52: * For method resolution, the method string is the name of the method in the bean. The parameter types can be optionally
53: * specified to identify the method. If the parameter types are not specified, the parameter objects are used in the
54: * method resolution.
55: * </p>
56: *
57: * <p>
58: * The JavaBeans specification predates the introduction of default method implementations defined on an interface. In
59: * addition to the JavaBeans specification requirements for looking up property getters, property setters and methods,
60: * this resolver also considers default methods and includes them in the results.
61: * </p>
62: *
63: * <p>
64: * This resolver can be constructed in read-only mode, which means that {@link #isReadOnly} will always return
65: * <code>true</code> and {@link #setValue} will always throw <code>PropertyNotWritableException</code>.
66: * </p>
67: *
68: * <p>
69: * <code>ELResolver</code>s are combined together using {@link CompositeELResolver}s, to define rich semantics for
70: * evaluating an expression. See the javadocs for {@link ELResolver} for details.
71: * </p>
72: *
73: * <p>
74: * Because this resolver handles base objects of any type, it should be placed near the end of a composite resolver.
75: * Otherwise, it will claim to have resolved a property before any resolvers that come after it get a chance to test if
76: * they can do so as well.
77: * </p>
78: *
79: * @see CompositeELResolver
80: * @see ELResolver
81: *
82: * @since Jakarta Server Pages 2.1
83: */
84: public class BeanELResolver extends ELResolver {
85:
86: static private class BPSoftReference extends SoftReference<BeanProperties> {
87: final Class<?> key;
88:
89: BPSoftReference(Class<?> key, BeanProperties beanProperties, ReferenceQueue<BeanProperties> refQ) {
90: super(beanProperties, refQ);
91: this.key = key;
92: }
93: }
94:
95: static private class SoftConcurrentHashMap extends ConcurrentHashMap<Class<?>, BeanProperties> {
96:
97: private static final long serialVersionUID = -178867497897782229L;
98: private static final int CACHE_INIT_SIZE = 1024;
99: private ConcurrentHashMap<Class<?>, BPSoftReference> map = new ConcurrentHashMap<>(CACHE_INIT_SIZE);
100: private ReferenceQueue<BeanProperties> refQ = new ReferenceQueue<>();
101:
102: // Remove map entries that have been placed on the queue by GC.
103: private void cleanup() {
104: BPSoftReference BPRef = null;
105:• while ((BPRef = (BPSoftReference) refQ.poll()) != null) {
106: map.remove(BPRef.key);
107: }
108: }
109:
110: @Override
111: public BeanProperties put(Class<?> key, BeanProperties value) {
112: cleanup();
113: BPSoftReference prev = map.put(key, new BPSoftReference(key, value, refQ));
114:• return prev == null ? null : prev.get();
115: }
116:
117: @Override
118: public BeanProperties putIfAbsent(Class<?> key, BeanProperties value) {
119: cleanup();
120: BPSoftReference prev = map.putIfAbsent(key, new BPSoftReference(key, value, refQ));
121:• return prev == null ? null : prev.get();
122: }
123:
124: @Override
125: public BeanProperties get(Object key) {
126: cleanup();
127: BPSoftReference BPRef = map.get(key);
128:• if (BPRef == null) {
129: return null;
130: }
131:• if (BPRef.get() == null) {
132: // value has been garbage collected, remove entry in map
133: map.remove(key);
134: return null;
135: }
136: return BPRef.get();
137: }
138: }
139:
140: private boolean isReadOnly;
141:
142: private static final SoftConcurrentHashMap properties = new SoftConcurrentHashMap();
143:
144: /*
145: * Defines a property for a bean.
146: */
147: final static class BeanProperty {
148:
149: private Method readMethod;
150: private Method writeMethod;
151: private PropertyDescriptor descriptor;
152:
153: public BeanProperty(Class<?> baseClass, PropertyDescriptor descriptor) {
154: this.descriptor = descriptor;
155: readMethod = ELUtil.getMethod(baseClass, descriptor.getReadMethod());
156: writeMethod = ELUtil.getMethod(baseClass, descriptor.getWriteMethod());
157: }
158:
159: public Class<?> getPropertyType() {
160: return descriptor.getPropertyType();
161: }
162:
163: public boolean isReadOnly() {
164: return getWriteMethod() == null;
165: }
166:
167: public Method getReadMethod() {
168: return readMethod;
169: }
170:
171: public Method getWriteMethod() {
172: return writeMethod;
173: }
174: }
175:
176: /*
177: * Defines the properties for a bean.
178: */
179: final static class BeanProperties {
180:
181: private final Map<String, BeanProperty> propertyMap = new HashMap<>();
182:
183: public BeanProperties(Class<?> baseClass) {
184: PropertyDescriptor[] descriptors;
185: try {
186: BeanInfo info = Introspector.getBeanInfo(baseClass);
187: descriptors = info.getPropertyDescriptors();
188: for (PropertyDescriptor descriptor : descriptors) {
189: propertyMap.put(descriptor.getName(), new BeanProperty(baseClass, descriptor));
190: }
191: /**
192: * Populating from any interfaces solves two distinct problems:
193: * 1. When running under a security manager, classes may be
194: * unaccessible but have accessible interfaces.
195: * 2. It enables default methods to be included.
196: */
197: populateFromInterfaces(baseClass, baseClass);
198: } catch (IntrospectionException ie) {
199: throw new ELException(ie);
200: }
201:
202: }
203:
204: private void populateFromInterfaces(Class<?> baseClass, Class<?> aClass) throws IntrospectionException {
205: Class<?> interfaces[] = aClass.getInterfaces();
206: if (interfaces.length > 0) {
207: for (Class<?> ifs : interfaces) {
208: BeanInfo info = Introspector.getBeanInfo(ifs);
209: PropertyDescriptor[] pds = info.getPropertyDescriptors();
210: for (PropertyDescriptor pd : pds) {
211: if (!this.propertyMap.containsKey(pd.getName())) {
212: this.propertyMap.put(pd.getName(), new BeanProperty(
213: baseClass, pd));
214: }
215: }
216: }
217: }
218: Class<?> superclass = aClass.getSuperclass();
219: if (superclass != null) {
220: populateFromInterfaces(baseClass, superclass);
221: }
222: }
223:
224: public BeanProperty getBeanProperty(String property) {
225: return propertyMap.get(property);
226: }
227: }
228:
229: /**
230: * Creates a new read/write <code>BeanELResolver</code>.
231: */
232: public BeanELResolver() {
233: this.isReadOnly = false;
234: }
235:
236: /**
237: * Creates a new <code>BeanELResolver</code> whose read-only status is determined by the given parameter.
238: *
239: * @param isReadOnly <code>true</code> if this resolver cannot modify beans; <code>false</code> otherwise.
240: */
241: public BeanELResolver(boolean isReadOnly) {
242: this.isReadOnly = isReadOnly;
243: }
244:
245: /**
246: * If the base object is not <code>null</code>, returns the most general acceptable type that can be set on this bean
247: * property.
248: *
249: * <p>
250: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
251: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
252: * this method is called, the caller should ignore the return value.
253: * </p>
254: *
255: * <p>
256: * The provided property will first be coerced to a <code>String</code>. If there is a <code>BeanInfoProperty</code> for
257: * this property and there were no errors retrieving it, the <code>propertyType</code> of the
258: * <code>propertyDescriptor</code> is returned. Otherwise, a <code>PropertyNotFoundException</code> is thrown.
259: * </p>
260: *
261: * @param context The context of this evaluation.
262: * @param base The bean to analyze.
263: * @param property The name of the property to analyze. Will be coerced to a <code>String</code>.
264: * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
265: * the most general acceptable type; otherwise undefined.
266: * @throws NullPointerException if context is <code>null</code>
267: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
268: * exist or is not readable.
269: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
270: * exception must be included as the cause property of this exception, if available.
271: */
272: @Override
273: public Class<?> getType(ELContext context, Object base, Object property) {
274: if (context == null) {
275: throw new NullPointerException();
276: }
277:
278: if (base == null || property == null) {
279: return null;
280: }
281:
282: BeanProperty beanProperty = getBeanProperty(context, base, property);
283: context.setPropertyResolved(true);
284: return beanProperty.getPropertyType();
285: }
286:
287: /**
288: * If the base object is not <code>null</code>, returns the current value of the given property on this bean.
289: *
290: * <p>
291: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
292: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
293: * this method is called, the caller should ignore the return value.
294: * </p>
295: *
296: * <p>
297: * The provided property name will first be coerced to a <code>String</code>. If the property is a readable property of
298: * the base object, as per the JavaBeans specification, then return the result of the getter call. If the getter throws
299: * an exception, it is propagated to the caller. If the property is not found or is not readable, a
300: * <code>PropertyNotFoundException</code> is thrown.
301: * </p>
302: *
303: * @param context The context of this evaluation.
304: * @param base The bean on which to get the property.
305: * @param property The name of the property to get. Will be coerced to a <code>String</code>.
306: * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
307: * the value of the given property. Otherwise, undefined.
308: * @throws NullPointerException if context is <code>null</code>.
309: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
310: * exist or is not readable.
311: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
312: * exception must be included as the cause property of this exception, if available.
313: */
314: @Override
315: public Object getValue(ELContext context, Object base, Object property) {
316: if (context == null) {
317: throw new NullPointerException();
318: }
319:
320: if (base == null || property == null) {
321: return null;
322: }
323:
324: Method method = getBeanProperty(context, base, property).getReadMethod();
325: if (method == null) {
326: throw new PropertyNotFoundException(
327: getExceptionMessageString(context, "propertyNotReadable", new Object[] { base.getClass().getName(), property.toString() }));
328: }
329:
330: Object value;
331: try {
332: value = method.invoke(base, new Object[0]);
333: context.setPropertyResolved(base, property);
334: } catch (ELException ex) {
335: throw ex;
336: } catch (InvocationTargetException ite) {
337: throw new ELException(ite.getCause());
338: } catch (Exception ex) {
339: throw new ELException(ex);
340: }
341:
342: return value;
343: }
344:
345: /**
346: * If the base object is not <code>null</code>, attempts to set the value of the given property on this bean.
347: *
348: * <p>
349: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
350: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
351: * this method is called, the caller can safely assume no value was set.
352: * </p>
353: *
354: * <p>
355: * If this resolver was constructed in read-only mode, this method will always throw
356: * <code>PropertyNotWritableException</code>.
357: * </p>
358: *
359: * <p>
360: * The provided property name will first be coerced to a <code>String</code>. If property is a writable property of
361: * <code>base</code> (as per the JavaBeans Specification), the setter method is called (passing <code>value</code>). If
362: * the property exists but does not have a setter, then a <code>PropertyNotFoundException</code> is thrown. If the
363: * property does not exist, a <code>PropertyNotFoundException</code> is thrown.
364: * </p>
365: *
366: * @param context The context of this evaluation.
367: * @param base The bean on which to set the property.
368: * @param property The name of the property to set. Will be coerced to a <code>String</code>.
369: * @param val The value to be associated with the specified key.
370: * @throws NullPointerException if context is <code>null</code>.
371: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
372: * exist.
373: * @throws PropertyNotWritableException if this resolver was constructed in read-only mode, or if there is no setter for
374: * the property.
375: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
376: * exception must be included as the cause property of this exception, if available.
377: */
378: @Override
379: public void setValue(ELContext context, Object base, Object property, Object val) {
380: if (context == null) {
381: throw new NullPointerException();
382: }
383:
384: if (base == null || property == null) {
385: return;
386: }
387:
388: if (isReadOnly) {
389: throw new PropertyNotWritableException(getExceptionMessageString(context, "resolverNotwritable", new Object[] { base.getClass().getName() }));
390: }
391:
392: Method method = getBeanProperty(context, base, property).getWriteMethod();
393: if (method == null) {
394: throw new PropertyNotWritableException(
395: getExceptionMessageString(context, "propertyNotWritable", new Object[] { base.getClass().getName(), property.toString() }));
396: }
397:
398: try {
399: method.invoke(base, new Object[] { val });
400: context.setPropertyResolved(base, property);
401: } catch (ELException ex) {
402: throw ex;
403: } catch (InvocationTargetException ite) {
404: throw new ELException(ite.getCause());
405: } catch (Exception ex) {
406: if (null == val) {
407: val = "null";
408: }
409: String message = getExceptionMessageString(context, "setPropertyFailed", new Object[] { property.toString(), base.getClass().getName(), val });
410: throw new ELException(message, ex);
411: }
412: }
413:
414: /**
415: * If the base object is not <code>null</code>, invoke the method, with the given parameters on this bean. The return
416: * value from the method is returned.
417: *
418: * <p>
419: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
420: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
421: * this method is called, the caller should ignore the return value.
422: * </p>
423: *
424: * <p>
425: * The provided method object will first be coerced to a <code>String</code>. The methods in the bean is then examined
426: * and an attempt will be made to select one for invocation. If no suitable can be found, a
427: * <code>MethodNotFoundException</code> is thrown.
428: *
429: * If the given paramTypes is not <code>null</code>, select the method with the given name and parameter types.
430: *
431: * Else select the method with the given name that has the same number of parameters. If there are more than one such
432: * method, the method selection process is undefined.
433: *
434: * Else select the method with the given name that takes a variable number of arguments.
435: *
436: * Note the resolution for overloaded methods will likely be clarified in a future version of the spec.
437: *
438: * The provide parameters are coerced to the corresponding parameter types of the method, and the method is then
439: * invoked.
440: *
441: * @param context The context of this evaluation.
442: * @param base The bean on which to invoke the method
443: * @param methodName The simple name of the method to invoke. Will be coerced to a <code>String</code>. If method is
444: * "<init>"or "<clinit>" a MethodNotFoundException is thrown.
445: * @param paramTypes An array of Class objects identifying the method's formal parameter types, in declared order. Use
446: * an empty array if the method has no parameters. Can be <code>null</code>, in which case the method's formal parameter
447: * types are assumed to be unknown.
448: * @param params The parameters to pass to the method, or <code>null</code> if no parameters.
449: * @return The result of the method invocation (<code>null</code> if the method has a <code>void</code> return type).
450: * @throws MethodNotFoundException if no suitable method can be found.
451: * @throws ELException if an exception was thrown while performing (base, method) resolution. The thrown exception must
452: * be included as the cause property of this exception, if available. If the exception thrown is an
453: * <code>InvocationTargetException</code>, extract its <code>cause</code> and pass it to the <code>ELException</code>
454: * constructor.
455: * @since Jakarta Expression Language 2.2
456: */
457: @Override
458: public Object invoke(ELContext context, Object base, Object methodName, Class<?>[] paramTypes, Object[] params) {
459: if (base == null || methodName == null) {
460: return null;
461: }
462:
463: Method method = ELUtil.findMethod(base.getClass(), methodName.toString(), paramTypes, params, false);
464:
465: for (Object param : params) {
466: // If the parameters is a LambdaExpression, set the ELContext
467: // for its evaluation
468: if (param instanceof LambdaExpression) {
469: ((LambdaExpression) param).setELContext(context);
470: }
471: }
472:
473: Object ret = ELUtil.invokeMethod(context, method, base, params);
474: context.setPropertyResolved(base, methodName);
475: return ret;
476: }
477:
478: /**
479: * If the base object is not <code>null</code>, returns whether a call to {@link #setValue} will always fail.
480: *
481: * <p>
482: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
483: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
484: * this method is called, the caller can safely assume no value was set.
485: * </p>
486: *
487: * <p>
488: * If this resolver was constructed in read-only mode, this method will always return <code>true</code>.
489: * </p>
490: *
491: * <p>
492: * The provided property name will first be coerced to a <code>String</code>. If property is a writable property of
493: * <code>base</code>, <code>false</code> is returned. If the property is found but is not writable, <code>true</code> is
494: * returned. If the property is not found, a <code>PropertyNotFoundException</code> is thrown.
495: * </p>
496: *
497: * @param context The context of this evaluation.
498: * @param base The bean to analyze.
499: * @param property The name of the property to analyzed. Will be coerced to a <code>String</code>.
500: * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
501: * <code>true</code> if calling the <code>setValue</code> method will always fail or <code>false</code> if it is
502: * possible that such a call may succeed; otherwise undefined.
503: * @throws NullPointerException if context is <code>null</code>
504: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
505: * exist.
506: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
507: * exception must be included as the cause property of this exception, if available.
508: */
509: @Override
510: public boolean isReadOnly(ELContext context, Object base, Object property) {
511: if (context == null) {
512: throw new NullPointerException();
513: }
514:
515: if (base == null || property == null) {
516: return false;
517: }
518:
519: context.setPropertyResolved(true);
520: if (isReadOnly) {
521: return true;
522: }
523:
524: return getBeanProperty(context, base, property).isReadOnly();
525: }
526:
527: /**
528: * If the base object is not <code>null</code>, returns an <code>Iterator</code> containing the set of JavaBeans
529: * properties available on the given object. Otherwise, returns <code>null</code>.
530: *
531: * <p>
532: * The <code>Iterator</code> returned must contain zero or more instances of {@link java.beans.FeatureDescriptor}. Each
533: * info object contains information about a property in the bean, as obtained by calling the
534: * <code>BeanInfo.getPropertyDescriptors</code> method. The <code>FeatureDescriptor</code> is initialized using the same
535: * fields as are present in the <code>PropertyDescriptor</code>, with the additional required named attributes
536: * "<code>type</code>" and "<code>resolvableAtDesignTime</code>" set as follows:
537: * <ul>
538: * <li>{@link ELResolver#TYPE} - The runtime type of the property, from
539: * <code>PropertyDescriptor.getPropertyType()</code>.</li>
540: * <li>{@link ELResolver#RESOLVABLE_AT_DESIGN_TIME} - <code>true</code>.</li>
541: * </ul>
542: *
543: *
544: * @param context The context of this evaluation.
545: * @param base The bean to analyze.
546: * @return An <code>Iterator</code> containing zero or more <code>FeatureDescriptor</code> objects, each representing a
547: * property on this bean, or <code>null</code> if the <code>base</code> object is <code>null</code>.
548: *
549: * @deprecated This method will be removed without replacement in EL 6.0
550: */
551: @Deprecated(forRemoval = true, since = "5.0")
552: @Override
553: public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
554: if (base == null) {
555: return null;
556: }
557:
558: BeanInfo info = null;
559: try {
560: info = Introspector.getBeanInfo(base.getClass());
561: } catch (Exception ex) {
562: }
563:
564: if (info == null) {
565: return null;
566: }
567:
568: ArrayList<FeatureDescriptor> featureDescriptors = new ArrayList<>(info.getPropertyDescriptors().length);
569: for (PropertyDescriptor propertyDescriptor : info.getPropertyDescriptors()) {
570: propertyDescriptor.setValue("type", propertyDescriptor.getPropertyType());
571: propertyDescriptor.setValue("resolvableAtDesignTime", TRUE);
572: featureDescriptors.add(propertyDescriptor);
573: }
574:
575: return featureDescriptors.iterator();
576: }
577:
578: /**
579: * If the base object is not <code>null</code>, returns the most general type that this resolver accepts for the
580: * <code>property</code> argument. Otherwise, returns <code>null</code>.
581: *
582: * <p>
583: * Assuming the base is not <code>null</code>, this method will always return <code>Object.class</code>. This is because
584: * any object is accepted as a key and is coerced into a string.
585: * </p>
586: *
587: * @param context The context of this evaluation.
588: * @param base The bean to analyze.
589: * @return <code>null</code> if base is <code>null</code> otherwise <code>Object.class</code>.
590: */
591: @Override
592: public Class<?> getCommonPropertyType(ELContext context, Object base) {
593: if (base == null) {
594: return null;
595: }
596:
597: return Object.class;
598: }
599:
600: private BeanProperty getBeanProperty(ELContext context, Object base, Object prop) {
601: String property = prop.toString();
602: Class<?> baseClass = base.getClass();
603:
604: BeanProperties beanProperties = properties.get(baseClass);
605: if (beanProperties == null) {
606: beanProperties = new BeanProperties(baseClass);
607: properties.put(baseClass, beanProperties);
608: }
609:
610: BeanProperty beanProperty = beanProperties.getBeanProperty(property);
611: if (beanProperty == null) {
612: throw new PropertyNotFoundException(getExceptionMessageString(context, "propertyNotFound", new Object[] { baseClass.getName(), property }));
613: }
614:
615: return beanProperty;
616: }
617: }