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

com.fitbur.assertj.util.introspection.PropertySupport Maven / Gradle / Ivy

There is a newer version: 1.0.0
Show newest version
/**
 * 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.
 *
 * Copyright 2012-2016 the original author or authors.
 */
package com.fitbur.assertj.util.introspection;

import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static com.fitbur.assertj.util.IterableUtil.isNullOrEmpty;
import static com.fitbur.assertj.util.introspection.Introspection.getProperty;

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.List;

import com.fitbur.assertj.util.VisibleForTesting;

/**
 * Utility methods for properties access.
 * 
 * @author Joel Costigliola
 * @author Alex Ruiz
 * @author Nicolas François
 * @author Florent Biville
 */
public class PropertySupport {

  private static final String SEPARATOR = ".";

  private static final PropertySupport INSTANCE = new PropertySupport();

  /**
   * Returns the singleton instance of this class.
   * 
   * @return the singleton instance of this class.
   */
  public static PropertySupport instance() {
    return INSTANCE;
  }

  @VisibleForTesting
  JavaBeanDescriptor javaBeanDescriptor = new JavaBeanDescriptor();

  @VisibleForTesting
  PropertySupport() {
  }

  /**
   * Returns a {@link List} containing the values of the given property name, from the elements of the
   * given {@link Iterable}. If the given {@code Iterable} is empty or {@code null}, this method will
   * return an empty {@code List}. This method supports nested properties (e.g. "address.street.number").
   * 
   * @param propertyName the name of the property. It may be a nested property. It is left to the clients to validate
   *          for {@code null} or empty.
   * @param target the given {@code Iterable}.
   * @return an {@code Iterable} containing the values of the given property name, from the elements of the given
   *         {@code Iterable}.
   * @throws IntrospectionError if an element in the given {@code Iterable} does not have a property with a matching
   *           name.
   */
  public  List propertyValues(String propertyName, Class clazz, Iterable target) {
    if (isNullOrEmpty(target)) {
      return emptyList();
    }
    if (isNestedProperty(propertyName)) {
      String firstPropertyName = popPropertyNameFrom(propertyName);
      Iterable propertyValues = propertyValues(firstPropertyName, Object.class, target);
      // extract next sub-property values until reaching the last sub-property
      return propertyValues(nextPropertyNameFrom(propertyName), clazz, propertyValues);
    }
    return simplePropertyValues(propertyName, clazz, target);
  }

  /**
   * Static variant of {@link #propertyValueOf(String, Class, Object)} for syntactic sugar.
   * 

* * @param propertyName the name of the property. It may be a nested property. It is left to the clients to validate * for {@code null} or empty. * @param target the given object * @param clazz type of property * @return a the values of the given property name * @throws IntrospectionError if the given target does not have a property with a matching name. */ public static T propertyValueOf(String propertyName, Object target, Class clazz) { return instance().propertyValueOf(propertyName, clazz, target); } private List simplePropertyValues(String propertyName, Class clazz, Iterable target) { List propertyValues = new ArrayList<>(); for (Object e : target) { propertyValues.add(e == null ? null : propertyValue(propertyName, clazz, e)); } return unmodifiableList(propertyValues); } private String popPropertyNameFrom(String propertyNameChain) { if (!isNestedProperty(propertyNameChain)) { return propertyNameChain; } return propertyNameChain.substring(0, propertyNameChain.indexOf(SEPARATOR)); } private String nextPropertyNameFrom(String propertyNameChain) { if (!isNestedProperty(propertyNameChain)) { return ""; } return propertyNameChain.substring(propertyNameChain.indexOf(SEPARATOR) + 1); } /** *

 isNestedProperty("address.street"); // true
   * isNestedProperty("address.street.name"); // true
   * isNestedProperty("person"); // false
   * isNestedProperty(".name"); // false
   * isNestedProperty("person."); // false
   * isNestedProperty("person.name."); // false
   * isNestedProperty(".person.name"); // false
   * isNestedProperty("."); // false
   * isNestedProperty(""); // false
*/ private boolean isNestedProperty(String propertyName) { return propertyName.contains(SEPARATOR) && !propertyName.startsWith(SEPARATOR) && !propertyName.endsWith(SEPARATOR); } /** * Return the value of a simple property from a target object. *

* This only works for simple property, nested property are not supported ! use * {@link #propertyValueOf(String, Class, Object)} * * @param propertyName the name of the property. It may be a nested property. It is left to the clients to validate * for {@code null} or empty. * @param target the given object * @param clazz type of property * @return a the values of the given property name * @throws IntrospectionError if the given target does not have a property with a matching name. */ public T propertyValue(String propertyName, Class clazz, Object target) { PropertyDescriptor descriptor = getProperty(propertyName, target); try { return clazz.cast(javaBeanDescriptor.invokeReadMethod(descriptor, target)); } catch (ClassCastException e) { String msg = format("Unable to obtain the value of the property <'%s'> from <%s> - wrong property type specified <%s>", propertyName, target, clazz); throw new IntrospectionError(msg, e); } catch (Exception unexpected) { String msg = format("Unable to obtain the value of the property <'%s'> from <%s>", propertyName, target); throw new IntrospectionError(msg, unexpected); } } /** * Returns the value of the given property name given target. If the given object is {@code null}, this method will * return null.
* This method supports nested properties (e.g. "address.street.number"). * * @param propertyName the name of the property. It may be a nested property. It is left to the clients to validate * for {@code null} or empty. * @param clazz the class of property. * @param target the given Object to extract property from. * @return the value of the given property name given target. * @throws IntrospectionError if target object does not have a property with a matching name. * @throws IllegalArgumentException if propertyName is null. */ public T propertyValueOf(String propertyName, Class clazz, Object target) { if (propertyName == null) throw new IllegalArgumentException("the property name should not be null."); // returns null if target is null as we can't extract a property from a null object // but don't want to raise an exception if we were looking at a nested property if (target == null) return null; if (isNestedProperty(propertyName)) { String firstPropertyName = popPropertyNameFrom(propertyName); Object propertyValue = propertyValue(firstPropertyName, Object.class, target); // extract next sub-property values until reaching the last sub-property return propertyValueOf(nextPropertyNameFrom(propertyName), clazz, propertyValue); } return propertyValue(propertyName, clazz, target); } /** * just delegates to {@link #propertyValues(String, Class, Iterable)} with Class being Object.class */ public List propertyValues(String fieldOrPropertyName, Iterable objects) { return propertyValues(fieldOrPropertyName, Object.class, objects); } public boolean publicGetterExistsFor(String fieldName, Object actual) { try { getProperty(fieldName, actual); } catch (IntrospectionError e) { return false; } return true; } }