/*
* Copyright (c) 2001-2017, Inversoft Inc., All Rights Reserved
*
* 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.primeframework.mvc.parameter.el;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.primeframework.mvc.config.MVCConfiguration;
import org.primeframework.mvc.parameter.convert.ConverterProvider;
import org.primeframework.mvc.util.ReflectionUtils;
/**
* This class provides member access.
*
* @author Brian Pontarelli
*/
public class MemberAccessor extends Accessor {
final Field field;
final ReflectionUtils.PropertyInfo propertyInfo;
private final List<Class<? extends Annotation>> unWrappedAnnotations;
public MemberAccessor(ConverterProvider converterProvider, MemberAccessor accessor, MVCConfiguration configuration) {
super(converterProvider, accessor);
this.field = accessor.field;
this.propertyInfo = accessor.propertyInfo;
if (configuration != null) {
this.unWrappedAnnotations = configuration.unwrapAnnotations();
} else {
this.unWrappedAnnotations = Collections.emptyList();
}
}
public MemberAccessor(ConverterProvider converterProvider, Class<?> declaringClass, String name, String expression, MVCConfiguration configuration) {
super(converterProvider);
if (configuration != null) {
this.unWrappedAnnotations = configuration.unwrapAnnotations();
} else {
this.unWrappedAnnotations = Collections.emptyList();
}
this.declaringClass = declaringClass;
Map<String, ReflectionUtils.PropertyInfo> properties = ReflectionUtils.findPropertyInfo(this.declaringClass);
ReflectionUtils.PropertyInfo bpi = properties.get(name);
if (bpi == null) {
Map<String, Field> fields = findFields();
this.propertyInfo = null;
this.field = fields.get(name);
} else {
this.propertyInfo = bpi;
this.field = null;
}
if (this.field == null && this.propertyInfo == null) {
throw new MissingPropertyExpressionException("While evaluating the expression [" + expression + "]. The property/field [" +
name + "] does not exist in the class [" + declaringClass + "]", name, declaringClass, expression);
}
super.type = (bpi != null) ? bpi.getGenericType() : this.field.getGenericType();
}
public Object get(Expression expression) {
if (propertyInfo != null) {
Method getter = propertyInfo.getMethods().get("get");
if (getter == null) {
throw new ReadExpressionException("Missing getter for property [" + propertyInfo.getName() +
"] in class [" + declaringClass + "]");
}
return ReflectionUtils.invokeGetter(getter, this.object);
}
return getField();
}
/**
* @return Returns this.
*/
public MemberAccessor getMemberAccessor() {
return this;
}
public boolean isIndexed() {
return propertyInfo != null && propertyInfo.isIndexed();
}
public void set(Object value, Expression expression) {
if (propertyInfo != null) {
Method setter = propertyInfo.getMethods().get("set");
if (setter == null) {
throw new UpdateExpressionException("Missing setter for property [" + propertyInfo.getName() +
"] in class [" + declaringClass + "]");
}
ReflectionUtils.invokeSetter(setter, object, value);
} else {
setField(value, expression);
}
}
public void set(String[] values, Expression expression) {
set(convert(expression, field, values), expression);
}
@Override
public String toString() {
return (propertyInfo != null) ? propertyInfo.toString() : "Field [" + field.toString() + "] in class [" +
field.getDeclaringClass() + "]";
}
/**
* This first checks for the annotation on the method and then the field. If this member is a field it doesn't check
* for any getter or setter.
*
* @param type The annotation type.
* @return The annotation or null.
*/
@Override
protected <T extends Annotation> T getAnnotation(Class<T> type) {
if (propertyInfo != null) {
Map<String, Method> methods = propertyInfo.getMethods();
for (Method method : methods.values()) {
if (method.isAnnotationPresent(type)) {
return method.getAnnotation(type);
}
}
// Get the field for the property
String name = propertyInfo.getName();
Field field = ReflectionUtils.findFields(declaringClass).get(name);
if (field != null && field.isAnnotationPresent(type)) {
return field.getAnnotation(type);
}
}
if (field != null && field.isAnnotationPresent(type)) {
return field.getAnnotation(type);
}
return null;
}
@Override
protected Object newInstance(Object key, Class<?> clazz) throws IllegalAccessException, InstantiationException {
Object object = clazz.newInstance();
Set<String> fieldNames = ReflectionUtils.findFields(clazz).keySet();
// If the object contains the field name matching the provided key we're done. This is the normal case.
if (fieldNames.contains(key.toString())) {
return object;
} else {
// Otherwise if there is a nested field to unwrap, let's do that until we find it.
for (Field annotatedField : ReflectionUtils.findAllFieldsWithAnnotations(object.getClass(), unWrappedAnnotations)) {
Object thisField = annotatedField.get(object);
if (thisField == null) {
annotatedField.set(object, newInstance(key, annotatedField.getType()));
}
}
}
return object;
}
/**
* Find the fields in the declaring class being aware that if any of those fields are annotated with an annotation indicating it should be
* unwrapped - we should ignore that field, and instead add the fields that belong to that object.
*
* @return the fields found keyed by the field name.
*/
private Map<String, Field> findFields() {
Map<String, Field> fields = new HashMap<>();
try {
for (Map.Entry<String, Field> entry : ReflectionUtils.findFields(this.declaringClass).entrySet()) {
if (ReflectionUtils.areAnyAnnotationsPresent(entry.getValue(), unWrappedAnnotations)) {
Field unwrappedField = declaringClass.getField(entry.getKey());
fields.putAll(ReflectionUtils.findFields(unwrappedField.getType()));
} else {
fields.put(entry.getKey(), entry.getValue());
}
}
} catch (NoSuchFieldException ignore) {
}
return fields;
}
/**
* Return the field for the object being aware that the field may be nested inside of another object annotated with an annotation
* indicating it should be unwrapped.
*
* @return the field object.
*/
private Object getField() {
if (field.getDeclaringClass().isAssignableFrom(this.object.getClass())) {
return ReflectionUtils.getField(field, this.object);
}
try {
for (Field f : ReflectionUtils.findAllFieldsWithAnnotations(this.object.getClass(), unWrappedAnnotations)) {
if (f.getType().equals(field.getDeclaringClass())) {
return ReflectionUtils.getField(field, f.get(this.object));
}
}
} catch (IllegalAccessException ignore) {
}
return null;
}
/**
* Set the field in the object being aware that the field may be nested inside of another object annotated with an annotation indicating
* it should be unwrapped.
*
* @param value The value to set into the field.
* @param expression the current expression that was used to idenfity the field, used only for exception cases.
*/
private void setField(Object value, Expression expression) {
// Normal case, the field is found in the object.
if (field.getDeclaringClass().isAssignableFrom(this.object.getClass())) {
ReflectionUtils.setField(field, object, value);
return;
}
// Declaring class doesn't match up with the object, look for unwrapped fields, the field may be nested.
for (Field f : ReflectionUtils.findAllFieldsWithAnnotations(this.object.getClass(), unWrappedAnnotations)) {
if (f.getType().equals(field.getDeclaringClass())) {
try {
ReflectionUtils.setField(field, f.get(object), value);
} catch (IllegalAccessException e) {
throw new UpdateExpressionException("Unexpected failure setting expression [" + expression.getExpression() + "]", e);
}
}
}
}
}