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

de.danielbechler.diff.introspection.PropertyAccessor Maven / Gradle / Ivy

There is a newer version: 0.95
Show newest version
/*
 * Copyright 2014 Daniel Bechler
 *
 * 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.
 */

package de.danielbechler.diff.introspection;

import de.danielbechler.diff.access.PropertyAwareAccessor;
import de.danielbechler.diff.selector.BeanPropertyElementSelector;
import de.danielbechler.util.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import static java.util.Arrays.asList;

/**
 * @author Daniel Bechler
 */
public class PropertyAccessor implements PropertyAwareAccessor
{
	private static final Logger logger = LoggerFactory.getLogger(PropertyAccessor.class);

	private final String propertyName;
	private final Class type;
	private final Method readMethod;
	private final Method writeMethod;

	public PropertyAccessor(final String propertyName, final Method readMethod, final Method writeMethod)
	{
		Assert.notNull(propertyName, "propertyName");
		Assert.notNull(readMethod, "readMethod");
		this.propertyName = propertyName;
		this.readMethod = makeAccessible(readMethod);
		this.writeMethod = makeAccessible(writeMethod);
		this.type = this.readMethod.getReturnType();
	}

	private static Method makeAccessible(final Method method)
	{
		if (method != null && !method.isAccessible())
		{
			logger.debug("Making method accessible: {}", method.toString());
			method.setAccessible(true);
		}
		return method;
	}

	public final Set getCategoriesFromAnnotation()
	{
		final ObjectDiffProperty annotation = readMethod.getAnnotation(ObjectDiffProperty.class);
		if (annotation != null)
		{
			return new TreeSet(asList(annotation.categories()));
		}
		return Collections.emptySet();
	}

	public boolean isExcludedByAnnotation()
	{
		final ObjectDiffProperty annotation = readMethod.getAnnotation(ObjectDiffProperty.class);
		return annotation != null && annotation.excluded();
	}

	public String getPropertyName()
	{
		return this.propertyName;
	}

	/**
	 * @return The annotations of the getter used to access this property.
	 */
	public Set getReadMethodAnnotations()
	{
		return new LinkedHashSet(asList(readMethod.getAnnotations()));
	}

	public  T getReadMethodAnnotation(final Class annotationClass)
	{
		final Set annotations = getReadMethodAnnotations();
		assert (annotations != null) : "Something is wrong here. " +
				"The contract of getReadAnnotations() guarantees a non-null return value.";
		for (final Annotation annotation : annotations)
		{
			if (annotationClass.isAssignableFrom(annotation.annotationType()))
			{
				return annotationClass.cast(annotation);
			}
		}
		return null;
	}

	public BeanPropertyElementSelector getElementSelector()
	{
		return new BeanPropertyElementSelector(this.propertyName);
	}

	public Object get(final Object target)
	{
		if (target == null)
		{
			return null;
		}
		try
		{
			return readMethod.invoke(target);
		}
		catch (final Exception cause)
		{
			throw new PropertyReadException(propertyName, target.getClass(), cause);
		}
	}

	public void set(final Object target, final Object value)
	{
		if (target == null)
		{
			logger.info("Couldn't set new value '{}' for property '{}' " +
					"because the target object is null", value, propertyName);
		}
		else if (writeMethod == null)
		{
			logger.debug("No setter found for property '{}'", propertyName);
			tryToReplaceContentOfCollectionTypes(target, value);
		}
		else
		{
			invokeWriteMethod(target, value);
		}
	}

	public void unset(final Object target)
	{
		set(target, null);
	}

	@SuppressWarnings("unchecked")
	private void tryToReplaceContentOfCollectionTypes(final Object target, final Object value)
	{
		if (Collection.class.isAssignableFrom(readMethod.getReturnType()))
		{
			if (tryToReplaceCollectionContent((Collection) get(target), (Collection) value))
			{
				return;
			}
		}
		if (Map.class.isAssignableFrom(readMethod.getReturnType()))
		{
			if (tryToReplaceMapContent((Map) get(target), (Map) value))
			{
				return;
			}
		}
		logger.info("Couldn't set new value '{}' for property '{}'", value, propertyName);
	}

	private void invokeWriteMethod(final Object target, final Object value)
	{
		try
		{
			writeMethod.invoke(target, value);
		}
		catch (final Exception cause)
		{
			throw new PropertyWriteException(propertyName, getType(), value, cause);
		}
	}

	private static boolean tryToReplaceCollectionContent(final Collection target,
														 final Collection value)
	{
		if (target == null)
		{
			return false;
		}
		try
		{
			target.clear();
			target.addAll(value);
			return true;
		}
		catch (final Exception unmodifiable)
		{
			logger.debug("Failed to replace content of existing Collection", unmodifiable);
			return false;
		}
	}

	private static boolean tryToReplaceMapContent(final Map target,
												  final Map value)
	{
		if (target == null)
		{
			return false;
		}
		try
		{
			target.clear();
			target.putAll(value);
			return true;
		}
		catch (final Exception unmodifiable)
		{
			logger.debug("Failed to replace content of existing Map", unmodifiable);
			return false;
		}
	}

	public Class getType()
	{
		return this.type;
	}

	@Override
	public String toString()
	{
		final StringBuilder sb = new StringBuilder("PropertyAccessor{");
		sb.append("propertyName='").append(propertyName).append('\'');
		sb.append(", type=").append(type.getCanonicalName());
		sb.append(", source=").append(readMethod.getDeclaringClass().getCanonicalName());
		sb.append(", hasWriteMethod=").append(writeMethod != null);
		sb.append('}');
		return sb.toString();
	}
}