
com.googlecode.jpattern.org.cojen.util.BeanPropertyAccessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jpattern-cojen Show documentation
Show all versions of jpattern-cojen Show documentation
This is a copy of the good Cojen project from http://cojen.sourceforge.net/ with package name changed
The newest version!
/*
* Copyright 2004-2010 Brian S O'Neill
*
* 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 com.googlecode.jpattern.org.cojen.util;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.math.BigInteger;
import java.security.AccessController;
import java.security.PrivilegedAction;
import com.googlecode.jpattern.org.cojen.classfile.ClassFile;
import com.googlecode.jpattern.org.cojen.classfile.CodeBuilder;
import com.googlecode.jpattern.org.cojen.classfile.Label;
import com.googlecode.jpattern.org.cojen.classfile.LocalVariable;
import com.googlecode.jpattern.org.cojen.classfile.MethodInfo;
import com.googlecode.jpattern.org.cojen.classfile.Modifiers;
import com.googlecode.jpattern.org.cojen.classfile.Opcode;
import com.googlecode.jpattern.org.cojen.classfile.RuntimeClassFile;
import com.googlecode.jpattern.org.cojen.classfile.TypeDesc;
/**
* Provides a simple and efficient means of reading and writing bean
* properties. BeanPropertyAccessor auto-generates code, eliminating the
* need to invoke methods via reflection. Bean access methods are bound-to
* directly, using a special hash/switch design pattern.
*
* @author Brian S O'Neill
* @see BeanPropertyMapFactory
*/
public abstract class BeanPropertyAccessor {
public static enum PropertySet {
/** Set of all properties */
ALL,
/** Set of all properties which declare only unchecked exceptions */
UNCHECKED_EXCEPTIONS,
/** Set of all read-write properties */
READ_WRITE,
/** Set of all read-write properties which declare only unchecked exceptions */
READ_WRITE_UNCHECKED_EXCEPTIONS,
}
private static final int READ_METHOD = 1;
private static final int WRITE_METHOD = 2;
private static final int TRY_READ_METHOD = 3;
private static final int TRY_WRITE_METHOD = 4;
private static final int HAS_READ_METHOD = 5;
private static final int HAS_WRITE_METHOD = 6;
private static final
Map>> cAccessors =
new HashMap>>();
/**
* Returns a new or cached BeanPropertyAccessor for the given class.
*/
public static BeanPropertyAccessor forClass(Class clazz) {
return forClass(clazz, PropertySet.ALL);
}
public static BeanPropertyAccessor forClass(Class clazz, PropertySet set) {
synchronized (cAccessors) {
Map> accessors = cAccessors.get(set);
if (accessors == null) {
accessors = new WeakIdentityMap>();
cAccessors.put(set, accessors);
}
BeanPropertyAccessor bpa;
SoftReference ref = accessors.get(clazz);
if (ref != null) {
bpa = ref.get();
if (bpa != null) {
return bpa;
}
}
bpa = generate(clazz, set);
accessors.put(clazz, new SoftReference(bpa));
return bpa;
}
}
private static BeanPropertyAccessor generate(final Class beanType,
final PropertySet set)
{
return AccessController.doPrivileged(new PrivilegedAction>() {
public BeanPropertyAccessor run() {
Class clazz = generateClassFile(beanType, set).defineClass();
try {
return (BeanPropertyAccessor) clazz.newInstance();
} catch (InstantiationException e) {
throw new InternalError(e.toString());
} catch (IllegalAccessException e) {
throw new InternalError(e.toString());
}
}
});
}
private static RuntimeClassFile generateClassFile(Class beanType, PropertySet set) {
BeanProperty[][] props = getBeanProperties(beanType, set);
RuntimeClassFile cf = new RuntimeClassFile
(BeanPropertyAccessor.class.getName(),
BeanPropertyAccessor.class.getName(),
beanType.getClassLoader());
cf.markSynthetic();
cf.setSourceFile(BeanPropertyAccessor.class.getName());
try {
cf.setTarget(System.getProperty("java.specification.version"));
} catch (Exception e) {
}
MethodInfo ctor = cf.addConstructor(Modifiers.PUBLIC, null);
ctor.markSynthetic();
CodeBuilder b = new CodeBuilder(ctor);
b.loadThis();
b.invokeSuperConstructor(null);
b.returnVoid();
generateAccessMethod(cf, beanType, props[0], READ_METHOD);
generateAccessMethod(cf, beanType, props[0], TRY_READ_METHOD);
generateAccessMethod(cf, beanType, props[0], HAS_READ_METHOD);
generateAccessMethod(cf, beanType, props[1], WRITE_METHOD);
generateAccessMethod(cf, beanType, props[1], TRY_WRITE_METHOD);
generateAccessMethod(cf, beanType, props[1], HAS_WRITE_METHOD);
generateSearchMethod(cf, beanType, props[0]);
return cf;
}
private static void generateAccessMethod(ClassFile cf,
Class beanType,
BeanProperty[] properties,
int methodType)
{
MethodInfo mi;
switch (methodType) {
case READ_METHOD: default: {
TypeDesc[] params = {TypeDesc.OBJECT, TypeDesc.STRING};
mi = cf.addMethod
(Modifiers.PUBLIC, "getPropertyValue", TypeDesc.OBJECT, params);
break;
}
case WRITE_METHOD: {
TypeDesc[] params = new TypeDesc[] {
TypeDesc.OBJECT, TypeDesc.STRING, TypeDesc.OBJECT
};
mi = cf.addMethod(Modifiers.PUBLIC, "setPropertyValue", null, params);
break;
}
case TRY_READ_METHOD: {
TypeDesc[] params = {TypeDesc.OBJECT, TypeDesc.STRING};
mi = cf.addMethod
(Modifiers.PUBLIC, "tryGetPropertyValue", TypeDesc.OBJECT, params);
break;
}
case TRY_WRITE_METHOD: {
TypeDesc[] params = new TypeDesc[] {
TypeDesc.OBJECT, TypeDesc.STRING, TypeDesc.OBJECT
};
mi = cf.addMethod(Modifiers.PUBLIC, "trySetPropertyValue", TypeDesc.BOOLEAN, params);
break;
}
case HAS_READ_METHOD: {
TypeDesc[] params = {TypeDesc.STRING};
mi = cf.addMethod(Modifiers.PUBLIC, "hasReadableProperty", TypeDesc.BOOLEAN, params);
break;
}
case HAS_WRITE_METHOD: {
TypeDesc[] params = {TypeDesc.STRING};
mi = cf.addMethod(Modifiers.PUBLIC, "hasWritableProperty", TypeDesc.BOOLEAN, params);
break;
}
}
mi.markSynthetic();
CodeBuilder b = new CodeBuilder(mi);
LocalVariable beanVar, propertyVar, valueVar;
switch (methodType) {
case READ_METHOD: case TRY_READ_METHOD: default:
beanVar = b.getParameter(0);
propertyVar = b.getParameter(1);
valueVar = null;
break;
case WRITE_METHOD: case TRY_WRITE_METHOD:
beanVar = b.getParameter(0);
propertyVar = b.getParameter(1);
valueVar = b.getParameter(2);
break;
case HAS_READ_METHOD: case HAS_WRITE_METHOD:
beanVar = null;
propertyVar = b.getParameter(0);
valueVar = null;
break;
}
if (beanVar != null) {
b.loadLocal(beanVar);
b.checkCast(TypeDesc.forClass(beanType));
b.storeLocal(beanVar);
}
if (properties.length > 0) {
int[] cases = new int[hashCapacity(properties.length)];
int caseCount = cases.length;
for (int i=0; i 1) {
b.loadLocal(propertyVar);
b.invokeVirtual(String.class.getName(), "hashCode", TypeDesc.INT, null);
b.loadConstant(0x7fffffff);
b.math(Opcode.IAND);
b.loadConstant(caseCount);
b.math(Opcode.IREM);
b.switchBranch(cases, switchLabels, noMatch);
}
// Params to invoke String.equals.
TypeDesc[] params = {TypeDesc.OBJECT};
for (int i=0; i.
TypeDesc[] params = {TypeDesc.STRING, TypeDesc.BOOLEAN};
b.invokeConstructor(NoSuchPropertyException.class.getName(), params);
b.throwObject();
}
}
/**
* Returns a prime number, at least twice as large as needed. This should
* minimize hash collisions. Since all the hash keys are known up front,
* the capacity could be tweaked until there are no collisions, but this
* technique is easier and deterministic.
*/
private static int hashCapacity(int min) {
BigInteger capacity = BigInteger.valueOf(min * 2 + 1);
while (!capacity.isProbablePrime(10)) {
capacity = capacity.add(BigInteger.valueOf(2));
}
return capacity.intValue();
}
/**
* Returns an array of Lists of BeanProperties. The first index
* matches a switch case, the second index provides a list of all the
* BeanProperties whose name hash matched on the case.
*/
private static List[] caseMethods(int caseCount,
BeanProperty[] props) {
List[] cases = new List[caseCount];
for (int i=0; i[] exceptionTypes = method.getExceptionTypes();
if (exceptionTypes == null) {
return false;
}
for (Class> exceptionType : exceptionTypes) {
if (RuntimeException.class.isAssignableFrom(exceptionType)) {
continue;
}
if (Error.class.isAssignableFrom(exceptionType)) {
continue;
}
return true;
}
return false;
}
protected BeanPropertyAccessor() {
}
// The actual public methods that will need to be defined.
public abstract Object getPropertyValue(B bean, String property)
throws NoSuchPropertyException;
public abstract void setPropertyValue(B bean, String property, Object value)
throws NoSuchPropertyException;
/**
* Returns true if readable bean property exists.
*
* @since 2.1
*/
public abstract boolean hasReadableProperty(String property);
/**
* Returns true if writable bean property exists.
*
* @since 2.1
*/
public abstract boolean hasWritableProperty(String property);
/**
* Returns true if at least one property is set to the given value.
*
* @since 2.1
*/
public abstract boolean hasPropertyValue(B bean, Object value);
/**
* Returns property value or null if it does not exist.
*
* @since 2.1
*/
public abstract Object tryGetPropertyValue(B bean, String property);
/**
* Tries to set property value, if it exists.
*
* @return false if property doesn't exist
* @since 2.1
*/
public abstract boolean trySetPropertyValue(B bean, String property, Object value);
// Auto-generated code sample:
/*
public Object getPropertyValue(Object bean, String property) {
Bean bean = (Bean)bean;
switch ((property.hashCode() & 0x7fffffff) % 11) {
case 0:
if ("name".equals(property)) {
return bean.getName();
}
break;
case 1:
// No case
break;
case 2:
// Hash collision
if ("value".equals(property)) {
return bean.getValue();
} else if ("age".equals(property)) {
return new Integer(bean.getAge());
}
break;
case 3:
if ("start".equals(property)) {
return bean.getStart();
}
break;
case 4:
case 5:
case 6:
// No case
break;
case 7:
if ("end".equals(property)) {
return bean.isEnd() ? Boolean.TRUE : Boolean.FALSE;
}
break;
case 8:
case 9:
case 10:
// No case
break;
}
throw new NoSuchPropertyException(property, true);
}
public void setPropertyValue(Object bean, String property, Object value) {
Bean bean = (Bean)bean;
switch ((property.hashCode() & 0x7fffffff) % 11) {
case 0:
if ("name".equals(property)) {
bean.setName(value);
}
break;
case 1:
// No case
break;
case 2:
// Hash collision
if ("value".equals(property)) {
bean.setValue(value);
} else if ("age".equals(property)) {
bean.setAge(((Integer)value).intValue());
}
break;
case 3:
if ("start".equals(property)) {
bean.setStart(value);
}
break;
case 4:
case 5:
case 6:
// No case
break;
case 7:
if ("end".equals(property)) {
bean.setEnd(((Boolean)value).booleanValue());
}
break;
case 8:
case 9:
case 10:
// No case
break;
}
throw new NoSuchPropertyException(property, false);
}
*/
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy