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

javaa.beans.DefaultPersistenceDelegate Maven / Gradle / Ivy

/*
 *  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 javaa.beans;

import javaa.beans.BeanInfo;
import javaa.beans.Encoder;
import javaa.beans.Expression;
import javaa.beans.IntrospectionException;
import javaa.beans.Introspector;
import javaa.beans.PersistenceDelegate;
import javaa.beans.PropertyDescriptor;
import javaa.beans.Statement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;

import jadex.common.SAccess;

/**
 * Default PersistenceDelegate for normal classes. The instances of this class
 * are used when other customized PersistenceDelegate is not set in the encoders
 * for a particular type.
 * 

* This PersistenceDelegate assumes that the bean to be made persistent has a * default constructor that takes no parameters or a constructor that takes some * properties as its parameters. Only the properties that can be got or set * based on the knowledge gained through an introspection will be made * persistent. In the case that a bean is constructed with some properties, the * value of these properties should be available via the conventional getter * method. *

* * @see Encoder */ public class DefaultPersistenceDelegate extends PersistenceDelegate { // shared empty property name array private static String[] EMPTY_PROPERTIES = new String[0]; // names of the properties accepted by the bean's constructor private String[] propertyNames = EMPTY_PROPERTIES; /** * Constructs a DefaultPersistenceDelegate instance that * supports the persistence of a bean which has a default constructor. * */ public DefaultPersistenceDelegate() { // empty } /** * Constructs a DefaultPersistenceDelegate instance that * supports the persistence of a bean which is constructed with some * properties. * * @param propertyNames * the name of the properties that are taken as parameters by the * bean's constructor */ public DefaultPersistenceDelegate(String[] propertyNames) { if (null != propertyNames) { this.propertyNames = propertyNames; } } /** * Initializes the new instance in the new environment so that it becomes * equivalent with the old one, meanwhile recording this process in the * encoder. *

* This is done by inspecting each property of the bean. The property value * from the old bean instance and the value from the new bean instance are * both retrieved and examined to see whether the latter mutates to the * former, and if not, issue a call to the write method to set the * equivalent value for the new instance. Exceptions occured during this * process are reported to the exception listener of the encoder. *

* * @param type * the type of the bean * @param oldInstance * the original bean object to be recorded * @param newInstance * the simmulating new bean object to be initialized * @param enc * the encoder to write the outputs to */ @Override protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder enc) { // Call the initialization of the super type super.initialize(type, oldInstance, newInstance, enc); // Continue only if initializing the "current" type if (type != oldInstance.getClass()) { return; } // Get all bean properties BeanInfo info = null; try { info = Introspector.getBeanInfo(type); } catch (IntrospectionException ex) { enc.getExceptionListener().exceptionThrown(ex); return; } PropertyDescriptor[] pds = info.getPropertyDescriptors(); // Initialize each found non-transient property for (int i = 0; i < pds.length; i++) { // Skip a property whose transient attribute is true if (Boolean.TRUE.equals(pds[i].getValue("transient"))) { //$NON-NLS-1$ continue; } // Skip a property having no setter or getter if (null == pds[i].getWriteMethod() || null == pds[i].getReadMethod()) { continue; } // Get the value of the property in the old instance Expression getterExp = new Expression(oldInstance, pds[i] .getReadMethod().getName(), null); try { // Calculate the old value of the property Object oldVal = getterExp.getValue(); // Write the getter expression to the encoder enc.writeExpression(getterExp); // Get the target value that exists in the new environment Object targetVal = enc.get(oldVal); // Get the current property value in the new environment Object newVal = new Expression(newInstance, pds[i] .getReadMethod().getName(), null).getValue(); /* * Make the target value and current property value equivalent * in the new environment */ if (null == targetVal) { if (null != newVal) { // Set to null Statement setterStm = new Statement(oldInstance, pds[i] .getWriteMethod().getName(), new Object[] { null }); enc.writeStatement(setterStm); } } else { PersistenceDelegate pd = enc .getPersistenceDelegate(targetVal.getClass()); if (!pd.mutatesTo(targetVal, newVal)) { Statement setterStm = new Statement(oldInstance, pds[i] .getWriteMethod().getName(), new Object[] { oldVal }); enc.writeStatement(setterStm); } } } catch (Exception ex) { enc.getExceptionListener().exceptionThrown(ex); } } } /* * Get the field value of an object using privileged code. */ private Object getFieldValue(Object oldInstance, String fieldName) throws NoSuchFieldException, IllegalAccessException { Class c = oldInstance.getClass(); final Field f = c.getDeclaredField(fieldName); AccessController.doPrivileged(new PrivilegedAction() { public Object run() { SAccess.setAccessible(f, true); return null; } }); return f.get(oldInstance); } /* * Get the value for the specified property of the given bean instance. */ private Object getPropertyValue(HashMap proDscMap, Object oldInstance, String propName) throws Exception { // Try to get the read method for the property Method getter = null; if (null != proDscMap) { PropertyDescriptor pd = proDscMap .get(Introspector.decapitalize(propName)); if (null != pd) { getter = pd.getReadMethod(); } } // Invoke read method to get the value if found if (null != getter) { return getter.invoke(oldInstance, (Object[]) null); } // Otherwise, try to access the field directly try { return getFieldValue(oldInstance, propName); } catch (Exception ex) { // Fail, throw an exception throw new NoSuchMethodException( "The getter method for the property " //$NON-NLS-1$ + propName + " can't be found."); //$NON-NLS-1$ } } /** * Returns an expression that represents a call to the bean's constructor. * The constructor may take zero or more parameters, as specified when this * DefaultPersistenceDelegate is constructed. * * @param oldInstance * the old instance * @param enc * the encoder that wants to record the old instance * @return an expression for instantiating an object of the same type as the * old instance */ @Override protected Expression instantiate(Object oldInstance, Encoder enc) { Object[] args = null; // Set the constructor arguments if any property names exist if (this.propertyNames.length > 0) { // Prepare the property descriptors for finding getter method later BeanInfo info = null; HashMap proDscMap = null; try { info = Introspector.getBeanInfo(oldInstance.getClass(), Introspector.IGNORE_ALL_BEANINFO); proDscMap = internalAsMap(info.getPropertyDescriptors()); } catch (IntrospectionException ex) { enc.getExceptionListener().exceptionThrown(ex); throw new Error(ex); } // Get the arguments values args = new Object[this.propertyNames.length]; for (int i = 0; i < this.propertyNames.length; i++) { String propertyName = propertyNames[i]; if (null == propertyName || 0 == propertyName.length()) { continue; } // Get the value for each property of the given instance try { args[i] = getPropertyValue(proDscMap, oldInstance, this.propertyNames[i]); } catch (Exception ex) { enc.getExceptionListener().exceptionThrown(ex); } } } return new Expression(oldInstance, oldInstance.getClass(), Statement.CONSTRUCTOR_NAME, args); } private static HashMap internalAsMap( PropertyDescriptor[] propertyDescs) { HashMap map = new HashMap(); for (int i = 0; i < propertyDescs.length; i++) { map.put(propertyDescs[i].getName(), propertyDescs[i]); } return map; } /** * Determines whether one object mutates to the other object. If this * DefaultPersistenceDelegate is constructed with one or more * property names, and the class of o1 overrides the * "equals(Object)" method, then o2 is considered to mutate * to o1 if o1 equals to o2. * Otherwise, the result is the same as the definition in * PersistenceDelegate. * * @param o1 * one object * @param o2 * the other object * @return true if second object mutates to the first object, otherwise * false */ @Override protected boolean mutatesTo(Object o1, Object o2) { if (this.propertyNames.length > 0) { Class clazz = o1.getClass(); // Check the "equals" method has been declared Method equalMethod = null; try { equalMethod = clazz.getDeclaredMethod("equals", //$NON-NLS-1$ new Class[] { Object.class }); } catch (NoSuchMethodException e) { // Ignored } if (null != equalMethod) { return o1.equals(o2); } } return super.mutatesTo(o1, o2); } }