cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aadamc...@apache.org
Subject cayenne git commit: CAY-1998 - Speeding up PropertyUtils
Date Thu, 02 Apr 2015 17:57:06 GMT
Repository: cayenne
Updated Branches:
  refs/heads/master cc3dba58a -> edc8975c5


CAY-1998 - Speeding up PropertyUtils

* replacing calls to java.beans.Introspector with our own concurrent map...
  Introspector is synchronized and is very slow in multi-threaded environment


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/edc8975c
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/edc8975c
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/edc8975c

Branch: refs/heads/master
Commit: edc8975c5c94db3a9b8e2b393d75b7cb3f3741ab
Parents: cc3dba5
Author: aadamchik <aadamchik@apache.org>
Authored: Thu Apr 2 16:37:31 2015 +0300
Committer: aadamchik <aadamchik@apache.org>
Committed: Thu Apr 2 19:07:08 2015 +0300

----------------------------------------------------------------------
 .../apache/cayenne/reflect/BeanAccessor.java    | 182 ++++++------
 .../org/apache/cayenne/reflect/MapAccessor.java |  52 ++++
 .../apache/cayenne/reflect/PropertyUtils.java   | 274 ++++++-------------
 .../org/apache/cayenne/CayenneDataObjectIT.java | 260 +++++++++---------
 .../org/apache/cayenne/exp/PropertyTest.java    |  25 +-
 .../cayenne/reflect/PropertyUtilsTest.java      |  53 +++-
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   1 +
 7 files changed, 414 insertions(+), 433 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/edc8975c/cayenne-server/src/main/java/org/apache/cayenne/reflect/BeanAccessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/reflect/BeanAccessor.java b/cayenne-server/src/main/java/org/apache/cayenne/reflect/BeanAccessor.java
index 3825879..bb5db57 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/reflect/BeanAccessor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/reflect/BeanAccessor.java
@@ -19,100 +19,106 @@
 
 package org.apache.cayenne.reflect;
 
