/* * 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. */ package org.apache.ignite.internal.util; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.ConcurrentMap; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.lang.IgnitePredicate; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.IgniteSystemProperties.IGNITE_REFLECTION_CACHE_SIZE; /** * Reflection field and method cache for classes. */ public class GridReflectionCache implements Externalizable { /** */ private static final long serialVersionUID = 0L; /** Compares fields by name. */ private static final Comparator<Field> FIELD_NAME_COMPARATOR = new Comparator<Field>() { @Override public int compare(Field f1, Field f2) { return f1.getName().compareTo(f2.getName()); } }; /** Compares methods by name. */ private static final Comparator<Method> METHOD_NAME_COMPARATOR = new Comparator<Method>() { @Override public int compare(Method m1, Method m2) { return m1.getName().compareTo(m2.getName()); } }; /** Cache size. */ private static final int CACHE_SIZE = Integer.getInteger(IGNITE_REFLECTION_CACHE_SIZE, 128); /** Fields cache. */ private ConcurrentMap<Class, List<Field>> fields = new GridBoundedConcurrentLinkedHashMap<>( CACHE_SIZE, CACHE_SIZE); /** Methods cache. */ private ConcurrentMap<Class, List<Method>> mtds = new GridBoundedConcurrentLinkedHashMap<>( CACHE_SIZE, CACHE_SIZE); /** Field predicate. */ private IgnitePredicate<Field> fp; /** Method predicate. */ private IgnitePredicate<Method> mp; /** * Reflection cache without any method or field predicates. */ public GridReflectionCache() { // No-op. } /** * Reflection cache with specified field and method predicates. * @param fp Field predicate. * @param mp Method predicate. */ public GridReflectionCache(@Nullable IgnitePredicate<Field> fp, @Nullable IgnitePredicate<Method> mp) { this.fp = fp; this.mp = mp; } /** * Gets field value for object. * * @param o Key to get affinity key for. * @return Value of the field for given object or {@code null} if field was not found. * @throws IgniteCheckedException If failed. */ @Nullable public Object firstFieldValue(Object o) throws IgniteCheckedException { assert o != null; Field f = firstField(o.getClass()); if (f != null) { try { return f.get(o); } catch (IllegalAccessException e) { throw new IgniteCheckedException("Failed to access field for object [field=" + f + ", obj=" + o + ']', e); } } return null; } /** * Gets method return value for object. * * @param o Key to get affinity key for. * @return Method return value for given object or {@code null} if method was not found. * @throws IgniteCheckedException If failed. */ @Nullable public Object firstMethodValue(Object o) throws IgniteCheckedException { assert o != null; Method m = firstMethod(o.getClass()); if (m != null) { try { return m.invoke(o); } catch (IllegalAccessException | InvocationTargetException e) { throw new IgniteCheckedException("Failed to invoke method for object [mtd=" + m + ", obj=" + o + ']', e); } } return null; } /** * Gets first field in the class list of fields. * * @param cls Class. * @return First field. */ @Nullable public Field firstField(Class<?> cls) { assert cls != null; List<Field> l = fields(cls); return l.isEmpty() ? null : l.get(0); } /** * Gets first method in the class list of methods. * * @param cls Class. * @return First method. */ @Nullable public Method firstMethod(Class<?> cls) { assert cls != null; List<Method> l = methods(cls); return l.isEmpty() ? null : l.get(0); } /** * Gets fields. * * @param cls Class. * @return Annotated field. */ public List<Field> fields(Class<?> cls) { assert cls != null; List<Field> fieldsList = fields.get(cls); if (fieldsList == null) { fieldsList = new ArrayList<>(); for (Class<?> c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) { List<Field> l = new ArrayList<>(); for (Field f : c.getDeclaredFields()) { if (fp == null || fp.apply(f)) { f.setAccessible(true); l.add(f); } } if (!l.isEmpty()) { Collections.sort(l, FIELD_NAME_COMPARATOR); fieldsList.addAll(l); } } fields.putIfAbsent(cls, fieldsList); } return fieldsList; } /** * Gets methods. * * @param cls Class. * @return Annotated method. */ public List<Method> methods(Class<?> cls) { assert cls != null; List<Method> mtdsList = mtds.get(cls); if (mtdsList == null) { mtdsList = new ArrayList<>(); for (Class<?> c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) { List<Method> l = new ArrayList<>(); for (Method m : c.getDeclaredMethods()) { if (mp == null || mp.apply(m)) { m.setAccessible(true); l.add(m); } } if (!l.isEmpty()) { Collections.sort(l, METHOD_NAME_COMPARATOR); mtdsList.addAll(l); } } mtds.putIfAbsent(cls, mtdsList); } return mtdsList; } /** {@inheritDoc} */ @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(fp); out.writeObject(mp); } /** {@inheritDoc} */ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { fp = (IgnitePredicate<Field>)in.readObject(); mp = (IgnitePredicate<Method>)in.readObject(); } }