blob: da6ae7489b6b481be1d1247be7a85ec0b5a0c339 [file] [log] [blame]
/*
* 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();
}
}