/* * 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.AccessibleObject; import java.lang.reflect.Array; import java.lang.reflect.Type; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.primeframework.mvc.parameter.convert.AnnotationConverter; import org.primeframework.mvc.parameter.convert.ConversionException; import org.primeframework.mvc.parameter.convert.ConverterProvider; import org.primeframework.mvc.parameter.convert.ConverterStateException; import org.primeframework.mvc.parameter.convert.GlobalConverter; import org.primeframework.mvc.parameter.convert.annotation.ConverterAnnotation; import org.primeframework.mvc.util.TypeTools; /** * This class provides the base accessor support. * * @author Brian Pontarelli */ public abstract class Accessor { protected final ConverterProvider converterProvider; protected Class<?> declaringClass; protected Object object; protected Type type; public Accessor(ConverterProvider converterProvider, Accessor accessor) { this.converterProvider = converterProvider; this.type = accessor.type; this.declaringClass = accessor.declaringClass; } protected Accessor(ConverterProvider converterProvider) { this.converterProvider = converterProvider; } public final Object get(Object object, Expression expression) { this.object = object; return get(expression); } /** * @return Returns the member accessor that is closest to the current atom in the expression. If the current atom is a * member, this should just return <strong>this</strong>. If the current atom is a collection for example, * this would return the member that the collection was retrieved from. */ public abstract MemberAccessor getMemberAccessor(); public abstract boolean isIndexed(); public final void set(Object object, Object value, Expression expression) { this.object = object; set(value, expression); } public final void set(Object object, String[] values, Expression expression) { this.object = object; set(values, expression); } @Override public String toString() { return "declaring class [" + declaringClass + "]"; } /** * After the object is originally get or set, this method can be called to update the value. This method should only * work if the {@link #set(Object, String[], Expression)} or {@link #get(Object, Expression)} method was called * first. * <p/> * <strong>NOTE:</strong> Accessors are not thread safe and need not be because a new one is created for each atom. * * @param value The value to update the accessor with. * @param expression The current expression. */ public void update(Object value, Expression expression) { if (object == null) { throw new UpdateExpressionException("The object is null, unable to update."); } set(object, value, expression); } /** * Converts the given value parameter (parameter) to a type that is accepted by the set method of this property. This * method attempts to convert the value regardless of the value being null. However, this method short circuits and * returns the value unchanged if value is runtime assignable to the type of this BaseBeanProperty. * * @param expression The current expression. * @param accessibleObject The field or method that the conversion is occurring for. This is used to look for * conversion annotations. * @param values The String values to convert. * @return The value parameter converted to the correct type. * @throws ConversionException If there was a problem converting the parameter. */ @SuppressWarnings("unchecked") protected Object convert(Expression expression, AccessibleObject accessibleObject, final String... values) throws ConversionException { Object newValue = values; // First look for annotations if (accessibleObject != null) { Annotation[] annotations = accessibleObject.getAnnotations(); for (Annotation annotation : annotations) { ConverterAnnotation converterAnnotation = annotation.annotationType().getAnnotation(ConverterAnnotation.class); if (converterAnnotation != null) { AnnotationConverter converter = converterProvider.lookup(annotation); return converter.convertFromStrings(annotation, values, type, expression.getAttributes(), expression.getExpression()); } } } // The converter does this, but pre-emptively checking these conditions will speed up conversion times Class<?> typeClass = TypeTools.rawType(type); if (!typeClass.isInstance(values)) { GlobalConverter converter = converterProvider.lookup(typeClass); if (converter == null) { throw new ConverterStateException("No type converter found for the type [" + typeClass.getName() + "]"); } newValue = converter.convertFromStrings(type, expression.getAttributes(), expression.getExpression(), values); } return newValue; } /** * Creates a new instance of the current type. * * @param key This is only used when creating arrays. It is the next atom, which is always the size of the array. * @return The new value. */ protected Object createValue(Object key) { Class<?> typeClass = TypeTools.rawType(type); Object value; if (Map.class == typeClass) { value = new HashMap(); } else if (List.class == typeClass) { value = new ArrayList(); } else if (Set.class == typeClass) { value = new HashSet(); } else if (Queue.class == typeClass) { value = new LinkedList(); } else if (Deque.class == typeClass) { value = new ArrayDeque(); } else if (SortedSet.class == typeClass) { value = new TreeSet(); } else if (typeClass.isArray()) { if (key == null) { throw new UpdateExpressionException("Attempting to create an array, but there isn't an index " + "available to determine the size of the array"); } value = Array.newInstance(typeClass.getComponentType(), Integer.parseInt(key.toString()) + 1); } else { try { value = newInstance(key, typeClass); } catch (Exception e) { throw new UpdateExpressionException("Unable to instantiate object [" + typeClass.getName() + "]"); } } return value; } protected abstract Object get(Expression expression); protected abstract <T extends Annotation> T getAnnotation(Class<T> type); /** * Gets a value from a collection using the index. This supports Arrays, Lists and Collections. * * @param index The index. * @return The value or null if the index is out of bounds. */ protected Object getValueFromCollection(int index) { if (this.object.getClass().isArray()) { if (Array.getLength(this.object) <= index) { return null; } return Array.get(this.object, index); } else if (this.object instanceof List) { List l = (List) this.object; if (l.size() <= index) { return null; } return l.get(index); } else { Iterator iter = ((Collection) this.object).iterator(); Object value = null; for (int i = 0; i < index; i++) { if (iter.hasNext()) { value = iter.next(); } else { return null; } } return value; } } protected Object newInstance(Object atom, Class<?> clazz) throws IllegalAccessException, InstantiationException { return clazz.newInstance(); } protected abstract void set(Object value, Expression expression); protected abstract void set(String[] values, Expression expression); /** * Sets the given value into the collection at the given index. * * @param index The index. * @param value The value. */ @SuppressWarnings("unchecked") protected void setValueIntoCollection(int index, Object value) { if (this.object.getClass().isArray()) { Array.set(this.object, index, value); } else if (this.object instanceof List) { List l = (List) this.object; l.set(index, value); } else { throw new UpdateExpressionException("You can only set values into arrays and Lists. You are setting a parameter into [" + getMemberAccessor() + "] which is of type [" + this.object.getClass() + "]"); } } }