001    /*
002     * Created on Dec 22, 2007
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005     * in compliance with the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License
010     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011     * or implied. See the License for the specific language governing permissions and limitations under
012     * the License.
013     *
014     * Copyright @2007-2010 the original author or authors.
015     */
016    package org.fest.swing.format;
017    
018    import static org.fest.swing.exception.ActionFailedException.actionFailure;
019    import static org.fest.util.Collections.list;
020    import static org.fest.util.Strings.concat;
021    import static org.fest.util.Strings.quote;
022    
023    import java.awt.Component;
024    import java.beans.*;
025    import java.util.*;
026    
027    import org.fest.util.Arrays;
028    
029    /**
030     * Understands a formatter that uses
031     * <a href="http://java.sun.com/docs/books/tutorial/javabeans/introspection/" target="_blank">introspection</a>
032     * to display property values of a <code>{@link Component}</code>. This formatter does not support nested properties.
033     *
034     * @author Alex Ruiz
035     */
036    public final class IntrospectionComponentFormatter extends ComponentFormatterTemplate {
037    
038      private final Class<? extends Component> targetType;
039      private final List<String> propertyNames;
040    
041      private final Map<String, PropertyDescriptor> descriptors = new HashMap<String, PropertyDescriptor>();
042    
043      /**
044       * Creates a new </code>{@link IntrospectionComponentFormatter}</code>.
045       * @param targetType the type of <code>Component</code> that this formatter supports.
046       * @param propertyNames the property names to show as the <code>String</code> representation of a given
047       * <code>Component</code>.
048       * @throws NullPointerException if <code>targetType</code> is <code>null</code>.
049       */
050      public IntrospectionComponentFormatter(Class<? extends Component> targetType, String...propertyNames) {
051        if (targetType == null) throw new NullPointerException("targetType should not be null");
052        this.targetType = targetType;
053        this.propertyNames = list(propertyNames);
054        populate();
055      }
056    
057      private void populate() {
058        BeanInfo beanInfo = null;
059        try {
060          beanInfo = Introspector.getBeanInfo(targetType, Object.class);
061        } catch (Exception e) { 
062          throw actionFailure(concat("Unable to get BeanInfo for type ", targetType.getName()), e);
063        }
064        for (PropertyDescriptor d : beanInfo.getPropertyDescriptors()) register(d);
065      }
066    
067      private void register(PropertyDescriptor d) {
068        String name = d.getName();
069        if (!propertyNames.contains(name)) return;
070        descriptors.put(name, d);
071      }
072    
073      /**
074       * Returns a <code>String</code> representation of the given <code>{@link Component}</code>, showing only the
075       * properties specified in this formatter's
076       * <code>{@link #IntrospectionComponentFormatter(Class, String...) constructor}</code>.
077       * @param c the given <code>Component</code>.
078       * @return a <code>String</code> representation of the given <code>Component</code>.
079       * @throws NullPointerException if the given <code>Component</code> is <code>null</code>.
080       * @throws IllegalArgumentException if the type of the given <code>Component</code> is not supported by this 
081       * formatter.
082       * @see #targetType()
083       */
084      protected String doFormat(Component c) {
085        StringBuilder b = new StringBuilder();
086        b.append(c.getClass().getName()).append("[");
087        int max = propertyNames.size() - 1;
088        for (int i = 0; i <= max; i++) {
089          appendProperty(b, propertyNames.get(i), c);
090          if (i < max) b.append(", ");
091        }
092        b.append("]");
093        return b.toString();
094      }
095    
096      private void appendProperty(StringBuilder b, String name, Component c) {
097        b.append(name).append("=");
098        try {
099          b.append(propertyValue(c, name));
100        } catch (Exception e) {
101          b.append(concat("<Unable to read property [", e.getClass().getName(), ": ", quote(e.getMessage()), "]>"));
102        }
103      }
104    
105      private Object propertyValue(Component c, String property) throws Exception {
106        if ("showing".equals(property)) return c.isShowing();
107        PropertyDescriptor descriptor = descriptors.get(property);
108        Object value = descriptor.getReadMethod().invoke(c);
109        if (isOneDimensionalArray(value)) return Arrays.format(value);
110        return quote(value);
111      }
112    
113      private boolean isOneDimensionalArray(Object o) {
114        return o != null && o.getClass().isArray() && !o.getClass().getComponentType().isArray();
115      }
116    
117      /**
118       * Returns the type of <code>{@link Component}</code> this formatter supports.
119       * @return the type of <code>Component</code> this formatter supports.
120       */
121      public Class<? extends Component> targetType() { return targetType; }
122    
123      @Override public String toString() {
124        return concat(
125            getClass().getName(), "[",
126            "propertyNames=", propertyNames,
127            "]"
128        );
129      }
130    }