/*
* Copyright (c) 2009, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* SQL Power Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.testutil;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.beanutils.PropertyUtils;
import ca.sqlpower.dao.SPPersister;
import ca.sqlpower.object.SPObject;
import ca.sqlpower.object.annotation.Accessor;
import ca.sqlpower.object.annotation.Mutator;
import ca.sqlpower.object.annotation.NonBound;
import ca.sqlpower.object.annotation.NonProperty;
import ca.sqlpower.object.annotation.Transient;
import junit.framework.TestCase;
public class TestUtils extends TestCase {
public static void testPropertiesFireEvents(Object target, Collection<String> propertiesToIgnore, NewValueMaker valueMaker) {
}
/**
* Sets all the settable properties on the given target object which are not
* in the given ignore set.
*
* @param target
* The object to change the properties of
* @param propertiesToIgnore
* The properties of target not to modify or read
* @return A Map describing the new values of all the non-ignored, readable
* properties in target.
*/
public static Map<String,Object> setAllInterestingProperties(Object target,
Set<String> propertiesToIgnore) throws Exception {
return setAllInterestingProperties(target, propertiesToIgnore,
new GenericNewValueMaker(new SPObjectRoot()));
}
/**
* Sets all the settable properties on the given target object which are not
* in the given ignore set.
* <p>
* TODO merge this with what is in Architect's TestUtils class. This was
* originally refactored out of there.
*
* @param target
* The object to change the properties of
* @param propertiesToIgnore
* The properties of target not to modify or read
* @return A Map describing the new values of all the non-ignored, readable
* properties in target.
*/
public static Map<String,Object> setAllInterestingProperties(Object target,
Set<String> propertiesToIgnore, NewValueMaker valueMaker) throws Exception {
PropertyDescriptor props[] = PropertyUtils.getPropertyDescriptors(target);
for (int i = 0; i < props.length; i++) {
Object oldVal = null;
if (PropertyUtils.isReadable(target, props[i].getName()) &&
props[i].getReadMethod() != null &&
!propertiesToIgnore.contains(props[i].getName())) {
oldVal = PropertyUtils.getProperty(target, props[i].getName());
}
if (PropertyUtils.isWriteable(target, props[i].getName()) &&
props[i].getWriteMethod() != null &&
!propertiesToIgnore.contains(props[i].getName())) {
Object newVal = valueMaker.makeNewValue(props[i].getPropertyType(), oldVal, props[i].getName());
System.out.println("Changing property \""+props[i].getName()+"\" to \""+newVal+"\"");
PropertyUtils.setProperty(target, props[i].getName(), newVal);
}
}
// read them all back at the end in case there were dependencies between properties
return TestUtils.getAllInterestingProperties(target, propertiesToIgnore);
}
/**
* Gets all the settable properties on the given target object
* which are not in the given ignore set, and stuffs them into a Map.
*
* @param target The object to change the properties of
* @param propertiesToIgnore The properties of target not to modify or read
* @return The aforementioned stuffed map
*/
public static Map<String, Object> getAllInterestingProperties(Object target, Set<String> propertiesToIgnore) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Map<String,Object> newDescription = new HashMap<String,Object>();
PropertyDescriptor[] props = PropertyUtils.getPropertyDescriptors(target);
for (int i = 0; i < props.length; i++) {
if (PropertyUtils.isReadable(target, props[i].getName()) &&
props[i].getReadMethod() != null &&
!propertiesToIgnore.contains(props[i].getName())) {
newDescription.put(props[i].getName(),
PropertyUtils.getProperty(target, props[i].getName()));
}
}
return newDescription;
}
/**
* Returns the set of property names which have both a getter and a setter
* method and are annotated to be persisted through the {@link SPPersister}
* classes.
*
* @param objectUnderTest
* The object that contains the persistable properties we want to
* find.
* @param includeTransient
* If true the properties marked as transient will also be
* included. If false only the properties that are persisted and
* not transient are returned.
* @param includeConstructorMutators
* If true the properties that have getters but can only be set
* through a constructor due to being final will be included. If
* false the persisted properties provided must have a setter.
*/
public static Set<String> findPersistableBeanProperties(SPObject objectUnderTest, boolean includeTransient, boolean includeConstructorMutators) throws Exception {
return findPersistableBeanProperties(objectUnderTest.getClass(), includeTransient, includeConstructorMutators);
}
/**
* Returns the set of property names which have both a getter and a setter
* method and are annotated to be persisted through the {@link SPPersister}
* classes.
*
* @param objectUnderTest
* The object that contains the persistable properties we want to
* find.
* @param includeTransient
* If true the properties marked as transient will also be
* included. If false only the properties that are persisted and
* not transient are returned.
* @param includeConstructorMutators
* If true the properties that have getters but can only be set
* through a constructor due to being final will be included. If
* false the persisted properties provided must have a setter.
*/
public static Set<String> findPersistableBeanProperties(Class<? extends SPObject> objectUnderTest, boolean includeTransient, boolean includeConstructorMutators) throws Exception {
Set<String> getters = new HashSet<String>();
Set<String> setters = new HashSet<String>();
for (Method m : objectUnderTest.getMethods()) {
if (m.getName().equals("getClass")) continue;
//skip non-public methods as they are not visible for persisting anyways.
if (!Modifier.isPublic(m.getModifiers())) continue;
//skip static methods
if (Modifier.isStatic(m.getModifiers())) continue;
if (m.getName().startsWith("get") || m.getName().startsWith("is")) {
Class<?> parentClass = objectUnderTest;
boolean accessor = false;
boolean ignored = false;
boolean isTransient = false;
parentClass.getMethod(m.getName(), m.getParameterTypes());//test
while (parentClass != null) {
Method parentMethod;
try {
parentMethod = parentClass.getMethod(m.getName(), m.getParameterTypes());
} catch (NoSuchMethodException e) {
parentClass = parentClass.getSuperclass();
continue;
}
if (parentMethod.getAnnotation(Accessor.class) != null) {
accessor = true;
if (parentMethod.getAnnotation(Transient.class) != null) {
isTransient = true;
}
break;
} else if (parentMethod.getAnnotation(NonProperty.class) != null ||
parentMethod.getAnnotation(NonBound.class) != null) {
ignored = true;
break;
}
parentClass = parentClass.getSuperclass();
}
if (accessor) {
if (includeTransient || !isTransient) {
if (m.getName().startsWith("get")) {
getters.add(m.getName().substring(3));
} else if (m.getName().startsWith("is")) {
getters.add(m.getName().substring(2));
}
}
} else if (ignored) {
//ignored so skip
} else {
fail("The method " + m.getName() + " of " + objectUnderTest.toString() + " is a getter that is not annotated " +
"to be an accessor or transient. The exiting annotations are " +
Arrays.toString(m.getAnnotations()));
}
} else if (m.getName().startsWith("set")) {
if (m.getAnnotation(Mutator.class) != null) {
if ((includeTransient || m.getAnnotation(Transient.class) == null)
&& (includeConstructorMutators || !m.getAnnotation(Mutator.class).constructorMutator())) {
setters.add(m.getName().substring(3));
}
} else if (m.getAnnotation(NonProperty.class) != null ||
m.getAnnotation(NonBound.class) != null) {
//ignored so skip and pass
} else {
fail("The method " + m.getName() + " is a setter that is not annotated " +
"to be a mutator or transient.");
}
}
}
Set<String> beanNames = new HashSet<String>();
for (String beanName : getters) {
if (setters.contains(beanName)) {
String firstLetter = new String(new char[]{beanName.charAt(0)});
beanNames.add(beanName.replaceFirst(firstLetter, firstLetter.toLowerCase()));
}
}
return beanNames;
}
}