All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nuiton.jaxx.compiler.beans.JAXXIntrospector Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * JAXX :: Compiler
 * %%
 * Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

package org.nuiton.jaxx.compiler.beans;

import org.nuiton.jaxx.compiler.reflect.ClassDescriptor;
import org.nuiton.jaxx.compiler.reflect.ClassDescriptorHelper;
import org.nuiton.jaxx.compiler.reflect.MethodDescriptor;

import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Map;

/**
 * Performs introspection on a ClassDescriptor.  Ideally, I could just have copied Sun's Introspector
 * and changed a few things, but the licensing terms are incompatible.  This implementation is incomplete -- it only
 * bothers to report info that JAXX actually checks.  It also relaxes some of Introspector's rules a bit, but I
 * don't believe it results in any meaningful incompatibilities.
 *
 * JAXX uses its own introspector rather than the built-in
 * java.beans.Introspector so that it can introspect {@link ClassDescriptor},
 * not just java.lang.Class.
 */
public class JAXXIntrospector {

    private final ClassDescriptor classDescriptor;

    private final Map propertyDescriptors = new HashMap<>();

    private final Map eventSetDescriptors = new HashMap<>();

    private JAXXIntrospector(ClassDescriptor classDescriptor) {
        this.classDescriptor = classDescriptor;
    }

    /**
     * Returns the JAXXBeanInfo for a given class.
     *
     * @param classDescriptor the class to introspect
     * @return the JAXXBeanInfo for the bean class
     */
    public static JAXXBeanInfo getJAXXBeanInfo(ClassDescriptor classDescriptor) {
        JAXXIntrospector introspector = new JAXXIntrospector(classDescriptor);
        return introspector.createBeanInfo();
    }