-import java.beans.IntrospectionException;
-import java.beans.PropertyDescriptor;
 import java.lang.reflect.Method;
 
 /**
- * A property accessor that uses set/get methods following JavaBean naming conventions.
+ * A property accessor that uses set/get methods following JavaBean naming
+ * conventions.
  * 
  * @since 1.2
  */
 public class BeanAccessor implements Accessor {
 
-    protected String propertyName;
-    protected Method readMethod;
-    protected Method writeMethod;
-    protected Object nullValue;
-
-    public BeanAccessor(Class<?> objectClass, String propertyName, Class<?> propertyType)
{
-        if (objectClass == null) {
-            throw new IllegalArgumentException("Null objectClass");
-        }
-
-        if (propertyName == null) {
-            throw new IllegalArgumentException("Null propertyName");
-        }
-
-        this.propertyName = propertyName;
-        this.nullValue = PropertyUtils.defaultNullValueForType(propertyType);
-
-        try {
-            PropertyDescriptor descriptor = new PropertyDescriptor(
-                    propertyName,
-                    objectClass);
-
-            this.readMethod = descriptor.getReadMethod();
-            this.writeMethod = descriptor.getWriteMethod();
-        }
-        catch (IntrospectionException e) {
-            throw new PropertyException("Invalid bean property: " + propertyName, this, e);
-        }
-    }
-
-    public String getName() {
-        return propertyName;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public Object getValue(Object object) throws PropertyException {
-        if (readMethod == null) {
-            throw new PropertyException(
-                    "Property '" + propertyName + "' is not readable",
-                    this,
-                    object);
-        }
-
-        try {
-            return readMethod.invoke(object, (Object[]) null);
-        }
-        catch (Throwable th) {
-            throw new PropertyException(
-                    "Error reading property: " + propertyName,
-                    this,
-                    object,
-                    th);
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    public void setValue(Object object, Object newValue) throws PropertyException {
-
-        if (writeMethod == null) {
-            throw new PropertyException(
-                    "Property '" + propertyName + "' is not writable",
-                    this,
-                    object);
-        }
-
-        // this will take care of primitives.
-        if (newValue == null) {
-            newValue = this.nullValue;
-        }
-
-        try {
-            writeMethod.invoke(object, newValue);
-        }
-        catch (Throwable th) {
-            throw new PropertyException(
-                    "Error reading property: " + propertyName,
-                    this,
-                    object,
-                    th);
-        }
-    }
+	private static final long serialVersionUID = 606253801447018099L;
+
+	protected String propertyName;
+	protected Method readMethod;
+	protected Method writeMethod;
+	protected Object nullValue;
+
+	public BeanAccessor(Class<?> objectClass, String propertyName, Class<?> propertyType)
{
+		if (objectClass == null) {
+			throw new IllegalArgumentException("Null objectClass");
+		}
+
+		if (propertyName == null) {
+			throw new IllegalArgumentException("Null propertyName");
+		}
+
+		if (propertyName.length() == 0) {
+			throw new IllegalArgumentException("Empty propertyName");
+		}
+
+		this.propertyName = propertyName;
+		this.nullValue = PropertyUtils.defaultNullValueForType(propertyType);
+
+		String capitalized = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
+
+		try {
+			this.readMethod = objectClass.getMethod("get" + capitalized);
+		} catch (NoSuchMethodException e) {
+
+			// try boolean
+			try {
+				Method readMethod = objectClass.getMethod("is" + capitalized);
+				this.readMethod = (readMethod.getReturnType().equals(Boolean.TYPE)) ? readMethod : null;
+			} catch (NoSuchMethodException e1) {
+				// not readable...
+			}
+		}
+
+		if (readMethod == null) {
+			throw new IllegalArgumentException("Property '" + propertyName + "' is not readbale");
+		}
+
+		// TODO: compare 'propertyType' arg with readMethod.getReturnType()
+
+		try {
+			this.writeMethod = objectClass.getMethod("set" + capitalized, readMethod.getReturnType());
+		} catch (NoSuchMethodException e) {
+			// read-only is supported...
+		}
+	}
+
+	public String getName() {
+		return propertyName;
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public Object getValue(Object object) throws PropertyException {
+
+		try {
+			return readMethod.invoke(object, (Object[]) null);
+		} catch (Throwable th) {
+			throw new PropertyException("Error reading property: " + propertyName, this, object, th);
+		}
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	public void setValue(Object object, Object newValue) throws PropertyException {
+
+		if (writeMethod == null) {
+			throw new PropertyException("Property '" + propertyName + "' is not writable", this, object);
+		}
+
+		Class type = writeMethod.getParameterTypes()[0];
+		Converter<?> converter = ConverterFactory.factory.getConverter(type);
+		newValue = (converter != null) ? converter.convert(newValue, type) : newValue;
+
+		// this will take care of primitives.
+		if (newValue == null) {
+			newValue = this.nullValue;
+		}
+
+		try {
+			writeMethod.invoke(object, newValue);
+		} catch (Throwable th) {
+			throw new PropertyException("Error reading property: " + propertyName, this, object, th);
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/edc8975c/cayenne-server/src/main/java/org/apache/cayenne/reflect/MapAccessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/reflect/MapAccessor.java b/cayenne-server/src/main/java/org/apache/cayenne/reflect/MapAccessor.java
new file mode 100644
index 0000000..8d5c464
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/reflect/MapAccessor.java
@@ -0,0 +1,52 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.reflect;
+
+import java.util.Map;
+
+/**
+ * @since 4.0
+ */
+public class MapAccessor implements Accessor {
+
+	private static final long serialVersionUID = 6032801387641617011L;
+	
+	private String propertyName;
+
+	public MapAccessor(String propertyName) {
+		this.propertyName = propertyName;
+	}
+
+	@Override
+	public String getName() {
+		return propertyName;
+	}
+
+	@SuppressWarnings("rawtypes")
+	@Override
+	public Object getValue(Object object) {
+		return ((Map) object).get(propertyName);
+	}
+
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	@Override
+	public void setValue(Object object, Object newValue) {
+		((Map) object).put(propertyName, newValue);
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/edc8975c/cayenne-server/src/main/java/org/apache/cayenne/reflect/PropertyUtils.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/reflect/PropertyUtils.java b/cayenne-server/src/main/java/org/apache/cayenne/reflect/PropertyUtils.java
index 9ce633a..d06726d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/reflect/PropertyUtils.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/reflect/PropertyUtils.java
@@ -19,21 +19,12 @@
 
 package org.apache.cayenne.reflect;
 
-import java.beans.BeanInfo;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
 import java.util.Map;
-import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.map.Entity;
-import org.apache.cayenne.util.Util;
 
 /**
  * Utility methods to quickly access object properties. This class supports
@@ -45,189 +36,104 @@ import org.apache.cayenne.util.Util;
  */
 public class PropertyUtils {
 
+	private static final ConcurrentMap<String, Accessor> PATH_ACCESSORS = new ConcurrentHashMap<String,
Accessor>();
+	private static final ConcurrentMap<Class<?>, ConcurrentMap<String, Accessor>>
SEGMENT_ACCESSORS = new ConcurrentHashMap<Class<?>, ConcurrentMap<String, Accessor>>();
+
 	/**
 	 * Compiles an accessor that can be used for fast access for the nested
 	 * property of the objects of a given class.
 	 * 
-	 * @since 3.0
+	 * @since 4.0
 	 */
-	public static Accessor createAccessor(Class<?> objectClass, String nestedPropertyName)
{
-		if (objectClass == null) {
-			throw new IllegalArgumentException("Null class.");
-		}
+	public static Accessor accessor(String nestedPropertyName) {
 
-		if (Util.isEmptyString(nestedPropertyName)) {
-			throw new IllegalArgumentException("Null or empty property name.");
+		if (nestedPropertyName == null) {
+			throw new IllegalArgumentException("Null property name.");
 		}
 
-		StringTokenizer path = new StringTokenizer(nestedPropertyName, Entity.PATH_SEPARATOR);
-
-		if (path.countTokens() == 1) {
-			return new BeanAccessor(objectClass, nestedPropertyName, null);
+		if (nestedPropertyName.length() == 0) {
+			throw new IllegalArgumentException("Empty property name.");
 		}
 
-		NestedBeanAccessor accessor = new NestedBeanAccessor(nestedPropertyName);
-		while (path.hasMoreTokens()) {
-			String token = path.nextToken();
-			accessor.addAccessor(new BeanAccessor(objectClass, token, null));
-		}
+		// PathAccessor is simply a chain of path segment wrappers. The actual
+		// accessor is resolved (with caching) during evaluation. Otherwise we
+		// won't be able to handle subclasses of declared property types...
 
-		return accessor;
-	}
+		// TODO: perhaps Java 7 MethodHandles are the answer to truly "compiled"
+		// path accessor?
 
-	/**
-	 * Returns object property using JavaBean-compatible introspection with one
-	 * addition - a property can be a dot-separated property name path.
-	 */
-	public static Object getProperty(Object object, String nestedPropertyName) throws CayenneRuntimeException
{
-
-		if (object == null) {
-			throw new IllegalArgumentException("Null object.");
-		}
-
-		if (Util.isEmptyString(nestedPropertyName)) {
-			throw new IllegalArgumentException("Null or empty property name.");
-		}
-
-		StringTokenizer path = new StringTokenizer(nestedPropertyName, Entity.PATH_SEPARATOR);
-		int len = path.countTokens();
+		return compilePathAccessor(nestedPropertyName);
+	}
 
-		Object value = object;
-		String pathSegment = null;
+	static Accessor compilePathAccessor(String path) {
 
-		try {
-			for (int i = 1; i <= len; i++) {
-				pathSegment = path.nextToken();
+		Accessor accessor = PATH_ACCESSORS.get(path);
 
-				if (value == null) {
-					return null;
-				}
+		if (accessor == null) {
 
-				value = getSimpleProperty(value, pathSegment);
+			int dot = path.indexOf(Entity.PATH_SEPARATOR);
+			if (dot == 0 || dot == path.length() - 1) {
+				throw new IllegalArgumentException("Invalid path: " + path);
 			}
 
-			return value;
-		} catch (Exception e) {
-			String objectType = value != null ? value.getClass().getName() : "<null>";
-			throw new CayenneRuntimeException("Error reading property segment '" + pathSegment + "'
in path '"
-					+ nestedPropertyName + "' for type " + objectType, e);
-		}
-	}
-
-	/**
-	 * Sets object property using JavaBean-compatible introspection with one
-	 * addition - a property can be a dot-separated property name path. Before
-	 * setting a value attempts to convert it to a type compatible with the
-	 * object property. Automatic conversion is supported between strings and
-	 * basic types like numbers or primitives.
-	 */
-	public static void setProperty(Object object, String nestedPropertyName, Object value)
-			throws CayenneRuntimeException {
+			String segment = dot < 0 ? path : path.substring(0, dot);
+			Accessor remainingAccessor = dot < 0 ? null : compilePathAccessor(path.substring(dot
+ 1));
+			Accessor newAccessor = new PathAccessor(segment, remainingAccessor);
 
-		if (object == null) {
-			throw new IllegalArgumentException("Null object.");
+			Accessor existingAccessor = PATH_ACCESSORS.putIfAbsent(path, newAccessor);
+			accessor = existingAccessor != null ? existingAccessor : newAccessor;
 		}
 
-		if (Util.isEmptyString(nestedPropertyName)) {
-			throw new IllegalArgumentException("Null or invalid property name.");
-		}
+		return accessor;
+	}
 
-		int dot = nestedPropertyName.lastIndexOf(Entity.PATH_SEPARATOR);
-		String lastSegment;
-		if (dot > 0) {
-			lastSegment = nestedPropertyName.substring(dot + 1);
-			String pathSegment = nestedPropertyName.substring(0, dot);
-			Object intermediateObject = getProperty(object, pathSegment);
+	static Accessor getOrCreateSegmentAccessor(Class<?> objectClass, String propertyName)
{
+		ConcurrentMap<String, Accessor> forType = SEGMENT_ACCESSORS.get(objectClass);
+		if (forType == null) {
 
-			if (intermediateObject == null) {
-				throw new UnresolvablePathException("Null value in the middle of the path, failed on
" + pathSegment
-						+ " from " + object);
-			} else {
-				object = intermediateObject;
-			}
-		} else {
-			lastSegment = nestedPropertyName;
+			ConcurrentMap<String, Accessor> newPropAccessors = new ConcurrentHashMap<String,
Accessor>();
+			ConcurrentMap<String, Accessor> existingPropAccessors = SEGMENT_ACCESSORS.putIfAbsent(objectClass,
+					newPropAccessors);
+			forType = existingPropAccessors != null ? existingPropAccessors : newPropAccessors;
 		}
 
-		try {
-			setSimpleProperty(object, lastSegment, value);
-		} catch (Exception e) {
-			throw new CayenneRuntimeException("Error setting property segment '" + lastSegment + "'
in path '"
-					+ nestedPropertyName + "'" + " to value '" + value + "' for object '" + object + "'",
e);
+		Accessor a = forType.get(propertyName);
+		if (a == null) {
+			Accessor newA = createSegmentAccessor(objectClass, propertyName);
+			Accessor existingA = forType.putIfAbsent(propertyName, newA);
+			a = existingA != null ? existingA : newA;
 		}
 
+		return a;
 	}
 
-	static Object getSimpleProperty(Object object, String pathSegment) throws IntrospectionException,
-			IllegalArgumentException, IllegalAccessException, InvocationTargetException {
-
-		PropertyDescriptor descriptor = getPropertyDescriptor(object.getClass(), pathSegment);
+	static Accessor createSegmentAccessor(Class<?> objectClass, String propertyName) {
 
-		if (descriptor != null) {
-			Method reader = descriptor.getReadMethod();
-
-			if (reader == null) {
-				throw new IntrospectionException("Unreadable property '" + pathSegment + "'");
-			}
-
-			return reader.invoke(object, (Object[]) null);
-		}
-		// note that Map has two traditional bean properties - 'empty' and
-		// 'class', so
-		// do a check here only after descriptor lookup failed.
-		else if (object instanceof Map) {
-			return ((Map<?, ?>) object).get(pathSegment);
+		if (Map.class.isAssignableFrom(objectClass)) {
+			return new MapAccessor(propertyName);
 		} else {
-			throw new IntrospectionException("No property '" + pathSegment + "' found in class "
-					+ object.getClass().getName());
+			return new BeanAccessor(objectClass, propertyName, null);
 		}
 	}
 
-	@SuppressWarnings({ "rawtypes", "unchecked" })
-	static void setSimpleProperty(Object object, String pathSegment, Object value) throws IntrospectionException,
-			IllegalArgumentException, IllegalAccessException, InvocationTargetException {
-
-		PropertyDescriptor descriptor = getPropertyDescriptor(object.getClass(), pathSegment);
-
-		if (descriptor != null) {
-			Method writer = descriptor.getWriteMethod();
-
-			if (writer == null) {
-				throw new IntrospectionException("Unwritable property '" + pathSegment + "'");
-			}
-
-			// do basic conversions
-			Converter<?> converter = ConverterFactory.factory.getConverter(descriptor.getPropertyType());
-			value = (converter != null) ? converter.convert(value, (Class) descriptor.getPropertyType())
: value;
-
-			// set
-			writer.invoke(object, value);
-		}
-		// note that Map has two traditional bean properties - 'empty' and
-		// 'class', so
-		// do a check here only after descriptor lookup failed.
-		else if (object instanceof Map) {
-			((Map) object).put(pathSegment, value);
-		} else {
-			throw new IntrospectionException("No property '" + pathSegment + "' found in class "
-					+ object.getClass().getName());
-		}
+	/**
+	 * Returns object property using JavaBean-compatible introspection with one
+	 * addition - a property can be a dot-separated property name path.
+	 */
+	public static Object getProperty(Object object, String nestedPropertyName) throws CayenneRuntimeException
{
+		return accessor(nestedPropertyName).getValue(object);
 	}
 
-	static PropertyDescriptor getPropertyDescriptor(Class<?> beanClass, String propertyName)
-			throws IntrospectionException {
-		// bean info is cached by introspector, so this should have reasonable
-		// performance...
-		BeanInfo info = Introspector.getBeanInfo(beanClass);
-		PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
-
-		for (PropertyDescriptor descriptor : descriptors) {
-			if (propertyName.equals(descriptor.getName())) {
-				return descriptor;
-			}
-		}
-
-		return null;
+	/**
+	 * Sets object property using JavaBean-compatible introspection with one
+	 * addition - a property can be a dot-separated property name path. Before
+	 * setting a value attempts to convert it to a type compatible with the
+	 * object property. Automatic conversion is supported between strings and
+	 * basic types like numbers or primitives.
+	 */
+	public static void setProperty(Object object, String nestedPropertyName, Object value)
+			throws CayenneRuntimeException {
+		accessor(nestedPropertyName).setValue(object, value);
 	}
 
 	/**
@@ -295,54 +201,44 @@ public class PropertyUtils {
 		super();
 	}
 
-	static final class NestedBeanAccessor implements Accessor {
+	static final class PathAccessor implements Accessor {
 
-		private static final long serialVersionUID = -6592741135509067043L;
+		private static final long serialVersionUID = 2056090443413498626L;
 
-		private Collection<Accessor> accessors;
-		private String name;
+		private String segmentName;
+		private Accessor nextAccessor;
 
-		NestedBeanAccessor(String name) {
-			accessors = new ArrayList<Accessor>();
-			this.name = name;
-		}
-
-		void addAccessor(Accessor accessor) {
-			accessors.add(accessor);
+		public PathAccessor(String segmentName, Accessor nextAccessor) {
+			this.segmentName = segmentName;
+			this.nextAccessor = nextAccessor;
 		}
 
 		@Override
 		public String getName() {
-			return name;
+			return segmentName;
 		}
 
 		@Override
 		public Object getValue(Object object) throws PropertyException {
-
-			Object value = object;
-			for (Accessor accessor : accessors) {
-				if (value == null) {
-					return null;
-				}
-
-				value = accessor.getValue(value);
+			if (object == null) {
+				return null;
 			}
 
-			return value;
+			Object value = getOrCreateSegmentAccessor(object.getClass(), segmentName).getValue(object);
+			return nextAccessor != null ? nextAccessor.getValue(value) : value;
 		}
 
 		@Override
 		public void setValue(Object object, Object newValue) throws PropertyException {
-			Object value = object;
-			Iterator<Accessor> accessors = this.accessors.iterator();
-			while (accessors.hasNext()) {
-				Accessor accessor = accessors.next();
-
-				if (accessors.hasNext()) {
-					value = accessor.getValue(value);
-				} else {
-					accessor.setValue(value, newValue);
-				}
+			if (object == null) {
+				return;
+			}
+
+			Accessor segmentAccessor = getOrCreateSegmentAccessor(object.getClass(), segmentName);
+			if (nextAccessor != null) {
+				nextAccessor.setValue(segmentAccessor.getValue(object), newValue);
+			} else {
+				segmentAccessor.setValue(object, newValue);
 			}
 		}
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/edc8975c/cayenne-server/src/test/java/org/apache/cayenne/CayenneDataObjectIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/CayenneDataObjectIT.java b/cayenne-server/src/test/java/org/apache/cayenne/CayenneDataObjectIT.java
index efe5c52..8efccd7 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/CayenneDataObjectIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/CayenneDataObjectIT.java
@@ -19,6 +19,13 @@
 
 package org.apache.cayenne;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import java.util.ArrayList;
+import java.util.List;
+
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
@@ -31,138 +38,129 @@ import org.apache.cayenne.unit.di.server.UseServerRuntime;
 import org.apache.cayenne.unit.util.TstBean;
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-
 @UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
 public class CayenneDataObjectIT extends ServerCase {
 
-    @Inject
-    private ObjectContext context;
-
-    @Test
-    public void testSetObjectId() throws Exception {
-        CayenneDataObject object = new CayenneDataObject();
-        ObjectId oid = new ObjectId("T");
-
-        assertNull(object.getObjectId());
-
-        object.setObjectId(oid);
-        assertSame(oid, object.getObjectId());
-    }
-
-    @Test
-    public void testSetPersistenceState() throws Exception {
-        CayenneDataObject obj = new CayenneDataObject();
-        assertEquals(PersistenceState.TRANSIENT, obj.getPersistenceState());
-
-        obj.setPersistenceState(PersistenceState.COMMITTED);
-        assertEquals(PersistenceState.COMMITTED, obj.getPersistenceState());
-    }
-
-    @Test
-    public void testReadNestedProperty1() throws Exception {
-        Artist a = new Artist();
-        assertNull(a.readNestedProperty("artistName"));
-        a.setArtistName("aaa");
-        assertEquals("aaa", a.readNestedProperty("artistName"));
-    }
-
-    @Test
-    public void testReadNestedPropertyNotPersistentString() throws Exception {
-        Artist a = new Artist();
-        assertNull(a.readNestedProperty("someOtherProperty"));
-        a.setSomeOtherProperty("aaa");
-        assertEquals("aaa", a.readNestedProperty("someOtherProperty"));
-    }
-
-    @Test
-    public void testReadNestedPropertyNonPersistentNotString() throws Exception {
-        Artist a = new Artist();
-        Object object = new Object();
-        assertNull(a.readNestedProperty("someOtherObjectProperty"));
-        a.setSomeOtherObjectProperty(object);
-        assertSame(object, a.readNestedProperty("someOtherObjectProperty"));
-    }
-
-    @Test
-    public void testReadNestedPropertyNonDataObjectPath() {
-        CayenneDataObject o1 = new CayenneDataObject();
-        TstBean o2 = new TstBean();
-        o2.setInteger(new Integer(55));
-        o1.writePropertyDirectly("o2", o2);
-
-        assertSame(o2, o1.readNestedProperty("o2"));
-        assertEquals(new Integer(55), o1.readNestedProperty("o2.integer"));
-        assertEquals(TstBean.class, o1.readNestedProperty("o2.class"));
-        assertEquals(TstBean.class.getName(), o1.readNestedProperty("o2.class.name"));
-    }
-
-    @Test
-    public void testReadNestedPropertyToManyInMiddle() throws Exception {
-
-        Artist a = context.newObject(Artist.class);
-        ArtistExhibit ex = context.newObject(ArtistExhibit.class);
-        Painting p1 = context.newObject(Painting.class);
-        Painting p2 = context.newObject(Painting.class);
-        p1.setPaintingTitle("p1");
-        p2.setPaintingTitle("p2");
-        a.addToPaintingArray(p1);
-        a.addToPaintingArray(p2);
-        ex.setToArtist(a);
-
-        List<String> names = (List<String>) a
-                .readNestedProperty("paintingArray.paintingTitle");
-        assertEquals(names.size(), 2);
-        assertEquals(names.get(0), "p1");
-        assertEquals(names.get(1), "p2");
-
-        List<String> names2 = (List<String>) ex
-                .readNestedProperty("toArtist.paintingArray.paintingTitle");
-        assertEquals(names, names2);
-    }
-
-    @Test
-    public void testReadNestedPropertyToManyInMiddle1() throws Exception {
-
-        Artist a = context.newObject(Artist.class);
-        ArtistExhibit ex = context.newObject(ArtistExhibit.class);
-        Painting p1 = context.newObject(Painting.class);
-        Painting p2 = context.newObject(Painting.class);
-        p1.setPaintingTitle("p1");
-        p2.setPaintingTitle("p2");
-        a.addToPaintingArray(p1);
-        a.addToPaintingArray(p2);
-        ex.setToArtist(a);
-
-        List<String> names = (List<String>) a
-                .readNestedProperty("paintingArray+.paintingTitle");
-        assertEquals(names.size(), 2);
-        assertEquals(names.get(0), "p1");
-        assertEquals(names.get(1), "p2");
-
-        List<String> names2 = (List<String>) ex
-                .readNestedProperty("toArtist.paintingArray+.paintingTitle");
-        assertEquals(names, names2);
-    }
-
-    @Test
-    public void testFilterObjects() {
-
-        List<Painting> paintingList = new ArrayList<Painting>();
-        Painting p1 = context.newObject(Painting.class);
-        Artist a1 = context.newObject(Artist.class);
-        a1.setArtistName("dddAd");
-        p1.setToArtist(a1);
-
-        paintingList.add(p1);
-        Expression exp = ExpressionFactory.likeExp("toArtist+.artistName", "d%");
-
-        List<Painting> rezult = exp.filterObjects(paintingList);
-        assertEquals(a1, rezult.get(0).getToArtist());
-    }
+	@Inject
+	private ObjectContext context;
+
+	@Test
+	public void testSetObjectId() throws Exception {
+		CayenneDataObject object = new CayenneDataObject();
+		ObjectId oid = new ObjectId("T");
+
+		assertNull(object.getObjectId());
+
+		object.setObjectId(oid);
+		assertSame(oid, object.getObjectId());
+	}
+
+	@Test
+	public void testSetPersistenceState() throws Exception {
+		CayenneDataObject obj = new CayenneDataObject();
+		assertEquals(PersistenceState.TRANSIENT, obj.getPersistenceState());
+
+		obj.setPersistenceState(PersistenceState.COMMITTED);
+		assertEquals(PersistenceState.COMMITTED, obj.getPersistenceState());
+	}
+
+	@Test
+	public void testReadNestedProperty1() throws Exception {
+		Artist a = new Artist();
+		assertNull(a.readNestedProperty("artistName"));
+		a.setArtistName("aaa");
+		assertEquals("aaa", a.readNestedProperty("artistName"));
+	}
+
+	@Test
+	public void testReadNestedPropertyNotPersistentString() throws Exception {
+		Artist a = new Artist();
+		assertNull(a.readNestedProperty("someOtherProperty"));
+		a.setSomeOtherProperty("aaa");
+		assertEquals("aaa", a.readNestedProperty("someOtherProperty"));
+	}
+
+	@Test
+	public void testReadNestedPropertyNonPersistentNotString() throws Exception {
+		Artist a = new Artist();
+		Object object = new Object();
+		assertNull(a.readNestedProperty("someOtherObjectProperty"));
+		a.setSomeOtherObjectProperty(object);
+		assertSame(object, a.readNestedProperty("someOtherObjectProperty"));
+	}
+
+	@Test
+	public void testReadNestedPropertyNonDataObjectPath() {
+		CayenneDataObject o1 = new CayenneDataObject();
+		TstBean o2 = new TstBean();
+		o2.setInteger(new Integer(55));
+		o1.writePropertyDirectly("o2", o2);
+
+		assertSame(o2, o1.readNestedProperty("o2"));
+		assertEquals(new Integer(55), o1.readNestedProperty("o2.integer"));
+		assertEquals(TstBean.class, o1.readNestedProperty("o2.class"));
+		assertEquals(TstBean.class.getName(), o1.readNestedProperty("o2.class.name"));
+	}
+
+	@SuppressWarnings("unchecked")
+	@Test
+	public void testReadNestedPropertyToManyInMiddle() throws Exception {
+
+		Artist a = context.newObject(Artist.class);
+		ArtistExhibit ex = context.newObject(ArtistExhibit.class);
+		Painting p1 = context.newObject(Painting.class);
+		Painting p2 = context.newObject(Painting.class);
+		p1.setPaintingTitle("p1");
+		p2.setPaintingTitle("p2");
+		a.addToPaintingArray(p1);
+		a.addToPaintingArray(p2);
+		ex.setToArtist(a);
+
+		List<String> names = (List<String>) a.readNestedProperty("paintingArray.paintingTitle");
+		assertEquals(names.size(), 2);
+		assertEquals(names.get(0), "p1");
+		assertEquals(names.get(1), "p2");
+
+		List<String> names2 = (List<String>) ex.readNestedProperty("toArtist.paintingArray.paintingTitle");
+		assertEquals(names, names2);
+	}
+
+	@SuppressWarnings("unchecked")
+	@Test
+	public void testReadNestedPropertyToManyInMiddle1() throws Exception {
+
+		Artist a = context.newObject(Artist.class);
+		ArtistExhibit ex = context.newObject(ArtistExhibit.class);
+		Painting p1 = context.newObject(Painting.class);
+		Painting p2 = context.newObject(Painting.class);
+		p1.setPaintingTitle("p1");
+		p2.setPaintingTitle("p2");
+		a.addToPaintingArray(p1);
+		a.addToPaintingArray(p2);
+		ex.setToArtist(a);
+
+		List<String> names = (List<String>) a.readNestedProperty("paintingArray+.paintingTitle");
+		assertEquals(names.size(), 2);
+		assertEquals(names.get(0), "p1");
+		assertEquals(names.get(1), "p2");
+
+		List<String> names2 = (List<String>) ex.readNestedProperty("toArtist.paintingArray+.paintingTitle");
+		assertEquals(names, names2);
+	}
+
+	@Test
+	public void testFilterObjects() {
+
+		List<Painting> paintingList = new ArrayList<Painting>();
+		Painting p1 = context.newObject(Painting.class);
+		Artist a1 = context.newObject(Artist.class);
+		a1.setArtistName("dddAd");
+		p1.setToArtist(a1);
+
+		paintingList.add(p1);
+		Expression exp = ExpressionFactory.likeExp("toArtist+.artistName", "d%");
+
+		List<Painting> rezult = exp.filterObjects(paintingList);
+		assertEquals(a1, rezult.get(0).getToArtist());
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/edc8975c/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
index 22b3b0e..0026f66 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
@@ -18,19 +18,16 @@
  ****************************************************************/
 package org.apache.cayenne.exp;
 
-import org.apache.cayenne.exp.parser.PatternMatchNode;
-import org.apache.cayenne.reflect.TstJavaBean;
-import org.apache.cayenne.reflect.UnresolvablePathException;
-import org.apache.cayenne.util.Util;
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import java.util.Arrays;
 import java.util.List;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import org.apache.cayenne.exp.parser.PatternMatchNode;
+import org.apache.cayenne.reflect.TstJavaBean;
+import org.junit.Test;
 
 public class PropertyTest {
 
@@ -112,15 +109,7 @@ public class PropertyTest {
 		TstJavaBean bean = new TstJavaBean();
 		bean.setObjectField(null);
 		final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
-		try {
-			OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
-			fail();
-		} catch (Exception e) {
-			Throwable rootException = Util.unwindException(e);
-			if (!(rootException instanceof UnresolvablePathException)) {
-				fail();
-			}
-		}
+		OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
 	}
 
 	@Test

http://git-wip-us.apache.org/repos/asf/cayenne/blob/edc8975c/cayenne-server/src/test/java/org/apache/cayenne/reflect/PropertyUtilsTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/reflect/PropertyUtilsTest.java
b/cayenne-server/src/test/java/org/apache/cayenne/reflect/PropertyUtilsTest.java
index 6c88531..9bce4db 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/reflect/PropertyUtilsTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/reflect/PropertyUtilsTest.java
@@ -22,6 +22,7 @@ package org.apache.cayenne.reflect;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -44,9 +45,9 @@ import org.junit.Test;
 public class PropertyUtilsTest {
 
 	@Test
-	public void testCreateAccessor() {
+	public void testAccessor() {
 
-		Accessor accessor = PropertyUtils.createAccessor(TstJavaBean.class, "byteArrayField");
+		Accessor accessor = PropertyUtils.accessor("byteArrayField");
 		assertNotNull(accessor);
 
 		TstJavaBean o1 = createBean();
@@ -60,9 +61,29 @@ public class PropertyUtilsTest {
 	}
 
 	@Test
-	public void testCreateAccessorNested() {
+	public void testAccessor_Cache() {
 
-		Accessor accessor = PropertyUtils.createAccessor(TstJavaBean.class, "related.byteArrayField");
+		Accessor accessor = PropertyUtils.accessor("p1");
+		assertNotNull(accessor);
+		assertSame(accessor, PropertyUtils.accessor("p1"));
+		assertSame(accessor, PropertyUtils.accessor("p1"));
+		assertNotSame(accessor, PropertyUtils.accessor("p2"));
+	}
+	
+	@Test
+	public void testAccessor_CacheNested() {
+
+		Accessor accessor = PropertyUtils.accessor("p1.p2");
+		assertNotNull(accessor);
+		assertSame(accessor, PropertyUtils.accessor("p1.p2"));
+		assertNotSame(accessor, PropertyUtils.accessor("p1"));
+		assertNotSame(accessor, PropertyUtils.accessor("p2"));
+	}
+
+	@Test
+	public void testAccessorNested() {
+
+		Accessor accessor = PropertyUtils.accessor("related.byteArrayField");
 		assertNotNull(accessor);
 
 		TstJavaBean o1 = createBean();
@@ -77,11 +98,11 @@ public class PropertyUtilsTest {
 		accessor.setValue(o2, b1);
 		assertSame(b1, o2.getRelated().getByteArrayField());
 	}
-	
+
 	@Test
-	public void testCreateAccessorNested_Null() {
+	public void testAccessorNested_Null() {
 
-		Accessor accessor = PropertyUtils.createAccessor(TstJavaBean.class, "related.byteArrayField");
+		Accessor accessor = PropertyUtils.accessor("related.byteArrayField");
 		assertNotNull(accessor);
 
 		TstJavaBean o1 = createBean();
@@ -141,6 +162,24 @@ public class PropertyUtilsTest {
 	}
 
 	@Test
+	public void testSetProperty_Nested() {
+		TstJavaBean o1 = createBean();
+		TstJavaBean o1related = new TstJavaBean();
+		o1related.setIntegerField(Integer.valueOf(44));
+		o1.setRelated(o1related);
+
+		PropertyUtils.setProperty(o1, "related.integerField", 55);
+		assertEquals(Integer.valueOf(55), o1related.getIntegerField());
+	}
+
+	@Test
+	public void testSetProperty_Null() {
+		TstJavaBean o1 = createBean();
+
+		PropertyUtils.setProperty(o1, "related.integerField", 55);
+	}
+
+	@Test
 	public void testSetPropertyMap() {
 		Map<String, Object> o1 = createMap();
 		Map<String, Object> o2 = new HashMap<String, Object>();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/edc8975c/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index 0e1e34a..3a6b2de 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -24,6 +24,7 @@ CAY-1990 Incorrect display of the raw SQL query in Modeler
 CAY-1993 Reverse Engineering does not work with PostgreSQL database
 CAY-1994 Modeler Migration Tool Shows No Changes
 CAY-1997 Difference in NULL handling inside the path between PropertyUtils and DataObject.readNestedProperty
+CAY-1998 Speeding up PropertyUtils
 
 ----------------------------------
 Release: 4.0.M2


Mime
View raw message