/******************************************************************************* * Copyright 2016 * Ubiquitous Knowledge Processing (UKP) Lab * Technische Universität Darmstadt * * 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 de.tudarmstadt.ukp.lmf.transform; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import de.tudarmstadt.ukp.lmf.model.miscellaneous.EVarType; import de.tudarmstadt.ukp.lmf.model.miscellaneous.VarType; /** * Helper class for caching the fields of an LMF class along with their * types, getters, and setters. The implementation basically determines all * field, type, and method information using the Java reflection API. Rather * than obtaining this kind of information over and over again (which would * slow down the application's performance), it is recommended to obtain * this meta information just once and store it in a cache (e.g., a simple * {@link Map}) using this class as a data model. * @author Christian M. Meyer */ public class UBYLMFClassMetadata { /** * Metadata information of a single field within an LMF class. The * class should not be instanciated individually. Use * {@link UBYLMFClassMetadata} as a container for the field * metadata instances. * @author Christian M. Meyer */ public static class UBYLMFFieldMetadata { protected Field field; protected Class<?> type; protected Class<?> genericElementType; protected EVarType varType; protected Method getter; protected Method setter; /** Instanciates a new field metadata cache for the given * field. This involves determining the field's type, getter, * and setter. For parameterized types, the given * actual type will be used which should match the generic * parameter of the enclosing subclass. */ protected UBYLMFFieldMetadata(final Field field, final Class<?> actualType) { this.field = field; // Raw type. type = field.getType(); if (actualType != null && type == Object.class && !type.equals(field.getGenericType())) { type = actualType; } // Generic type parameter. genericElementType = null; Type genericFieldType = field.getGenericType(); if (genericFieldType instanceof ParameterizedType) { ParameterizedType aType = (ParameterizedType) genericFieldType; genericElementType = (Class<?>) aType.getActualTypeArguments()[0]; } // VarType. VarType varTypeAnnot = field.getAnnotation(VarType.class); if (varTypeAnnot == null) { varType = EVarType.NONE; return; // no getter/setter } else { varType = varTypeAnnot.type(); } // Determine getter and setter methods. String methodName = field.getName(); methodName = methodName.replace("_", ""); if (methodName.startsWith("is")) { methodName = methodName.substring(2); } methodName = methodName.substring(0,1).toUpperCase() + methodName.substring(1); String getterName = "get" + methodName; if (type == boolean.class || type == Boolean.class) { getterName = "is" + methodName; } String setterName = "set" + methodName; for (Method method : field.getDeclaringClass().getMethods()) { if (method.getName().equals(getterName)) { getter = method; } else if (method.getName().equals(setterName)) { setter = method; } } } /** Returns the name of the field. Shorthand for * <code>getField().getName()</code>.*/ public String getName() { return field.getName(); } /** Returns the data type of this field. For generic types, the raw * type will be returned (e.g., List for List<Number>). Use * {@link #getGenericElementType()} for obtaining the generic * type parameter. For parameterized types, the actual type is * returned, if available (e.g., <code>Double</code> for an * inherited field defined as <code>T number</code> in a generic class * <code>TestClass extends BaseClass<Double</code> that is * a subclass of <code>BaseClass%lt;T extends Number></code>). */ public Class<?> getType() { return type; } /** Returns true if, and only if, the field represents a truth * value using a primitive or wrapped boolean type. */ public boolean isBoolean() { return type == boolean.class || type == Boolean.class; } /** Returns true if, and only if, the field represents an integer * using a primitive or wrapped int type. */ public boolean isInteger() { return type == int.class || type == Integer.class; } /** Returns true if, and only if, the field represents a floating * point number using a primitive or wrapped double type. */ public boolean isDouble() { return type == double.class || type == Double.class; } /** Returns true if, and only if, the field is an enumeration type. */ public boolean isEnum() { return type.isEnum(); } /** Returns true if, and only if, the field represents the Date type. */ public boolean isDate() { return type == Date.class; } /** Returns the generic parameter of the field. That is, if the * field is not a raw type, the actual type parameter will be * returned (e.g., Number for List<Number>). */ public Class<?> getGenericElementType() { return genericElementType; } /** Returns the variable type of this field. */ public EVarType getVarType() { return varType; } /** Returns the getter method of this field. */ public Method getGetter() { return getter; } /** Returns the setter method of this field. */ public Method getSetter() { return setter; } } protected Class<?> clazz; protected List<UBYLMFFieldMetadata> fields; /** Create a new metadata representation for the given LMF class. * That is, the implementation determines information on all declared * fields of the given class (using the Java reflection API) and * creates a {@link UBYLMFFieldMetadata} instance for each field. */ public UBYLMFClassMetadata(final Class<?> clazz) { this.clazz = clazz; fields = new ArrayList<UBYLMFFieldMetadata>(); for (Field field : clazz.getDeclaredFields()) { fields.add(new UBYLMFFieldMetadata(field, null)); } Type superClassType = clazz.getGenericSuperclass(); while (superClassType != null) { //TODO: This is hacky, since only the first type parameter // is used and there's no matching to type parameter names. // However, there seems to be no clear mapping between // type parameter index and name. Class<?> superClass; Class<?> genericType = null; if (superClassType instanceof ParameterizedType) { ParameterizedType aType = (ParameterizedType) superClassType; genericType = (Class<?>) aType.getActualTypeArguments()[0]; superClass = (Class<?>) aType.getRawType(); } else { superClass = (Class<?>) superClassType; } for (Field field : superClass.getDeclaredFields()) { fields.add(new UBYLMFFieldMetadata(field, genericType)); } superClassType = superClass.getGenericSuperclass(); } } /** Return the meta information of all fields the current LMF class * contains. */ public Iterable<UBYLMFFieldMetadata> getFields() { return fields; } /** Returns the class type for which the metadata is stored. */ public Class<?> getClazz() { return clazz; } }