001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.beans;
019:
020: import java.lang.reflect.Field;
021: import java.lang.reflect.Method;
022: import java.security.AccessController;
023: import java.security.PrivilegedAction;
024: import java.util.HashMap;
025:
026: /**
027: * Default PersistenceDelegate for normal classes. The instances of this class
028: * are used when other customized PersistenceDelegate is not set in the encoders
029: * for a particular type.
030: * <p>
031: * This PersistenceDelegate assumes that the bean to be made persistent has a
032: * default constructor that takes no parameters or a constructor that takes some
033: * properties as its parameters. Only the properties that can be got or set
034: * based on the knowledge gained through an introspection will be made
035: * persistent. In the case that a bean is constructed with some properties, the
036: * value of these properties should be available via the conventional getter
037: * method.
038: * </p>
039: *
040: * @see Encoder
041: */
042:
043: public class DefaultPersistenceDelegate extends PersistenceDelegate {
044:
045: // shared empty property name array
046: private static String[] EMPTY_PROPERTIES = new String[0];
047:
048: // names of the properties accepted by the bean's constructor
049: private String[] propertyNames = EMPTY_PROPERTIES;
050:
051: /**
052: * Constructs a <code>DefaultPersistenceDelegate</code> instance that
053: * supports the persistence of a bean which has a default constructor.
054: *
055: */
056: public DefaultPersistenceDelegate() {
057: // empty
058: }
059:
060: /**
061: * Constructs a <code>DefaultPersistenceDelegate</code> instance that
062: * supports the persistence of a bean which is constructed with some
063: * properties.
064: *
065: * @param propertyNames
066: * the name of the properties that are taken as parameters by the
067: * bean's constructor
068: */
069: public DefaultPersistenceDelegate(String[] propertyNames) {
070: if (null != propertyNames) {
071: this .propertyNames = propertyNames;
072: }
073: }
074:
075: /**
076: * Initializes the new instance in the new environment so that it becomes
077: * equivalent with the old one, meanwhile recording this process in the
078: * encoder.
079: * <p>
080: * This is done by inspecting each property of the bean. The property value
081: * from the old bean instance and the value from the new bean instance are
082: * both retrieved and examined to see whether the latter mutates to the
083: * former, and if not, issue a call to the write method to set the
084: * equivalent value for the new instance. Exceptions occured during this
085: * process are reported to the exception listener of the encoder.
086: * </p>
087: *
088: * @param type
089: * the type of the bean
090: * @param oldInstance
091: * the original bean object to be recorded
092: * @param newInstance
093: * the simmulating new bean object to be initialized
094: * @param enc
095: * the encoder to write the outputs to
096: */
097: @Override
098: protected void initialize(Class<?> type, Object oldInstance,
099: Object newInstance, Encoder enc) {
100: // Call the initialization of the super type
101: super .initialize(type, oldInstance, newInstance, enc);
102: // Continue only if initializing the "current" type
103: if (type != oldInstance.getClass()) {
104: return;
105: }
106:
107: // Get all bean properties
108: BeanInfo info = null;
109: try {
110: info = Introspector.getBeanInfo(type);
111: } catch (IntrospectionException ex) {
112: enc.getExceptionListener().exceptionThrown(ex);
113: return;
114: }
115: PropertyDescriptor[] pds = info.getPropertyDescriptors();
116:
117: // Initialize each found non-transient property
118: for (int i = 0; i < pds.length; i++) {
119: // Skip a property whose transient attribute is true
120: if (Boolean.TRUE.equals(pds[i].getValue("transient"))) { //$NON-NLS-1$
121: continue;
122: }
123: // Skip a property having no setter or getter
124: if (null == pds[i].getWriteMethod()
125: || null == pds[i].getReadMethod()) {
126: continue;
127: }
128:
129: // Get the value of the property in the old instance
130: Expression getterExp = new Expression(oldInstance, pds[i]
131: .getReadMethod().getName(), null);
132: try {
133: // Calculate the old value of the property
134: Object oldVal = getterExp.getValue();
135: // Write the getter expression to the encoder
136: enc.writeExpression(getterExp);
137: // Get the target value that exists in the new environment
138: Object targetVal = enc.get(oldVal);
139: // Get the current property value in the new environment
140: Object newVal = new Expression(newInstance, pds[i]
141: .getReadMethod().getName(), null).getValue();
142: /*
143: * Make the target value and current property value equivalent
144: * in the new environment
145: */
146: if (null == targetVal) {
147: if (null != newVal) {
148: // Set to null
149: Statement setterStm = new Statement(
150: oldInstance, pds[i].getWriteMethod()
151: .getName(),
152: new Object[] { null });
153: enc.writeStatement(setterStm);
154: }
155: } else {
156: PersistenceDelegate pd = enc
157: .getPersistenceDelegate(targetVal
158: .getClass());
159: if (!pd.mutatesTo(targetVal, newVal)) {
160: Statement setterStm = new Statement(
161: oldInstance, pds[i].getWriteMethod()
162: .getName(),
163: new Object[] { oldVal });
164: enc.writeStatement(setterStm);
165: }
166: }
167: } catch (Exception ex) {
168: enc.getExceptionListener().exceptionThrown(ex);
169: }
170: }
171: }
172:
173: /*
174: * Get the field value of an object using privileged code.
175: */
176: private Object getFieldValue(Object oldInstance, String fieldName)
177: throws NoSuchFieldException, IllegalAccessException {
178: Class<? extends Object> c = oldInstance.getClass();
179: final Field f = c.getDeclaredField(fieldName);
180: AccessController.doPrivileged(new PrivilegedAction<Object>() {
181: public Object run() {
182: f.setAccessible(true);
183: return null;
184: }
185: });
186: return f.get(oldInstance);
187: }
188:
189: /*
190: * Get the value for the specified property of the given bean instance.
191: */
192: private Object getPropertyValue(
193: HashMap<String, PropertyDescriptor> proDscMap,
194: Object oldInstance, String propName) throws Exception {
195: // Try to get the read method for the property
196: Method getter = null;
197: if (null != proDscMap) {
198: PropertyDescriptor pd = proDscMap.get(Introspector
199: .decapitalize(propName));
200: if (null != pd) {
201: getter = pd.getReadMethod();
202: }
203: }
204:
205: // Invoke read method to get the value if found
206: if (null != getter) {
207: return getter.invoke(oldInstance, (Object[]) null);
208: }
209:
210: // Otherwise, try to access the field directly
211: try {
212: return getFieldValue(oldInstance, propName);
213: } catch (Exception ex) {
214: // Fail, throw an exception
215: throw new NoSuchMethodException(
216: "The getter method for the property " //$NON-NLS-1$
217: + propName + " can't be found."); //$NON-NLS-1$
218: }
219:
220: }
221:
222: /**
223: * Returns an expression that represents a call to the bean's constructor.
224: * The constructor may take zero or more parameters, as specified when this
225: * <code>DefaultPersistenceDelegate</code> is constructed.
226: *
227: * @param oldInstance
228: * the old instance
229: * @param enc
230: * the encoder that wants to record the old instance
231: * @return an expression for instantiating an object of the same type as the
232: * old instance
233: */
234: @Override
235: protected Expression instantiate(Object oldInstance, Encoder enc) {
236: Object[] args = null;
237:
238: // Set the constructor arguments if any property names exist
239: if (this .propertyNames.length > 0) {
240: // Prepare the property descriptors for finding getter method later
241: BeanInfo info = null;
242: HashMap<String, PropertyDescriptor> proDscMap = null;
243: try {
244: info = Introspector.getBeanInfo(oldInstance.getClass(),
245: Introspector.IGNORE_ALL_BEANINFO);
246: proDscMap = internalAsMap(info.getPropertyDescriptors());
247: } catch (IntrospectionException ex) {
248: enc.getExceptionListener().exceptionThrown(ex);
249: throw new Error(ex);
250: }
251:
252: // Get the arguments values
253: args = new Object[this .propertyNames.length];
254: for (int i = 0; i < this .propertyNames.length; i++) {
255: String propertyName = propertyNames[i];
256: if (null == propertyName || 0 == propertyName.length()) {
257: continue;
258: }
259:
260: // Get the value for each property of the given instance
261: try {
262: args[i] = getPropertyValue(proDscMap, oldInstance,
263: this .propertyNames[i]);
264: } catch (Exception ex) {
265: enc.getExceptionListener().exceptionThrown(ex);
266: }
267: }
268: }
269:
270: return new Expression(oldInstance, oldInstance.getClass(),
271: Statement.CONSTRUCTOR_NAME, args);
272: }
273:
274: private static HashMap<String, PropertyDescriptor> internalAsMap(
275: PropertyDescriptor[] propertyDescs) {
276: HashMap<String, PropertyDescriptor> map = new HashMap<String, PropertyDescriptor>();
277: for (int i = 0; i < propertyDescs.length; i++) {
278: map.put(propertyDescs[i].getName(), propertyDescs[i]);
279: }
280: return map;
281: }
282:
283: /**
284: * Determines whether one object mutates to the other object. If this
285: * <code>DefaultPersistenceDelegate</code> is constructed with one or more
286: * property names, and the class of <code>o1</code> overrides the
287: * "equals(Object)" method, then <code>o2</code> is considered to mutate
288: * to <code>o1</code> if <code>o1</code> equals to <code>o2</code>.
289: * Otherwise, the result is the same as the definition in
290: * <code>PersistenceDelegate</code>.
291: *
292: * @param o1
293: * one object
294: * @param o2
295: * the other object
296: * @return true if second object mutates to the first object, otherwise
297: * false
298: */
299: @Override
300: protected boolean mutatesTo(Object o1, Object o2) {
301: if (null == o1 || null == o2) {
302: return false;
303: }
304: Class<? extends Object> c = o1.getClass();
305: if (this .propertyNames.length > 0) {
306: // Check the "equals" method has been declared
307: Method equalMethod = null;
308: try {
309: equalMethod = c.getDeclaredMethod("equals", //$NON-NLS-1$
310: new Class[] { Object.class });
311: } catch (NoSuchMethodException ex) {
312: // ignore
313: }
314:
315: if (null != equalMethod) {
316: return o1.equals(o2);
317: }
318: }
319:
320: return super.mutatesTo(o1, o2);
321: }
322: }
|