    private JAXXBeanInfo createBeanInfo() {
        ClassDescriptor explicitInfoClass = classDescriptor;
        BeanInfo explicitBeanInfo = null;
        while (explicitInfoClass != null) {
            explicitBeanInfo = getExplicitBeanInfo(explicitInfoClass);
            if (explicitBeanInfo != null) {
                break;
            }
            explicitInfoClass = explicitInfoClass.getSuperclass();
        }

        if (explicitBeanInfo != null) {
            PropertyDescriptor[] explicitProperties = explicitBeanInfo.getPropertyDescriptors();
            for (PropertyDescriptor explicitProperty : explicitProperties) {
                Class type = explicitProperty.getPropertyType();
                if (type == null) {
                    continue;
                }
                Method readMethod = explicitProperty.getReadMethod();
                Method writeMethod = explicitProperty.getWriteMethod();
                try {
                    ClassDescriptor typeDescriptor = null;
                    if (writeMethod != null) {
                        type = writeMethod.getParameterTypes()[0];
                        typeDescriptor = ClassDescriptorHelper.getClassDescriptor(writeMethod.getParameterTypes()[0].getName(), type.getClassLoader());
                    }
                    JAXXPropertyDescriptor propertyDescriptor = new JAXXPropertyDescriptor(classDescriptor, explicitProperty.getName(),
                                                                                           readMethod != null ? classDescriptor.getMethodDescriptor(readMethod.getName()) : null,
                                                                                           writeMethod != null ? classDescriptor.getMethodDescriptor(writeMethod.getName(), typeDescriptor) : null);
                    propertyDescriptor.setBound(explicitProperty.isBound());
                    Enumeration attributeNames = explicitProperty.attributeNames();
                    while (attributeNames.hasMoreElements()) {
                        String name = attributeNames.nextElement();
                        propertyDescriptor.setValue(name, explicitProperty.getValue(name));
                    }
                    propertyDescriptors.put(propertyDescriptor.getName(), propertyDescriptor);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException("Internal error: Could not find ClassDescriptor corresponding to Java " + type, e);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Internal error: Could not find expected MethodDescriptor in " + classDescriptor, e);
                }
            }
        }

        // if the class broadcasts PropertyChangeEvent, assume all properties are bound (java.beans.Introspector
        // does the same)
        boolean propertyChangeSource;
        try {
            classDescriptor.getMethodDescriptor("addPropertyChangeListener", ClassDescriptorHelper.getClassDescriptor(PropertyChangeListener.class));
            propertyChangeSource = true;
        } catch (NoSuchMethodException e) {
            propertyChangeSource = false;
        }

        MethodDescriptor[] methods = classDescriptor.getMethodDescriptors();
        for (MethodDescriptor method : methods) {
            String name = method.getName();
            if (name.startsWith("get") && name.length() > 3 && Character.isUpperCase(name.charAt(3)) && method.getParameterTypes().length == 0) {
                String propertyName = Introspector.decapitalize(name.substring(3));
                if (!propertyDescriptors.containsKey(propertyName)) {
                    propertyDescriptors.put(propertyName, new JAXXPropertyDescriptor(classDescriptor, propertyName, method, null, propertyChangeSource));
                }
            } else if (name.startsWith("is") && name.length() > 2 && Character.isUpperCase(name.charAt(2)) && method.getParameterTypes().length == 0) {
                String propertyName = Introspector.decapitalize(name.substring(2));
                if (!propertyDescriptors.containsKey(propertyName)) {
                    propertyDescriptors.put(propertyName, new JAXXPropertyDescriptor(classDescriptor, propertyName, method, null, propertyChangeSource));
                }
            } else if (name.startsWith("set") && name.length() > 3 && Character.isUpperCase(name.charAt(3)) && method.getParameterTypes().length == 1) {
                String propertyName = Introspector.decapitalize(name.substring(3));
                if (!propertyDescriptors.containsKey(propertyName)) {
                    propertyDescriptors.put(propertyName, new JAXXPropertyDescriptor(classDescriptor, propertyName, null, method, propertyChangeSource));
                }
            } else if (name.startsWith("add") && name.length() > 3 && Character.isUpperCase(name.charAt(3))) {
                ClassDescriptor[] parameters = method.getParameterTypes();
                if (parameters.length != 1 || !ClassDescriptorHelper.getClassDescriptor(EventListener.class).isAssignableFrom(parameters[0])) {
                    continue; // not an event listener method
                }
                try {
                    String eventSetName = method.getName().substring(3);
                    MethodDescriptor remove = classDescriptor.getMethodDescriptor("remove" + eventSetName, parameters);
                    eventSetDescriptors.put(eventSetName, new JAXXEventSetDescriptor(classDescriptor, eventSetName, method, remove, parameters[0].getMethodDescriptors()));
                } catch (NoSuchMethodException e) {
                    // no matching remove method, not a valid event
                }
            }
        }

        JAXXBeanDescriptor beanDescriptor = new JAXXBeanDescriptor(classDescriptor);
        if (explicitBeanInfo != null) {
            BeanDescriptor explicitBeanDescriptor = explicitBeanInfo.getBeanDescriptor();
            if (explicitBeanDescriptor != null) {
                Enumeration attributeNames =
                        explicitBeanDescriptor.attributeNames();
                while (attributeNames.hasMoreElements()) {
                    String name = attributeNames.nextElement();
                    Object value = explicitBeanDescriptor.getValue(name);
                    if ("containerDelegate".equals(name) && "".equals(value)) {
                        // Since java > 8 this returns an empty value and then breaks jaxx compiler
                        continue;
                    }
                    beanDescriptor.setValue(name, value);
                }
            }
        }

        return new JAXXBeanInfo(beanDescriptor,
                                propertyDescriptors.values().toArray(new JAXXPropertyDescriptor[0]),
                                eventSetDescriptors.values().toArray(new JAXXEventSetDescriptor[0]));
    }

    private static BeanInfo getExplicitBeanInfo(ClassDescriptor classDescriptor) {
        try {
            Class beanClass = Class.forName(classDescriptor.getName(), true, classDescriptor.getClassLoader()); // see if there is a class by that name in this package
            return Introspector.getBeanInfo(beanClass);
//            Method findExplicitBeanInfo = Introspector.class.getDeclaredMethod("findExplicitBeanInfo", Class.class);
//            findExplicitBeanInfo.setAccessible(true);
//            return (BeanInfo) findExplicitBeanInfo.invoke(null, beanClass);
        } catch (ClassNotFoundException | NoClassDefFoundError e) {
            return null; // happens for uncompiled classes
//        } catch (NoSuchMethodException e) {
//            throw new RuntimeException("Error: could not find method 'findExplicitBeanInfo' in java.beans.Introspector.  You are most likely running a version of Java against which JAXX has not been tested.");
//        } catch (InvocationTargetException | IllegalAccessException e) {
//            throw new RuntimeException(e);
        } catch (IntrospectionException e) {
            throw new RuntimeException(e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy