在一个项目中需要用到使用反射机制访问对象,于是写了一个工具类 ObjectAccessor ,主要用途是方便通过反射机制访问Java对象:
1.访问成员变量,取值和赋值
2.使用构造函数生成新对象(可根据传递的参数自动匹配合适的构造器)
3.方法调用(可根据传递的参数自动匹配合适的函数)
4.精确类型指定工具方法
构造器和函数的参数支持根据传递的参数自动匹配最佳构造器或函数,对于大部分情况,自动匹配可以完全正确的工作,包括可变参数表的自动匹配,除了以下情况:
传递的参数为null
由于null可匹配除了基本类型以外的所有类型,当存在同名函数且参数个数相同,均为对象类型时,如果参数中有null值,则自动匹配将无法确定唯一匹配值,最终将执行第一个查找到且匹配的函数/构造器
同名函数且参数个数相同,且为可兼容类型
如 public void a(int t); 和 public void a(Integer t),由于参数传递时会自动将基本类型转换为对象类型,所以自动匹配只会选择参数为Integer的函数
再如 Class A,Class B extends A; Class C extends B, public void a(A a);public void a(B a); 如果参数是C类的实例,则同样自动匹配无法确定唯一匹配值,最终将执行第一个查找到且匹配的函数/构造器
上述情况仅发生在存在同名函数且个数相同时,所以大多数情况自动匹配可以完全正确工作的
如果确实存在上述情况,就需要指定确定的参数表来访问了
访问成员变量
public static <T> T variable(Object obj,String varName,Class<T> returnClass);
如果obj传递的是一个Class对象,则只访问static变量
给成员变量赋值
public static void setVariable(Object obj,String varName,Object newObj);
如果obj传递的是一个Class对象,则只访问static变量
使用精确类型数组指定的构造器生成新对象
public static <T> T strictConstructor(Class<T> c,Class<?>[] parameterTypes,Object... args);
根据传递的参数匹配的最佳构造器生成新对象
public static <T> T constructor(Class<T> c,Object... args);
使用精确类型数组调用指定的方法
public static <T> T strictMethod(Object obj,String methodName,Class<T> returnClass,Class<?>[] parameterTypes,Object... args);
如果obj传递的是一个Class对象,则只调用static方法
根据传递的参数匹配调用最佳匹配的方法
public static <T> T method(Object obj,String methodName,Class<T> returnClass,Object... args);
如果obj传递的是一个Class对象,则只调用static方法
非public的访问
ObjectAccessor的上述静态方法只能访问public类型的变量,构造器,方法,
如果希望访问非public的,可使用其静态子类Accessible提供的上述所有方法
精确类型指定工具
public static Class<?>[] toClass(String[] classNames);
public static Class<?>[] toClass(String[] classNames,ClassLoader cl);
有时可能需要使用字符串来指定精确类型,toClass方法可以将String数组转为对应的Class数组
基本类型可直接使用其名称,如 int float : toClass(new String[]{"int","float"});
数组可直接在类名后加'[]' ,如 int[] java.lang.String[] org.example.TestClass[]
/**
*
* Copyright (C) 2010 the original author or authors.
*
* This file is part of JIOPi iBean project.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*
*/
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
*
* 使用Java反射机制进行对象的访问
*
* <P>使用字符串指定类型时,基本类型直接使用 int long 等定义,数组使用 int[] 或 类名[] 如 java.lang.String[]</P>
*
* @version 0.1 , 2010.4.20
* @since iBeanKernel0.1 , 2010.4.20
*
*/
public class ObjectAccessor {
/**
*
* 根据类名数组获取类数组的便捷方法
* <P>优先使用线程上下文类加载器,如果不存在,则使用当前类的类加载器</P>
* @param classNames
* @return
* @since iBeanKernel0.1 , 2010.4.21
*/
public static Class<?>[] toClass(String[] classNames){
return toClass(classNames,null);
}
/**
* 使用给定的类加载器将指定的类名数组变更为类数组
* @param classNames
* @param cl
* @return
* @see ObjectAccessor#toClass(String[])
* @since iBeanKernel0.1 , 2010.4.21
*/
public static Class<?>[] toClass(String[] classNames,ClassLoader cl){
if(classNames == null) throw new IllegalArgumentException("classNames can't be NULL!");
Class<?>[] classes = new Class<?>[classNames.length];
for(int i=0;i<classNames.length;i++){
classes[i] = loadClass(classNames[i],cl);
}
return classes;
}
private final static Map<String, Class<?>> primitiveClassMap;
static {
Map<String,Class<?>> tmp = new HashMap<String, Class<?>>();
tmp.put("short", short.class );
tmp.put("int", int.class);
tmp.put("char", char.class);
tmp.put("long", long.class);
tmp.put("boolean", boolean.class);
tmp.put("float", float.class);
tmp.put("double", double.class);
tmp.put("byte", byte.class);
primitiveClassMap = Collections.unmodifiableMap( tmp );
}
private final static Map<String, String> primitiveArrayClassMap;
static {
Map<String,String> tmp = new HashMap<String, String>();
tmp.put("short[]", "[S" );
tmp.put("int[]", "[I");
tmp.put("char[]", "[C");
tmp.put("long[]", "[L");
tmp.put("boolean[]", "[Z");
tmp.put("float[]", "[F");
tmp.put("double[]", "[D");
tmp.put("byte[]", "[B");
primitiveArrayClassMap = Collections.unmodifiableMap( tmp );
}
private static Class<?> loadClass(String className,ClassLoader cl){
if(primitiveClassMap.containsKey(className))return primitiveClassMap.get(className);
else if(primitiveArrayClassMap.containsKey(className)) className = primitiveArrayClassMap.get(className);
else if(className.endsWith("[]")){
className = "[L"+className.substring(0,className.length()-2)+";";
}
if(cl==null){
cl = Thread.currentThread().getContextClassLoader();
}
try {
return Class.forName(className,false,cl);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 访问指定的public成员变量
* @param obj
* @param varName
* @param returnClass
* @return
* @since iBeanKernel0.1 , 2010.4.21
*/
public static <T> T variable(Object obj,String varName,Class<T> returnClass){
return variable(obj,varName,returnClass,true);
}
@SuppressWarnings("unchecked")
protected static <T> T variable(Object obj,String varName,Class<T> returnClass,boolean isPublic){
Object variableObject = obj;
Class<?> objectClass = obj.getClass();
if(objectClass == Class.class){//类的静态方法
objectClass = (Class<?>)variableObject;
variableObject = null;
}
Field f = getProperField(objectClass,varName,variableObject == null,isPublic);
Object rt;
try {
rt = f.get(variableObject);
}catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return (T)rt;
}
/**
* 设置成员变量的值
* @param obj
* @param varName
* @param newObj
*/
public static void setVariable(Object obj,String varName,Object newObj){
setVariable(obj,varName,newObj,true);
}
protected static void setVariable(Object obj,String varName,Object newObj,boolean isPublic){
Object variableObject = obj;
Class<?> objectClass = obj.getClass();
if(objectClass == Class.class){//类的静态方法
objectClass = (Class<?>)variableObject;
variableObject = null;
}
Field f = getProperField(objectClass,varName,variableObject == null,isPublic);
try {
f.set(variableObject, newObj);
}catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private static Field getProperField(Class<?> c, String name, boolean isStatic,boolean isPublic){
Field f = null;
try {
if(isPublic) f = c.getField( name );
else f = c.getDeclaredField(name);
int mod = f.getModifiers();
if( isStatic&&!Modifier.isStatic( mod ) ) f = null;
else if(!isPublic && !Modifier.isPublic(mod)) f.setAccessible(true);
}catch (NoSuchFieldException e) {
}
if(f==null){
throw new RuntimeException(new NoSuchFieldException("no"+isPublicToString(isPublic)+isStaticToString(isStatic)+" variable : "+c.getName() + "." + name));
}
return f;
}
/**
* 使用public构造器生成对象
* @param c
* @param parameterTypes
* @param args
* @return
* @since iBeanKernel0.1 , 2010.4.21
*/
public static <T> T strictConstructor(Class<T> c,Class<?>[] parameterTypes,Object... args){
return strictConstructor(c,parameterTypes,true,args);
}
@SuppressWarnings("unchecked")
protected static <T> T strictConstructor(Class<T> c,Class<?>[] parameterTypes,boolean isPublic,Object... args){
Constructor<?> con;
try {
if(isPublic){
con = c.getConstructor(parameterTypes);
}else{
con = c.getDeclaredConstructor(parameterTypes);
}
}catch (NoSuchMethodException e1) {
throw new RuntimeException(new NoSuchMethodException("no public constructor : "+c.getName()+argumentTypesToString(parameterTypes)));
}
try {
return (T) con.newInstance( args );
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* 通过给定参数自动匹配public
* @param c
* @param args
* @return
* @since iBeanKernel0.1 , 2010.4.21
*/
public static <T> T constructor(Class<T> c,Object... args){
return constructor(c,true,args);
}
@SuppressWarnings("unchecked")
protected static <T> T constructor(Class<T> c,boolean isPublic,Object... args){
Constructor<?> con = null;
Class<?>[] argTypes = getArgTypes( args );
Class<?>[] constructorTypes;
int matchScore = NOT_MATCH,score;
Constructor<?>[] constructors = null;
if(isPublic) constructors = c.getConstructors();
else constructors = c.getDeclaredConstructors();
for(Constructor<?> constructor:constructors){
constructorTypes = constructor.getParameterTypes();
boolean isVarArgs = constructor.isVarArgs();
score = testMatch(argTypes,constructorTypes,isVarArgs);
if(isVarArgs)score-=1;
if(score != NOT_MATCH && score>matchScore){
matchScore = score;
con = constructor;
}
}
if(con==null){
throw new RuntimeException(new NoSuchMethodException("no"+isPublicToString(isPublic)+" constructor : "+c.getName()+argumentTypesToString(argTypes)));
}
if(con.isVarArgs()&&args!=null){
args = transVarArgs(con.getParameterTypes(),args);
}
try {
if(!isPublic && !Modifier.isPublic(con.getModifiers()))con.setAccessible(true);
return (T) con.newInstance( args );
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
*
* 使用明确的参数表类型动态调用方法
*
* @param obj
* @param methodName
* @param returnClass
* @param parameterTypes
* @param args
* @return
* @since iBeanKernel0.1 , 2010.4.21
*/
public static <T> T strictMethod(Object obj,String methodName,Class<T> returnClass,Class<?>[] parameterTypes,Object... args){
return strictMethod(obj,methodName,returnClass,parameterTypes,true,args);
}
@SuppressWarnings("unchecked")
protected static <T> T strictMethod(Object obj,String methodName,Class<T> returnClass,Class<?>[] parameterTypes,boolean isPublic,Object... args){
Object methodObject = obj;
Class<?> objectClass = obj.getClass();
if(objectClass == Class.class){
objectClass = (Class<?>)methodObject;
methodObject = null;
}
boolean isStatic = methodObject == null;
Method m = null;
try {
if(isPublic)m = objectClass.getMethod(methodName, parameterTypes);
else m = objectClass.getDeclaredMethod(methodName, parameterTypes);
}catch (NoSuchMethodException e) {
throw new RuntimeException(new NoSuchMethodException("no"+isPublicToString(isPublic)+isStaticToString(isStatic)+" method : "+objectClass.getName() + "." + methodName + argumentTypesToString(parameterTypes)));
}
int mod = m.getModifiers();
if(isStatic&&!Modifier.isStatic(mod)){
throw new RuntimeException(new NoSuchMethodException("no"+isPublicToString(isPublic)+isStaticToString(isStatic)+" method : "+objectClass.getName() + "." + methodName + argumentTypesToString(parameterTypes)));
}
if(!isPublic && !Modifier.isPublic(mod)) m.setAccessible(true);
try {
return (T) m.invoke(methodObject, args);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* 执行给定对象的方法
*
* <P>
* 如果obj是一个 类对象(Class对象的子类),则认为是执行类的静态方法
* </P>
* @param obj
* @param methodName
* @param returnClass
* @param args
* @return
* @since iBeanKernel0.1 , 2010.4.20
*/
public static <T> T method(Object obj,String methodName,Class<T> returnClass,Object... args){
return method(obj,methodName,returnClass,true,args);
}
@SuppressWarnings("unchecked")
protected static <T> T method(Object obj,String methodName,Class<T> returnClass,boolean isPublic,Object... args){
Object methodObject = obj;
Class<?> objectClass = obj.getClass();
if(objectClass == Class.class){
objectClass = (Class<?>)methodObject;
methodObject = null;
}
Method m = getProperMethod(objectClass,methodName,methodObject==null,isPublic,args);
if(m.isVarArgs()&&args!=null) args = transVarArgs(m.getParameterTypes(),args);
else if(args == null) args = new Object[]{null};
try {
Object o = m.invoke(methodObject, args);
return (T)o;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
private static Object[] transVarArgs(Class<?>[] varTypes,Object... args){
Object[] newArgs = new Object[varTypes.length];
Object[] varArgs = null;
int varArgIndex = 0;
boolean notChecked = true;
for(int i=0;i<args.length;i++){
if((i+1==varTypes.length)){//分界点
varArgs = (Object[]) Array.newInstance(varTypes[i].getComponentType(), args.length-varTypes.length+1);
}else if( i< varTypes.length ){
newArgs[i]= args[i];
}
if(varArgs!=null){
if(notChecked && args[i]!=null && args[i].getClass().getComponentType()!=null){
notChecked = false;
varArgs = (Object[]) args[i];
break;
}
varArgs[varArgIndex++]=args[i];
}
}
newArgs[varTypes.length-1]=varArgs;
return newArgs;
}
private final static int NOT_MATCH = 0;
private final static int MATCH = 1000;
private final static int MAY_MATCH = 2;
private static Method getProperMethod(Class<?> c,String methodName,boolean isStatic,boolean isPublic,Object... args){
Method m = null;
//获取参数类型
Class<?>[] argTypes = getArgTypes( args );
Method[] methods = MethodManager.getMethods( c, methodName, argTypes.length ,true);
m = getProperMethod(methods,isStatic,false,argTypes);
if(m==null){//尝试查找变参函数
methods = MethodManager.getMethods( c, methodName, -1 ,true);
m = getProperMethod(methods,isStatic,true,argTypes);
}
if(m==null&&!isPublic){
methods = MethodManager.getMethods( c, methodName, argTypes.length ,false);
m = getProperMethod(methods,isStatic,false,argTypes);
if(m==null){//尝试查找变参函数
methods = MethodManager.getMethods( c, methodName, -1 ,false);
m = getProperMethod(methods,isStatic,true,argTypes);
}
if(m!=null) m.setAccessible(true);
}
if(m==null){
throw new RuntimeException(new NoSuchMethodException("no"+isPublicToString(isPublic)+isStaticToString(isStatic)+" method : "+c.getName() + "." + methodName + argumentTypesToString(argTypes)));
}
return m;
}
private static Method getProperMethod(Method[] methods,boolean isStatic,boolean isVarArgs,Class<?>[] argTypes){
if(methods == null )return null;
Method m = null;
Class<?>[] methodTypes;
int matchScore = NOT_MATCH,score,mod;
for( Method method:methods ) {
mod = method.getModifiers();
if( !isStatic||isStatic&&Modifier.isStatic( mod ) ) {
methodTypes = method.getParameterTypes();
score = testMatch(argTypes,methodTypes,isVarArgs);
if(score != NOT_MATCH && score>matchScore){//匹配
matchScore = score;
m = method;
}
}
}
return m;
}
private static int testMatch(Class<?>[] argTypes, Class<?>[] paramTypes,boolean isVarArgs){
if(!isVarArgs && paramTypes.length<argTypes.length)return NOT_MATCH;
Class<?> arg, param=null;
int total = MAY_MATCH, score;
for( int i=0;i<argTypes.length;i++ ) {
arg = argTypes[i];
if(isVarArgs && (i+1)==paramTypes.length){
if(arg.getComponentType()==null)param = paramTypes[i].getComponentType();
else{
score = testMatch(arg,paramTypes[i]);
if(score==NOT_MATCH)return NOT_MATCH;
return total + score;
}
}
else if( i< paramTypes.length ){
param = paramTypes[i];
}
score = testMatch(arg,param);
if(score==NOT_MATCH) return NOT_MATCH;
total+=score;
}
// System.out.println( "args:" + argumentTypesToString( argTypes )
// + ", params:" + argumentTypesToString( paramTypes )
// + ",\n returned " + total );
return total;
}
private static int testMatch(Class<?> argType,Class<?> paramType){
if(argType==null){
if(paramType.isPrimitive()) return NOT_MATCH;
else return MAY_MATCH;
}
if( !paramType.isPrimitive() ) {
if( argType.equals( paramType ) ) {
return MATCH;
} else if( paramType.isAssignableFrom( argType ) ) {
return MAY_MATCH;
} else {
return NOT_MATCH;
}
} else {
return primitiveEquals( argType, paramType );
}
}
private final static Map<Class<?>, Class<?>> primitiveMap;
static {
// 设置基本类型和封装类
Map<Class<?>,Class<?>> tmp = new HashMap<Class<?>, Class<?>>();
tmp.put( short.class, Short.class );
tmp.put( int.class, Integer.class );
tmp.put( char.class, Character.class );
tmp.put( long.class, Long.class );
tmp.put( boolean.class, Boolean.class );
tmp.put( float.class, Float.class );
tmp.put( double.class, Double.class );
tmp.put( byte.class, Byte.class );
primitiveMap = Collections.unmodifiableMap( tmp );
}
private static int primitiveEquals( Class<?> wrapper, Class<?> prim ) {
if( prim.equals( wrapper ) ) {
return MATCH;
}
if( primitiveMap.get( prim ).equals( wrapper ) ) {
return MAY_MATCH;
}
return NOT_MATCH;
}
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class[1];
private static Class<?>[] getArgTypes(Object... args){
if(args == null)return EMPTY_CLASS_ARRAY;
int len = args.length;
Class<?>[] types = new Class[ len ];
for(int i=0;i<len;i++){
if( args[i]!=null ) types[i] = args[i].getClass();
}
return types;
}
private static class MethodManager{
private static Map<Class<?>,Map<String,Method[]>> publicClassMethodCache = new HashMap<Class<?>,Map<String,Method[]>>();
private static Map<Class<?>,Map<String,Method[]>> otherClassMethodCache = new HashMap<Class<?>,Map<String,Method[]>>();
public static Method[] getMethods( Class<?> c, String name, int argLen ,boolean isPublic){
Map<String,Method[]> classMethods = getClassMethods(c,isPublic);
return classMethods.get( getMethodKey( name, argLen) );
}
private static Map<String,Method[]> getClassMethods(Class<?> c,boolean isPublic){
Map<String,Method[]> classMethods = null;
Map<Class<?>,Map<String,Method[]>> needCache = publicClassMethodCache;
if(!isPublic) needCache = otherClassMethodCache;
synchronized(publicClassMethodCache){
classMethods = needCache.get(c);
}
if(classMethods == null){
synchronized(c){
synchronized(publicClassMethodCache){
classMethods = needCache.get(c);
}
if(classMethods == null){
Map<String, List<Method>> mMap = new LinkedHashMap<String, List<Method>>();
Method[] allMethods = null;
if(isPublic) allMethods = c.getMethods();
else allMethods = c.getDeclaredMethods();
for( Method m:allMethods) {
if(!isPublic && Modifier.isPublic(m.getModifiers())) continue;
int typesLen = m.getParameterTypes().length;
if(m.isVarArgs())typesLen = -1;
String mKey = getMethodKey( m.getName(), typesLen);
List<Method> methods = mMap.get( mKey );
if( methods==null ) {
methods = new ArrayList<Method>();
mMap.put(mKey, methods);
}
methods.add(m);
}
classMethods = new HashMap<String,Method[]>();
for( Map.Entry<String, List<Method>> en:mMap.entrySet() ) {
classMethods.put( en.getKey(), en.getValue().toArray( new Method[]{} ) );
}
synchronized(publicClassMethodCache){
needCache.put(c, classMethods);
}
}
}
}
return classMethods;
}
private static String getMethodKey( String methodName, int argLen) {
return argLen + methodName;
}
}
private static String isStaticToString(boolean isStatic){
if(isStatic) return " static";
return "";
}
private static String isPublicToString(boolean isPublic){
if(isPublic) return " public";
return "";
}
private static String argumentTypesToString(Class<?>[] argTypes) {
StringBuilder buf = new StringBuilder();
buf.append("(");
if (argTypes != null) {
for (int i = 0; i < argTypes.length; i++) {
if (i > 0) {
buf.append(", ");
}
Class<?> c = argTypes[i];
buf.append((c == null) ? "null" : c.getName());
}
}
buf.append(")");
return buf.toString();
}
/**
*
* 覆盖ReflectUtil的 variable constructor method 方法,访问时不做只访问public的限制
* ,
*
* @version 0.1 , 2010.4.21
* @since iBeanKernel0.1 , 2010.4.21
*/
public static class Accessible extends ObjectAccessor{
/**
* 访问指定的成员变量
* @param obj
* @param varName
* @param returnClass
* @return
* @since iBeanKernel0.1 , 2010.4.21
*/
public static <T> T variable(Object obj,String varName,Class<T> returnClass){
return variable(obj,varName,returnClass,false);
}
/**
* 设置成员变量的值
* @param obj
* @param varName
* @param newObj
*/
public static void setVariable(Object obj,String varName,Object newObj){
setVariable(obj,varName,newObj,false);
}
/**
* 使用构造器生成对象
* @param c
* @param parameterTypes
* @param args
* @return
* @since iBeanKernel0.1 , 2010.4.21
*/
public static <T> T strictConstructor(Class<T> c,Class<?>[] parameterTypes,Object... args){
return strictConstructor(c,parameterTypes,false,args);
}
/**
* 通过给定参数自动匹配
* @param c
* @param args
* @return
* @since iBeanKernel0.1 , 2010.4.21
*/
public static <T> T constructor(Class<T> c,Object... args){
return constructor(c,false,args);
}
/**
*
* 使用明确的参数表类型动态调用方法
*
* @param obj
* @param methodName
* @param returnClass
* @param parameterTypes
* @param args
* @return
* @since iBeanKernel0.1 , 2010.4.21
*/
public static <T> T strictMethod(Object obj,String methodName,Class<T> returnClass,Class<?>[] parameterTypes,Object... args){
return strictMethod(obj,methodName,returnClass,parameterTypes,false,args);
}
/**
* 执行给定对象的方法
*
* <P>
* 如果obj是一个 类对象(Class对象的子类),则认为是执行类的静态方法
* </P>
* @param obj
* @param methodName
* @param returnClass
* @param args
* @return
* @since iBeanKernel0.1 , 2010.4.20
*/
public static <T> T method(Object obj,String methodName,Class<T> returnClass,Object... args){
return method(obj,methodName,returnClass,false,args);
}
}
}