Package org.castor.jdo.jpa.info

Source Code of org.castor.jdo.jpa.info.ClassInfoBuilder

/*
* Copyright 2008 Werner Guttmann
*
* 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 org.castor.jdo.jpa.info;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.castor.core.annotationprocessing.AnnotationProcessingService;
import org.castor.jdo.jpa.natures.JPAClassNature;
import org.castor.jdo.jpa.natures.JPAFieldNature;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.loader.FieldHandlerImpl;
import org.exolab.castor.mapping.loader.TypeInfo;

/**
* Uses a JPA annotated {@link Class} to build {@link ClassInfo} and
* {@link FieldInfo}s of it and parse the mapping information in them. For now,
* all fields are mapped as normal fields. No keys fields are recognized.
*
* @author Peter Schmidt
* @since 1.3
*/
public class ClassInfoBuilder {

    /**
     * The {@link AnnotationProcessingService} for class related annotations.
     */
    private AnnotationProcessingService _classAnnotationProcessingService;
    /**
     * The {@link AnnotationProcessingService} for field related annotations.
     */
    private AnnotationProcessingService _fieldAnnotationProcessingService;

    /**
     * Create a new ClassInfoBuilder with the JPA annotation capable
     * {@link AnnotationProcessingService}s.
     * <ul>
     * <li>{@link JPAClassAnnotationProcessingService}</li>
     * <li>{@link JPAFieldAnnotationProcessingService}</li>
     * </ul>
     */
    public ClassInfoBuilder() {
        _classAnnotationProcessingService = new JPAClassAnnotationProcessingService();
        _fieldAnnotationProcessingService = new JPAFieldAnnotationProcessingService();
    }

    /**
     * @return the {@link AnnotationProcessingService} used to read annotations
     *         of the class.
     */
    public final AnnotationProcessingService getClassAnnotationProcessingService() {
        return _classAnnotationProcessingService;
    }

    /**
     * Set the {@link AnnotationProcessingService} used to read the classes
     * annotations.
     *
     * @param annotationProcessingService
     *            the {@link AnnotationProcessingService} used to read the
     *            classes annotations.
     */
    public final void setClassAnnotationProcessingService(
            final AnnotationProcessingService annotationProcessingService) {
        _classAnnotationProcessingService = annotationProcessingService;
    }

    /**
     * @return the {@link AnnotationProcessingService} used to read annotations
     *         of the classes fields.
     */
    public final AnnotationProcessingService getFieldAnnotationProcessingService() {
        return _fieldAnnotationProcessingService;
    }

    /**
     * Set the {@link AnnotationProcessingService} used to read annotations of
     * the classes fields.
     *
     * @param annotationProcessingService
     *            the {@link AnnotationProcessingService} used to read
     *            annotations of the classes fields.
     */
    public final void setFieldAnnotationProcessingService(
            final AnnotationProcessingService annotationProcessingService) {
        _fieldAnnotationProcessingService = annotationProcessingService;
    }

    /**
     * Builds a new {@link ClassInfo} describing the given Class. Annotations for the class and its
     * fields are read using the {@link AnnotationProcessingService}s defined by
     * {@link #setClassAnnotationProcessingService(AnnotationProcessingService) and
     *
     * @link #setFieldAnnotationProcessingService(AnnotationProcessingService)} . The information is
     *       stored in the {@link ClassInfo} and its related {@link FieldInfo}s.
     *
     * @param type
     *            The Class Object representing the Class that shall be described.
     * @return a new {@link ClassInfo} describing the given Class.
     * @throws MappingException
     *          if annotation placement is invalid (field and property access for the same field).
     */
    public final ClassInfo buildClassInfo(final Class<?> type)
            throws MappingException {

        if (type == null) {
            throw new IllegalArgumentException("Argument type must not be null");
        }

        if (!isDescribeable(type)) {
            return null;
        }

        /*
         * create new ClassInfo and Nature
         */
        ClassInfo classInfo = new ClassInfo(type);
        classInfo.addNature(JPAClassNature.class.getName());
        JPAClassNature jpaClassNature = new JPAClassNature(classInfo);

        /*
         * process class annotations
         */
        _classAnnotationProcessingService.processAnnotations(jpaClassNature,
                type.getAnnotations());

        /*
         * process annotations for all declared (not inherited) fields
         */
        for (Field field : type.getDeclaredFields()) {
            if (field.getAnnotations().length != 0) {
                if (isDescribeable(type, field)) {
                    buildFieldInfo(classInfo, field);
                } else {
                    throw new MappingException(
                            "Invalid field annotated, field is not describeable!");
                }
            }
        }

        /*
         * process annotations for all declared (not inherited) getter methods
         */

        for (Method method : type.getDeclaredMethods()) {
            if (isGetter(method) && method.getAnnotations().length != 0) {
                if (isDescribeable(type, method)) {
                    buildFieldInfo(classInfo, method);
                } else {
                    throw new MappingException(
                            "Invalid method annotated, method is not describeable!");
                }
            }
        }

        return classInfo;
    }

