001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.jexl3.internal.introspection; 019 020 021import org.apache.commons.jexl3.introspection.JexlPropertyGet; 022import java.lang.reflect.Method; 023import java.beans.IntrospectionException; 024 025/** 026 * Abstract an indexed property container. 027 * <p>This allows getting properties from expressions like <code>var.container.property</code>. 028 * This stores the container name and class as well as the list of available getter and setter methods. 029 * It implements JexlPropertyGet since such a container can only be accessed from its owning instance (not set). 030 */ 031public final class IndexedType implements JexlPropertyGet { 032 /** The container name. */ 033 private final String container; 034 /** The container class. */ 035 private final Class<?> clazz; 036 /** The array of getter methods. */ 037 private final Method[] getters; 038 /** Last get method used. */ 039 private volatile Method get = null; 040 /** The array of setter methods. */ 041 private final Method[] setters; 042 /** Last set method used. */ 043 private volatile Method set = null; 044 045 /** 046 * Attempts to find an indexed-property getter in an object. 047 * The code attempts to find the list of methods getXXX() and setXXX(). 048 * Note that this is not equivalent to the strict bean definition of indexed properties; the type of the key 049 * is not necessarily an int and the set/get arrays are not resolved. 050 * 051 * @param is the introspector 052 * @param object the object 053 * @param name the container name 054 * @return a JexlPropertyGet is successful, null otherwise 055 */ 056 public static JexlPropertyGet discover(final Introspector is, final Object object, final String name) { 057 if (object != null && name != null && !name.isEmpty()) { 058 final String base = name.substring(0, 1).toUpperCase() + name.substring(1); 059 final String container = name; 060 final Class<?> clazz = object.getClass(); 061 final Method[] getters = is.getMethods(object.getClass(), "get" + base); 062 final Method[] setters = is.getMethods(object.getClass(), "set" + base); 063 if (getters != null) { 064 return new IndexedType(container, clazz, getters, setters); 065 } 066 } 067 return null; 068 } 069 070 /** 071 * A generic indexed property container, exposes get(key) and set(key, value) 072 * and solves method call dynamically based on arguments. 073 * <p>Must remain public for introspection purpose.</p> 074 */ 075 public static final class IndexedContainer { 076 /** The container instance. */ 077 private final Object container; 078 /** The container type instance. */ 079 private final IndexedType type; 080 081 /** 082 * Creates a new duck container. 083 * @param theType the container type 084 * @param theContainer the container instance 085 */ 086 private IndexedContainer(final IndexedType theType, final Object theContainer) { 087 this.type = theType; 088 this.container = theContainer; 089 } 090 091 /** 092 * Gets the property container name. 093 * @return the container name 094 */ 095 public String getContainerName() { 096 return type.container; 097 } 098 099 /** 100 * Gets the property container class. 101 * @return the container class 102 */ 103 public Class<?> getContainerClass() { 104 return type.clazz; 105 } 106 107 /** 108 * Gets a property from this indexed container. 109 * @param key the property key 110 * @return the property value 111 * @throws Exception if inner invocation fails 112 */ 113 public Object get(final Object key) throws Exception { 114 return type.invokeGet(container, key); 115 } 116 117 /** 118 * Sets a property in this indexed container. 119 * @param key the property key 120 * @param value the property value 121 * @return the invocation result (frequently null) 122 * @throws Exception if inner invocation fails 123 */ 124 public Object set(final Object key, final Object value) throws Exception { 125 return type.invokeSet(container, key, value); 126 } 127 } 128 129 /** 130 * Creates a new indexed property container type. 131 * @param name the container name 132 * @param c the owning class 133 * @param gets the array of getter methods 134 * @param sets the array of setter methods 135 */ 136 private IndexedType(final String name, final Class<?> c, final Method[] gets, final Method[] sets) { 137 this.container = name; 138 this.clazz = c; 139 this.getters = gets; 140 this.setters = sets; 141 } 142 143 @Override 144 public Object invoke(final Object obj) throws Exception { 145 if (obj != null && clazz.equals(obj.getClass())) { 146 return new IndexedContainer(this, obj); 147 } 148 throw new IntrospectionException("property resolution error"); 149 } 150 151 @Override 152 public Object tryInvoke(final Object obj, final Object key) { 153 if (obj != null && key != null 154 && clazz.equals(obj.getClass()) 155 && container.equals(key.toString())) { 156 return new IndexedContainer(this, obj); 157 } 158 return Uberspect.TRY_FAILED; 159 } 160 161 @Override 162 public boolean tryFailed(final Object rval) { 163 return rval == Uberspect.TRY_FAILED; 164 } 165 166 @Override 167 public boolean isCacheable() { 168 return true; 169 } 170 171 /** 172 * Gets the value of a property from a container. 173 * @param object the container instance (not null) 174 * @param key the property key (not null) 175 * @return the property value 176 * @throws Exception if invocation failed; 177 * IntrospectionException if a property getter could not be found 178 */ 179 private Object invokeGet(final Object object, final Object key) throws Exception { 180 if (getters != null && getters.length > 0) { 181 Method jm = get; 182 if (jm != null) { 183 final Class<?>[] ptypes = jm.getParameterTypes(); 184 if (ptypes[0].isAssignableFrom(key.getClass())) { 185 return jm.invoke(object, key); 186 } 187 } 188 final Object[] args = {key}; 189 final String mname = getters[0].getName(); 190 final MethodKey km = new MethodKey(mname, args); 191 jm = km.getMostSpecificMethod(getters); 192 if (jm != null) { 193 final Object invoked = jm.invoke(object, args); 194 get = jm; 195 return invoked; 196 } 197 } 198 throw new IntrospectionException("property get error: " 199 + object.getClass().toString() 200 + "@" + key.toString()); 201 } 202 203 /** 204 * Sets the value of a property in a container. 205 * @param object the container instance (not null) 206 * @param key the property key (not null) 207 * @param value the property value (not null) 208 * @return the result of the method invocation (frequently null) 209 * @throws Exception if invocation failed; 210 * IntrospectionException if a property setter could not be found 211 */ 212 private Object invokeSet(final Object object, final Object key, final Object value) throws Exception { 213 if (setters != null && setters.length > 0) { 214 Method jm = set; 215 if (jm != null) { 216 final Class<?>[] ptypes = jm.getParameterTypes(); 217 if (ptypes[0].isAssignableFrom(key.getClass()) 218 && (value == null 219 || ptypes[1].isAssignableFrom(value.getClass()))) { 220 return jm.invoke(object, key, value); 221 } 222 } 223 final Object[] args = {key, value}; 224 final String mname = setters[0].getName(); 225 final MethodKey km = new MethodKey(mname, args); 226 jm = km.getMostSpecificMethod(setters); 227 if (jm != null) { 228 final Object invoked = jm.invoke(object, args); 229 set = jm; 230 return invoked; 231 } 232 } 233 throw new IntrospectionException("property set error: " 234 + object.getClass().toString() 235 + "@" + key.toString()); 236 } 237 238}