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}