    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.jxpath;
    019
    020import java.beans.BeanInfo;
    021import java.beans.IntrospectionException;
    022import java.beans.Introspector;
    023import java.beans.PropertyDescriptor;
    024import java.util.Arrays;
    025import java.util.Comparator;
    026import java.util.HashMap;
    027
    028/**
    029 * An implementation of JXPathBeanInfo based on JavaBeans' BeanInfo. Properties advertised by JXPathBasicBeanInfo are the same as those advertised by BeanInfo
    030 * for the corresponding class.
    031 *
    032 * @see java.beans.BeanInfo
    033 * @see java.beans.Introspector
    034 */
    035public class JXPathBasicBeanInfo implements JXPathBeanInfo {
    036
    037    private static final long serialVersionUID = -3863803443111484155L;
    038    private static final Comparator<PropertyDescriptor> PROPERTY_DESCRIPTOR_COMPARATOR = Comparator.comparing(PropertyDescriptor::getName);
    039    /**
    040     * Whether objects of this class are treated as atomic objects which have no properties of their own.
    041     */
    042    private boolean atomic;
    043
    044    /**
    045     * Bean class.
    046     */
    047    private final Class clazz;
    048
    049    /**
    050     * The DynamicPropertyHandler class for a dynamic class.
    051     */
    052    private Class dynamicPropertyHandlerClass;
    053
    054    /**
    055     * List of property descriptors for the beans described by this bean info object.
    056     */
    057    private transient PropertyDescriptor[] propertyDescriptors;
    058
    059    /**
    060     * PropertyDescriptor for the specified name or null if there is no such property.
    061     */
    062    private transient HashMap<String, PropertyDescriptor> propertyDescriptorMap;
    063
    064    /**
    065     * Constructs a new JXPathBasicBeanInfo.
    066     *
    067     * @param clazz bean class
    068     */
    069    public JXPathBasicBeanInfo(final Class clazz) {
    070        this.clazz = clazz;
    071    }
    072
    073    /**
    074     * Constructs a new JXPathBasicBeanInfo.
    075     *
    076     * @param clazz  bean class
    077     * @param atomic whether objects of this class are treated as atomic objects which have no properties of their own.
    078     */
    079    public JXPathBasicBeanInfo(final Class clazz, final boolean atomic) {
    080        this.clazz = clazz;
    081        this.atomic = atomic;
    082    }
    083
    084    /**
    085     * Constructs a new JXPathBasicBeanInfo.
    086     *
    087     * @param clazz                       bean class
    088     * @param dynamicPropertyHandlerClass dynamic property handler class
    089     */
    090    public JXPathBasicBeanInfo(final Class clazz, final Class dynamicPropertyHandlerClass) {
    091        this.clazz = clazz;
    092        this.atomic = false;
    093        this.dynamicPropertyHandlerClass = dynamicPropertyHandlerClass;
    094    }
    095
    096    /**
    097     * Gets the DynamicPropertyHandler class for a dynamic class.
    098     *
    099     * @return the DynamicPropertyHandler class for a dynamic class.
    100     */
    101    @Override
    102    public Class getDynamicPropertyHandlerClass() {
    103        return dynamicPropertyHandlerClass;
    104    }
    105
    106    @Override
    107    public synchronized PropertyDescriptor getPropertyDescriptor(final String propertyName) {
    108        if (propertyDescriptorMap == null) {
    109            propertyDescriptorMap = new HashMap<>();
    110            final PropertyDescriptor[] pds = getPropertyDescriptors();
    111            for (final PropertyDescriptor pd : pds) {
    112                propertyDescriptorMap.put(pd.getName(), pd);
    113            }
    114        }
    115        return propertyDescriptorMap.get(propertyName);
    116    }
    117
    118    @Override
    119    public synchronized PropertyDescriptor[] getPropertyDescriptors() {
    120        if (propertyDescriptors == null) {
    121            if (clazz == Object.class) {
    122                propertyDescriptors = new PropertyDescriptor[0];
    123            } else {
    124                try {
    125                    BeanInfo bi;
    126                    if (clazz.isInterface()) {
    127                        bi = Introspector.getBeanInfo(clazz);
    128                    } else {
    129                        bi = Introspector.getBeanInfo(clazz, Object.class);
    130                    }
    131                    final PropertyDescriptor[] pds = bi.getPropertyDescriptors();
    132                    final PropertyDescriptor[] descriptors = new PropertyDescriptor[pds.length];
    133                    System.arraycopy(pds, 0, descriptors, 0, pds.length);
    134                    Arrays.sort(descriptors, PROPERTY_DESCRIPTOR_COMPARATOR);
    135                    propertyDescriptors = descriptors;
    136                } catch (final IntrospectionException ex) {
    137                    ex.printStackTrace();
    138                    return new PropertyDescriptor[0];
    139                }
    140            }
    141        }
    142        if (propertyDescriptors.length == 0) {
    143            return propertyDescriptors;
    144        }
    145        final PropertyDescriptor[] result = new PropertyDescriptor[propertyDescriptors.length];
    146        System.arraycopy(propertyDescriptors, 0, result, 0, propertyDescriptors.length);
    147        return result;
    148    }
    149
    150    /**
    151     * Tests whether objects of this class are treated as atomic objects which have no properties of their own.
    152     *
    153     * @return whether objects of this class are treated as atomic objects which have no properties of their own.
    154     */
    155    @Override
    156    public boolean isAtomic() {
    157        return atomic;
    158    }
    159
    160    /**
    161     * Return true if the corresponding objects have dynamic properties.
    162     *
    163     * @return boolean
    164     */
    165    @Override
    166    public boolean isDynamic() {
    167        return dynamicPropertyHandlerClass != null;
    168    }
    169
    170    @Override
    171    public String toString() {
    172        final StringBuilder buffer = new StringBuilder();
    173        buffer.append("BeanInfo [class = ");
    174        buffer.append(clazz.getName());
    175        if (isDynamic()) {
    176            buffer.append(", dynamic");
    177        }
    178        if (isAtomic()) {
    179            buffer.append(", atomic");
    180        }
    181        buffer.append(", properties = ");
    182        final PropertyDescriptor[] jpds = getPropertyDescriptors();
    183        for (final PropertyDescriptor jpd : jpds) {
    184            buffer.append("\n    ");
    185            buffer.append(jpd.getPropertyType());
    186            buffer.append(": ");
    187            buffer.append(jpd.getName());
    188        }
    189        buffer.append("]");
    190        return buffer.toString();
    191    }
    192}