    /**
     * Build a {@link FieldInfo} describing the field (accessed by the given
     * {@link Method}) by processing its annotations and add the generated
     * {@link FieldInfo} to the given {@link ClassInfo} (as field or key).
     *
     * @param classInfo
     *            the {@link ClassInfo} of the declaring class
     * @param method
     *            the {@link Method} used to access (get) the underlying member.
     * @throws MappingException
     *             if a FieldInfo with the same name already exists (usually
     *             when using field AND property access).
     */
    private void buildFieldInfo(final ClassInfo classInfo, final Method method)
            throws MappingException {
        if (classInfo == null) {
            throw new IllegalArgumentException(
                    "Argument classInfo must not be null.");
        }
        if (method == null) {
            throw new IllegalArgumentException(
                    "Argument method must not be null.");
        }
        String fieldName = getFieldNameFromGetterMethod(method);
        if (fieldName == null) {
            throw new IllegalArgumentException(
                    "Can not resolve Fieldname from method name.");
        }

        FieldInfo fieldInfo = classInfo.getFieldInfoByName(fieldName);
        if (fieldInfo == null) {
            fieldInfo = classInfo.getKeyFieldInfoByName(fieldName);
            if (fieldInfo != null) {
                throw new MappingException("Can not annotate field and method!");
            }
        }

        Class<?> fieldType = method.getReturnType();
        TypeInfo typeInfo = new TypeInfo(fieldType);
        FieldHandlerImpl fieldHandler;
        try {
            fieldHandler = new FieldHandlerImpl(fieldName, null, null, method,
                    getSetterMethodFromGetter(method), typeInfo);
        } catch (SecurityException e) {
            throw new MappingException("Setter method for field " + fieldName
                    + " is not accessible!");
        } catch (NoSuchMethodException e) {
            throw new MappingException("Setter method for field " + fieldName
                    + " does not exist!");
        }
        fieldInfo = new FieldInfo(classInfo, fieldType, fieldName, fieldHandler);

        fieldInfo.addNature(JPAFieldNature.class.getName());
        JPAFieldNature jpaFieldNature = new JPAFieldNature(fieldInfo);
        _fieldAnnotationProcessingService.processAnnotations(jpaFieldNature,
                method.getAnnotations());

        // if (jpaFieldNature.isId ()) {
        // classInfo.addKey (fieldInfo);
        // } else {
        classInfo.addFieldInfo(fieldInfo);
        // }
    }

    /**
     * Build a {@link FieldInfo} describing the given {@link Field} by
     * processing its annotations and add the generated {@link FieldInfo} to the
     * given {@link ClassInfo}.
     *
     * @param classInfo
     *            the {@link ClassInfo} of the declaring class
     * @param field
     *            the {@link Field} to describe.
     * @throws MappingException
     *             If the field is not public, is static or transient
     */
    private void buildFieldInfo(final ClassInfo classInfo, final Field field)
            throws MappingException {
        if (classInfo == null) {
            throw new IllegalArgumentException(
                    "Argument classInfo must not be null.");
        }

        if (field == null) {
            throw new IllegalArgumentException(
                    "Argument field must not be null.");
        }

        Class<?> fieldType = field.getType();
        TypeInfo typeInfo = new TypeInfo(fieldType);
        FieldHandlerImpl fieldHandler = new FieldHandlerImpl(field, typeInfo);
        FieldInfo fieldInfo = new FieldInfo(classInfo, fieldType, field
                .getName(), fieldHandler);

        fieldInfo.addNature(JPAFieldNature.class.getName());
        JPAFieldNature jpaFieldNature = new JPAFieldNature(fieldInfo);
        _fieldAnnotationProcessingService.processAnnotations(jpaFieldNature,
                field.getAnnotations());

        // if (jpaFieldNature.isId ()) {
        // classInfo.addKey (fieldInfo);
        // } else {
        classInfo.addFieldInfo(fieldInfo);
        // }
    }

    /**
     * Convenience method to check whether a {@link Method} is a getter method,
     * i.e. starts with "get" or "is".
     *
     * @param method
     *            the {@link Method} to check.
     * @return true if the methods name starts with "get" or "is" (Java Beans
     *         convention).
     */
    private boolean isGetter(final Method method) {
        if (method.getName().startsWith("get")
                || method.getName().startsWith("is")) {
            return true;
        }
        return false;
    }

    /**
     * Create a member name from a getter {@link Method}.
     *
     * @param method
     *            The getter {@link Method}
     * @return The name of the underlying member.
     */
    private String getFieldNameFromGetterMethod(final Method method) {
        if (!isGetter(method)) {
            throw new IllegalArgumentException("Method is not a getter method!");
        }

        if (method.getName().startsWith("get")) {
            String nameRest = method.getName().substring(4);
            return Character.toLowerCase(method.getName().charAt(3)) + nameRest;
        }
        if (method.getName().startsWith("is")) {
            String nameRest = method.getName().substring(3);
            return Character.toLowerCase(method.getName().charAt(2)) + nameRest;
        }
        throw new IllegalArgumentException(
                "Method name does not start with 'get' or 'is'!");
    }

    /**
     * Return a setter {@link Method} for a given getter {@link Method}.
     *
     * @param getter
     *            The getter {@link Method}
     * @return The setter Method for the given getter Method.
     * @throws NoSuchMethodException
     *             If the setter method does not exist
     * @throws SecurityException
     *             If the setter method is not accessible
     */
    private Method getSetterMethodFromGetter(final Method getter)
            throws SecurityException, NoSuchMethodException {
        if (!isGetter(getter)) {
            throw new IllegalArgumentException("Method is not a getter method!");
        }

        String setterName;
        if (getter.getName().startsWith("get")) {
            String name = getter.getName().substring(1);
            setterName = "s" + name;
        } else if (getter.getName().startsWith("is")) {
            String name = getter.getName().substring(2);
            setterName = "set" + name;
        } else {
            throw new IllegalArgumentException(
                    "Method name does not start with 'get' or 'is'!");
        }

        return getter.getDeclaringClass().getDeclaredMethod(setterName,
                getter.getReturnType());
    }

    /**
     * Checks whether a class is describable or not.
     *
     * @param type
     *            the class to check
     * @return false if the given type is Void, Object or Class - all other
     *         Classes are describable.
     */
    private boolean isDescribeable(final Class<?> type) {
        if (Object.class.equals(type) || Void.class.equals(type)
                || Class.class.equals(type)) {
            return false;
        }
        return true;
    }

    /**
     * Checks whether a field is describable or not. A field is NOT describable
     * if:
     * <ul>
     * <li>The declaring Class of the Field is NOT the given type (except for
     * fields defined by interfaces)</li>
     * <li>The field is synthetic</li>
     * <li>The field is static</li>
     * <li>The field is transient</li>
     * </ul>
     *
     * @param type
     *            the Class the field belongs to
     * @param field
     *            the field we want to check
     * @return true if the field is describable, false if not.
     */

    private boolean isDescribeable(final Class<?> type, final Field field) {
        boolean isDescribeable = true;
        Class<?> declaringClass = field.getDeclaringClass();
        if ((declaringClass != null) && !type.equals(declaringClass)
                && (!declaringClass.isInterface())) {
            isDescribeable = false;
        }
        if (Modifier.isStatic(field.getModifiers())) {
            isDescribeable &= false;
        }
        if (Modifier.isTransient(field.getModifiers())) {
            isDescribeable &= false;
        }
        if (field.isSynthetic()) {
            isDescribeable &= false;
        }
        return isDescribeable;
    }

    /**
     * Checks whether a {@link Method} is describable or not. A method is NOT
     * describable if:
     * <ul>
     * <li>The declaring Class of the method is NOT the given type (except for
     * methods defined by interfaces)</li>
     * <li>The method is synthetic</li>
     * <li>The method is static</li>
     * <li>The method is transient</li>
     * </ul>
     *
     * @param type
     *            the Class the method belongs to
     * @param method
     *            the {@link Method} we want to check
     * @return true if the method is describable, false if not.
     */
    private boolean isDescribeable(final Class<?> type, final Method method) {
        boolean isDescribeable = true;
        Class<?> declaringClass = method.getDeclaringClass();

        if ((declaringClass != null) && (!type.equals(declaringClass))
                && (!declaringClass.isInterface())) {
            isDescribeable = false;
        }
        if (method.isSynthetic()) {
            isDescribeable &= false;
        }
        if (Modifier.isStatic(method.getModifiers())) {
            isDescribeable &= false;
        }
        if (Modifier.isTransient(method.getModifiers())) {
            isDescribeable &= false;
        }
        return isDescribeable;
    }
}
TOP

Related Classes of org.castor.jdo.jpa.info.ClassInfoBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.