// Indexing into a Map
if (targetObject instanceof Map) {
if (targetObject == null) {
// Current decision: attempt to index into null map == exception and does not just return null
throw new SpelEvaluationException(getStartPosition(),SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
}
Object possiblyConvertedKey = index;
if (targetObjectTypeDescriptor.isMapEntryTypeKnown()) {
possiblyConvertedKey = state.convertValue(index,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getMapKeyType()));
}
Object o = ((Map<?, ?>) targetObject).get(possiblyConvertedKey);
if (targetObjectTypeDescriptor.isMapEntryTypeKnown()) {
return new TypedValue(o, targetObjectTypeDescriptor.getMapValueTypeDescriptor());
} else {
return new TypedValue(o);
}
}
if (targetObject == null) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
}
// if the object is something that looks indexable by an integer, attempt to treat the index value as a number
if ((targetObject instanceof Collection ) || targetObject.getClass().isArray() || targetObject instanceof String) {
int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
if (targetObject.getClass().isArray()) {
return new TypedValue(accessArrayElement(targetObject, idx), targetObjectTypeDescriptor.getElementTypeDescriptor());
} else if (targetObject instanceof Collection) {
Collection c = (Collection) targetObject;
if (idx >= c.size()) {
if (!growCollection(state, targetObjectTypeDescriptor.getElementType(), idx, c)) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
}
}
int pos = 0;
for (Object o : c) {
if (pos == idx) {
return new TypedValue(o, targetObjectTypeDescriptor.getElementTypeDescriptor());
}
pos++;
}
} else if (targetObject instanceof String) {
String ctxString = (String) targetObject;
if (idx >= ctxString.length()) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.STRING_INDEX_OUT_OF_BOUNDS, ctxString.length(), idx);
}
return new TypedValue(String.valueOf(ctxString.charAt(idx)));
}
}
// Try and treat the index value as a property of the context object
// TODO could call the conversion service to convert the value to a String
if (indexValue.getTypeDescriptor().getType()==String.class) {
Class<?> targetObjectRuntimeClass = getObjectClass(targetObject);
String name = (String)indexValue.getValue();
EvaluationContext eContext = state.getEvaluationContext();
try {
if (cachedReadName!=null && cachedReadName.equals(name) && cachedReadTargetType!=null && cachedReadTargetType.equals(targetObjectRuntimeClass)) {
// it is OK to use the cached accessor
return cachedReadAccessor.read(eContext, targetObject, name);
}
List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry(targetObjectRuntimeClass, state);
if (accessorsToTry != null) {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canRead(eContext, targetObject, name)) {
if (accessor instanceof ReflectivePropertyAccessor) {
accessor = ((ReflectivePropertyAccessor)accessor).createOptimalAccessor(eContext, targetObject, name);
}
this.cachedReadAccessor = accessor;
this.cachedReadName = name;
this.cachedReadTargetType = targetObjectRuntimeClass;
return accessor.read(eContext, targetObject, name);
}
}
}
} catch (AccessException e) {
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.asString());
}
}
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.asString());
}