Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/classes/java/beans/DefaultPersistenceDelegate.java
41152 views
1
/*
2
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
package java.beans;
26
27
import java.util.*;
28
import java.lang.reflect.*;
29
import java.util.Objects;
30
import sun.reflect.misc.*;
31
32
33
/**
34
* The {@code DefaultPersistenceDelegate} is a concrete implementation of
35
* the abstract {@code PersistenceDelegate} class and
36
* is the delegate used by default for classes about
37
* which no information is available. The {@code DefaultPersistenceDelegate}
38
* provides, version resilient, public API-based persistence for
39
* classes that follow the JavaBeans conventions without any class specific
40
* configuration.
41
* <p>
42
* The key assumptions are that the class has a nullary constructor
43
* and that its state is accurately represented by matching pairs
44
* of "setter" and "getter" methods in the order they are returned
45
* by the Introspector.
46
* In addition to providing code-free persistence for JavaBeans,
47
* the {@code DefaultPersistenceDelegate} provides a convenient means
48
* to effect persistent storage for classes that have a constructor
49
* that, while not nullary, simply requires some property values
50
* as arguments.
51
*
52
* @see #DefaultPersistenceDelegate(String[])
53
* @see java.beans.Introspector
54
*
55
* @since 1.4
56
*
57
* @author Philip Milne
58
*/
59
60
public class DefaultPersistenceDelegate extends PersistenceDelegate {
61
private static final String[] EMPTY = {};
62
private final String[] constructor;
63
private Boolean definesEquals;
64
65
/**
66
* Creates a persistence delegate for a class with a nullary constructor.
67
*
68
* @see #DefaultPersistenceDelegate(java.lang.String[])
69
*/
70
public DefaultPersistenceDelegate() {
71
this.constructor = EMPTY;
72
}
73
74
/**
75
* Creates a default persistence delegate for a class with a
76
* constructor whose arguments are the values of the property
77
* names as specified by {@code constructorPropertyNames}.
78
* The constructor arguments are created by
79
* evaluating the property names in the order they are supplied.
80
* To use this class to specify a single preferred constructor for use
81
* in the serialization of a particular type, we state the
82
* names of the properties that make up the constructor's
83
* arguments. For example, the {@code Font} class which
84
* does not define a nullary constructor can be handled
85
* with the following persistence delegate:
86
*
87
* <pre>
88
* new DefaultPersistenceDelegate(new String[]{"name", "style", "size"});
89
* </pre>
90
*
91
* @param constructorPropertyNames The property names for the arguments of this constructor.
92
*
93
* @see #instantiate
94
*/
95
public DefaultPersistenceDelegate(String[] constructorPropertyNames) {
96
this.constructor = (constructorPropertyNames == null) ? EMPTY : constructorPropertyNames.clone();
97
}
98
99
private static boolean definesEquals(Class<?> type) {
100
try {
101
return type == type.getMethod("equals", Object.class).getDeclaringClass();
102
}
103
catch(NoSuchMethodException e) {
104
return false;
105
}
106
}
107
108
private boolean definesEquals(Object instance) {
109
if (definesEquals != null) {
110
return (definesEquals == Boolean.TRUE);
111
}
112
else {
113
boolean result = definesEquals(instance.getClass());
114
definesEquals = result ? Boolean.TRUE : Boolean.FALSE;
115
return result;
116
}
117
}
118
119
/**
120
* If the number of arguments in the specified constructor is non-zero and
121
* the class of {@code oldInstance} explicitly declares an "equals" method
122
* this method returns the value of {@code oldInstance.equals(newInstance)}.
123
* Otherwise, this method uses the superclass's definition which returns true if the
124
* classes of the two instances are equal.
125
*
126
* @param oldInstance The instance to be copied.
127
* @param newInstance The instance that is to be modified.
128
* @return True if an equivalent copy of {@code newInstance} may be
129
* created by applying a series of mutations to {@code oldInstance}.
130
*
131
* @see #DefaultPersistenceDelegate(String[])
132
*/
133
protected boolean mutatesTo(Object oldInstance, Object newInstance) {
134
// Assume the instance is either mutable or a singleton
135
// if it has a nullary constructor.
136
return (constructor.length == 0) || !definesEquals(oldInstance) ?
137
super.mutatesTo(oldInstance, newInstance) :
138
oldInstance.equals(newInstance);
139
}
140
141
/**
142
* This default implementation of the {@code instantiate} method returns
143
* an expression containing the predefined method name "new" which denotes a
144
* call to a constructor with the arguments as specified in
145
* the {@code DefaultPersistenceDelegate}'s constructor.
146
*
147
* @param oldInstance The instance to be instantiated.
148
* @param out The code output stream.
149
* @return An expression whose value is {@code oldInstance}.
150
*
151
* @throws NullPointerException if {@code out} is {@code null}
152
* and this value is used in the method
153
*
154
* @see #DefaultPersistenceDelegate(String[])
155
*/
156
protected Expression instantiate(Object oldInstance, Encoder out) {
157
int nArgs = constructor.length;
158
Class<?> type = oldInstance.getClass();
159
Object[] constructorArgs = new Object[nArgs];
160
for(int i = 0; i < nArgs; i++) {
161
try {
162
Method method = findMethod(type, this.constructor[i]);
163
constructorArgs[i] = MethodUtil.invoke(method, oldInstance, new Object[0]);
164
}
165
catch (Exception e) {
166
out.getExceptionListener().exceptionThrown(e);
167
}
168
}
169
return new Expression(oldInstance, oldInstance.getClass(), "new", constructorArgs);
170
}
171
172
private Method findMethod(Class<?> type, String property) {
173
if (property == null) {
174
throw new IllegalArgumentException("Property name is null");
175
}
176
PropertyDescriptor pd = getPropertyDescriptor(type, property);
177
if (pd == null) {
178
throw new IllegalStateException("Could not find property by the name " + property);
179
}
180
Method method = pd.getReadMethod();
181
if (method == null) {
182
throw new IllegalStateException("Could not find getter for the property " + property);
183
}
184
return method;
185
}
186
187
private void doProperty(Class<?> type, PropertyDescriptor pd, Object oldInstance, Object newInstance, Encoder out) throws Exception {
188
Method getter = pd.getReadMethod();
189
Method setter = pd.getWriteMethod();
190
191
if (getter != null && setter != null) {
192
Expression oldGetExp = new Expression(oldInstance, getter.getName(), new Object[]{});
193
Expression newGetExp = new Expression(newInstance, getter.getName(), new Object[]{});
194
Object oldValue = oldGetExp.getValue();
195
Object newValue = newGetExp.getValue();
196
out.writeExpression(oldGetExp);
197
if (!Objects.equals(newValue, out.get(oldValue))) {
198
// Search for a static constant with this value;
199
Object e = (Object[])pd.getValue("enumerationValues");
200
if (e instanceof Object[] && Array.getLength(e) % 3 == 0) {
201
Object[] a = (Object[])e;
202
for(int i = 0; i < a.length; i = i + 3) {
203
try {
204
Field f = type.getField((String)a[i]);
205
if (f.get(null).equals(oldValue)) {
206
out.remove(oldValue);
207
out.writeExpression(new Expression(oldValue, f, "get", new Object[]{null}));
208
}
209
}
210
catch (Exception ex) {}
211
}
212
}
213
invokeStatement(oldInstance, setter.getName(), new Object[]{oldValue}, out);
214
}
215
}
216
}
217
218
static void invokeStatement(Object instance, String methodName, Object[] args, Encoder out) {
219
out.writeStatement(new Statement(instance, methodName, args));
220
}
221
222
// Write out the properties of this instance.
223
private void initBean(Class<?> type, Object oldInstance, Object newInstance, Encoder out) {
224
for (Field field : type.getFields()) {
225
if (!ReflectUtil.isPackageAccessible(field.getDeclaringClass())) {
226
continue;
227
}
228
int mod = field.getModifiers();
229
if (Modifier.isFinal(mod) || Modifier.isStatic(mod) || Modifier.isTransient(mod)) {
230
continue;
231
}
232
try {
233
Expression oldGetExp = new Expression(field, "get", new Object[] { oldInstance });
234
Expression newGetExp = new Expression(field, "get", new Object[] { newInstance });
235
Object oldValue = oldGetExp.getValue();
236
Object newValue = newGetExp.getValue();
237
out.writeExpression(oldGetExp);
238
if (!Objects.equals(newValue, out.get(oldValue))) {
239
out.writeStatement(new Statement(field, "set", new Object[] { oldInstance, oldValue }));
240
}
241
}
242
catch (Exception exception) {
243
out.getExceptionListener().exceptionThrown(exception);
244
}
245
}
246
BeanInfo info;
247
try {
248
info = Introspector.getBeanInfo(type);
249
} catch (IntrospectionException exception) {
250
return;
251
}
252
// Properties
253
for (PropertyDescriptor d : info.getPropertyDescriptors()) {
254
if (d.isTransient()) {
255
continue;
256
}
257
try {
258
doProperty(type, d, oldInstance, newInstance, out);
259
}
260
catch (Exception e) {
261
out.getExceptionListener().exceptionThrown(e);
262
}
263
}
264
265
// Listeners
266
/*
267
Pending(milne). There is a general problem with the archival of
268
listeners which is unresolved as of 1.4. Many of the methods
269
which install one object inside another (typically "add" methods
270
or setters) automatically install a listener on the "child" object
271
so that its "parent" may respond to changes that are made to it.
272
For example the JTable:setModel() method automatically adds a
273
TableModelListener (the JTable itself in this case) to the supplied
274
table model.
275
276
We do not need to explicitly add these listeners to the model in an
277
archive as they will be added automatically by, in the above case,
278
the JTable's "setModel" method. In some cases, we must specifically
279
avoid trying to do this since the listener may be an inner class
280
that cannot be instantiated using public API.
281
282
No general mechanism currently
283
exists for differentiating between these kind of listeners and
284
those which were added explicitly by the user. A mechanism must
285
be created to provide a general means to differentiate these
286
special cases so as to provide reliable persistence of listeners
287
for the general case.
288
*/
289
if (!java.awt.Component.class.isAssignableFrom(type)) {
290
return; // Just handle the listeners of Components for now.
291
}
292
for (EventSetDescriptor d : info.getEventSetDescriptors()) {
293
if (d.isTransient()) {
294
continue;
295
}
296
Class<?> listenerType = d.getListenerType();
297
298
299
// The ComponentListener is added automatically, when
300
// Contatiner:add is called on the parent.
301
if (listenerType == java.awt.event.ComponentListener.class) {
302
continue;
303
}
304
305
// JMenuItems have a change listener added to them in
306
// their "add" methods to enable accessibility support -
307
// see the add method in JMenuItem for details. We cannot
308
// instantiate this instance as it is a private inner class
309
// and do not need to do this anyway since it will be created
310
// and installed by the "add" method. Special case this for now,
311
// ignoring all change listeners on JMenuItems.
312
if (listenerType == javax.swing.event.ChangeListener.class &&
313
type == javax.swing.JMenuItem.class) {
314
continue;
315
}
316
317
EventListener[] oldL = new EventListener[0];
318
EventListener[] newL = new EventListener[0];
319
try {
320
Method m = d.getGetListenerMethod();
321
oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object[]{});
322
newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object[]{});
323
}
324
catch (Exception e2) {
325
try {
326
Method m = type.getMethod("getListeners", new Class<?>[]{Class.class});
327
oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object[]{listenerType});
328
newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object[]{listenerType});
329
}
330
catch (Exception e3) {
331
return;
332
}
333
}
334
335
// Asssume the listeners are in the same order and that there are no gaps.
336
// Eventually, this may need to do true differencing.
337
String addListenerMethodName = d.getAddListenerMethod().getName();
338
for (int i = newL.length; i < oldL.length; i++) {
339
// System.out.println("Adding listener: " + addListenerMethodName + oldL[i]);
340
invokeStatement(oldInstance, addListenerMethodName, new Object[]{oldL[i]}, out);
341
}
342
343
String removeListenerMethodName = d.getRemoveListenerMethod().getName();
344
for (int i = oldL.length; i < newL.length; i++) {
345
invokeStatement(oldInstance, removeListenerMethodName, new Object[]{newL[i]}, out);
346
}
347
}
348
}
349
350
/**
351
* This default implementation of the {@code initialize} method assumes
352
* all state held in objects of this type is exposed via the
353
* matching pairs of "setter" and "getter" methods in the order
354
* they are returned by the Introspector. If a property descriptor
355
* defines a "transient" attribute with a value equal to
356
* {@code Boolean.TRUE} the property is ignored by this
357
* default implementation. Note that this use of the word
358
* "transient" is quite independent of the field modifier
359
* that is used by the {@code ObjectOutputStream}.
360
* <p>
361
* For each non-transient property, an expression is created
362
* in which the nullary "getter" method is applied
363
* to the {@code oldInstance}. The value of this
364
* expression is the value of the property in the instance that is
365
* being serialized. If the value of this expression
366
* in the cloned environment {@code mutatesTo} the
367
* target value, the new value is initialized to make it
368
* equivalent to the old value. In this case, because
369
* the property value has not changed there is no need to
370
* call the corresponding "setter" method and no statement
371
* is emitted. If not however, the expression for this value
372
* is replaced with another expression (normally a constructor)
373
* and the corresponding "setter" method is called to install
374
* the new property value in the object. This scheme removes
375
* default information from the output produced by streams
376
* using this delegate.
377
* <p>
378
* In passing these statements to the output stream, where they
379
* will be executed, side effects are made to the {@code newInstance}.
380
* In most cases this allows the problem of properties
381
* whose values depend on each other to actually help the
382
* serialization process by making the number of statements
383
* that need to be written to the output smaller. In general,
384
* the problem of handling interdependent properties is reduced to
385
* that of finding an order for the properties in
386
* a class such that no property value depends on the value of
387
* a subsequent property.
388
*
389
* @param type the type of the instances
390
* @param oldInstance The instance to be copied.
391
* @param newInstance The instance that is to be modified.
392
* @param out The stream to which any initialization statements should be written.
393
*
394
* @throws NullPointerException if {@code out} is {@code null}
395
*
396
* @see java.beans.Introspector#getBeanInfo
397
* @see java.beans.PropertyDescriptor
398
*/
399
protected void initialize(Class<?> type,
400
Object oldInstance, Object newInstance,
401
Encoder out)
402
{
403
// System.out.println("DefulatPD:initialize" + type);
404
super.initialize(type, oldInstance, newInstance, out);
405
if (oldInstance.getClass() == type) { // !type.isInterface()) {
406
initBean(type, oldInstance, newInstance, out);
407
}
408
}
409
410
private static PropertyDescriptor getPropertyDescriptor(Class<?> type, String property) {
411
try {
412
for (PropertyDescriptor pd : Introspector.getBeanInfo(type).getPropertyDescriptors()) {
413
if (property.equals(pd.getName()))
414
return pd;
415
}
416
} catch (IntrospectionException exception) {
417
}
418
return null;
419
}
420
}
421
422