Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/classes/java/beans/PropertyChangeSupport.java
41152 views
1
/*
2
* Copyright (c) 1996, 2021, 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
26
package java.beans;
27
28
import java.io.IOException;
29
import java.io.ObjectInputStream;
30
import java.io.ObjectOutputStream;
31
import java.io.ObjectStreamField;
32
import java.io.Serial;
33
import java.io.Serializable;
34
import java.util.Hashtable;
35
import java.util.Map.Entry;
36
37
/**
38
* This is a utility class that can be used by beans that support bound
39
* properties. It manages a list of listeners and dispatches
40
* {@link PropertyChangeEvent}s to them. You can use an instance of this class
41
* as a member field of your bean and delegate these types of work to it.
42
* The {@link PropertyChangeListener} can be registered for all properties
43
* or for a property specified by name.
44
* <p>
45
* Here is an example of {@code PropertyChangeSupport} usage that follows
46
* the rules and recommendations laid out in the JavaBeans specification:
47
* <pre>
48
* public class MyBean {
49
* private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
50
*
51
* public void addPropertyChangeListener(PropertyChangeListener listener) {
52
* this.pcs.addPropertyChangeListener(listener);
53
* }
54
*
55
* public void removePropertyChangeListener(PropertyChangeListener listener) {
56
* this.pcs.removePropertyChangeListener(listener);
57
* }
58
*
59
* private String value;
60
*
61
* public String getValue() {
62
* return this.value;
63
* }
64
*
65
* public void setValue(String newValue) {
66
* String oldValue = this.value;
67
* this.value = newValue;
68
* this.pcs.firePropertyChange("value", oldValue, newValue);
69
* }
70
*
71
* [...]
72
* }
73
* </pre>
74
* <p>
75
* A {@code PropertyChangeSupport} instance is thread-safe.
76
* <p>
77
* This class is serializable. When it is serialized it will save
78
* (and restore) any listeners that are themselves serializable. Any
79
* non-serializable listeners will be skipped during serialization.
80
*
81
* @see VetoableChangeSupport
82
* @since 1.1
83
*/
84
public class PropertyChangeSupport implements Serializable {
85
private PropertyChangeListenerMap map = new PropertyChangeListenerMap();
86
87
/**
88
* Constructs a {@code PropertyChangeSupport} object.
89
*
90
* @param sourceBean The bean to be given as the source for any events.
91
*/
92
public PropertyChangeSupport(Object sourceBean) {
93
if (sourceBean == null) {
94
throw new NullPointerException();
95
}
96
source = sourceBean;
97
}
98
99
/**
100
* Add a PropertyChangeListener to the listener list.
101
* The listener is registered for all properties.
102
* The same listener object may be added more than once, and will be called
103
* as many times as it is added.
104
* If {@code listener} is null, no exception is thrown and no action
105
* is taken.
106
*
107
* @param listener The PropertyChangeListener to be added
108
*/
109
public void addPropertyChangeListener(PropertyChangeListener listener) {
110
if (listener == null) {
111
return;
112
}
113
if (listener instanceof PropertyChangeListenerProxy) {
114
PropertyChangeListenerProxy proxy =
115
(PropertyChangeListenerProxy)listener;
116
// Call two argument add method.
117
addPropertyChangeListener(proxy.getPropertyName(),
118
proxy.getListener());
119
} else {
120
this.map.add(null, listener);
121
}
122
}
123
124
/**
125
* Remove a PropertyChangeListener from the listener list.
126
* This removes a PropertyChangeListener that was registered
127
* for all properties.
128
* If {@code listener} was added more than once to the same event
129
* source, it will be notified one less time after being removed.
130
* If {@code listener} is null, or was never added, no exception is
131
* thrown and no action is taken.
132
*
133
* @param listener The PropertyChangeListener to be removed
134
*/
135
public void removePropertyChangeListener(PropertyChangeListener listener) {
136
if (listener == null) {
137
return;
138
}
139
if (listener instanceof PropertyChangeListenerProxy) {
140
PropertyChangeListenerProxy proxy =
141
(PropertyChangeListenerProxy)listener;
142
// Call two argument remove method.
143
removePropertyChangeListener(proxy.getPropertyName(),
144
proxy.getListener());
145
} else {
146
this.map.remove(null, listener);
147
}
148
}
149
150
/**
151
* Returns an array of all the listeners that were added to the
152
* PropertyChangeSupport object with addPropertyChangeListener().
153
* <p>
154
* If some listeners have been added with a named property, then
155
* the returned array will be a mixture of PropertyChangeListeners
156
* and {@code PropertyChangeListenerProxy}s. If the calling
157
* method is interested in distinguishing the listeners then it must
158
* test each element to see if it's a
159
* {@code PropertyChangeListenerProxy}, perform the cast, and examine
160
* the parameter.
161
*
162
* <pre>{@code
163
* PropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
164
* for (int i = 0; i < listeners.length; i++) {
165
* if (listeners[i] instanceof PropertyChangeListenerProxy) {
166
* PropertyChangeListenerProxy proxy =
167
* (PropertyChangeListenerProxy)listeners[i];
168
* if (proxy.getPropertyName().equals("foo")) {
169
* // proxy is a PropertyChangeListener which was associated
170
* // with the property named "foo"
171
* }
172
* }
173
* }
174
* }</pre>
175
*
176
* @see PropertyChangeListenerProxy
177
* @return all of the {@code PropertyChangeListeners} added or an
178
* empty array if no listeners have been added
179
* @since 1.4
180
*/
181
public PropertyChangeListener[] getPropertyChangeListeners() {
182
return this.map.getListeners();
183
}
184
185
/**
186
* Add a PropertyChangeListener for a specific property. The listener
187
* will be invoked only when a call on firePropertyChange names that
188
* specific property.
189
* The same listener object may be added more than once. For each
190
* property, the listener will be invoked the number of times it was added
191
* for that property.
192
* If {@code propertyName} or {@code listener} is null, no
193
* exception is thrown and no action is taken.
194
*
195
* @param propertyName The name of the property to listen on.
196
* @param listener The PropertyChangeListener to be added
197
* @since 1.2
198
*/
199
public void addPropertyChangeListener(
200
String propertyName,
201
PropertyChangeListener listener) {
202
if (listener == null || propertyName == null) {
203
return;
204
}
205
listener = this.map.extract(listener);
206
if (listener != null) {
207
this.map.add(propertyName, listener);
208
}
209
}
210
211
/**
212
* Remove a PropertyChangeListener for a specific property.
213
* If {@code listener} was added more than once to the same event
214
* source for the specified property, it will be notified one less time
215
* after being removed.
216
* If {@code propertyName} is null, no exception is thrown and no
217
* action is taken.
218
* If {@code listener} is null, or was never added for the specified
219
* property, no exception is thrown and no action is taken.
220
*
221
* @param propertyName The name of the property that was listened on.
222
* @param listener The PropertyChangeListener to be removed
223
* @since 1.2
224
*/
225
public void removePropertyChangeListener(
226
String propertyName,
227
PropertyChangeListener listener) {
228
if (listener == null || propertyName == null) {
229
return;
230
}
231
listener = this.map.extract(listener);
232
if (listener != null) {
233
this.map.remove(propertyName, listener);
234
}
235
}
236
237
/**
238
* Returns an array of all the listeners which have been associated
239
* with the named property.
240
*
241
* @param propertyName The name of the property being listened to
242
* @return all of the {@code PropertyChangeListeners} associated with
243
* the named property. If no such listeners have been added,
244
* or if {@code propertyName} is null, an empty array is
245
* returned.
246
* @since 1.4
247
*/
248
public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
249
return this.map.getListeners(propertyName);
250
}
251
252
/**
253
* Reports a bound property update to listeners
254
* that have been registered to track updates of
255
* all properties or a property with the specified name.
256
* <p>
257
* No event is fired if old and new values are equal and non-null.
258
* <p>
259
* This is merely a convenience wrapper around the more general
260
* {@link #firePropertyChange(PropertyChangeEvent)} method.
261
*
262
* @param propertyName the programmatic name of the property that was changed
263
* @param oldValue the old value of the property
264
* @param newValue the new value of the property
265
*/
266
public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
267
if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
268
firePropertyChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));
269
}
270
}
271
272
/**
273
* Reports an integer bound property update to listeners
274
* that have been registered to track updates of
275
* all properties or a property with the specified name.
276
* <p>
277
* No event is fired if old and new values are equal.
278
* <p>
279
* This is merely a convenience wrapper around the more general
280
* {@link #firePropertyChange(String, Object, Object)} method.
281
*
282
* @param propertyName the programmatic name of the property that was changed
283
* @param oldValue the old value of the property
284
* @param newValue the new value of the property
285
* @since 1.2
286
*/
287
public void firePropertyChange(String propertyName, int oldValue, int newValue) {
288
if (oldValue != newValue) {
289
firePropertyChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue));
290
}
291
}
292
293
/**
294
* Reports a boolean bound property update to listeners
295
* that have been registered to track updates of
296
* all properties or a property with the specified name.
297
* <p>
298
* No event is fired if old and new values are equal.
299
* <p>
300
* This is merely a convenience wrapper around the more general
301
* {@link #firePropertyChange(String, Object, Object)} method.
302
*
303
* @param propertyName the programmatic name of the property that was changed
304
* @param oldValue the old value of the property
305
* @param newValue the new value of the property
306
* @since 1.2
307
*/
308
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
309
if (oldValue != newValue) {
310
firePropertyChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
311
}
312
}
313
314
/**
315
* Fires a property change event to listeners
316
* that have been registered to track updates of
317
* all properties or a property with the specified name.
318
* <p>
319
* No event is fired if the given event's old and new values are equal and non-null.
320
*
321
* @param event the {@code PropertyChangeEvent} to be fired
322
* @since 1.2
323
*/
324
public void firePropertyChange(PropertyChangeEvent event) {
325
Object oldValue = event.getOldValue();
326
Object newValue = event.getNewValue();
327
if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
328
String name = event.getPropertyName();
329
330
PropertyChangeListener[] common = this.map.get(null);
331
PropertyChangeListener[] named = (name != null)
332
? this.map.get(name)
333
: null;
334
335
fire(common, event);
336
fire(named, event);
337
}
338
}
339
340
private static void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) {
341
if (listeners != null) {
342
for (PropertyChangeListener listener : listeners) {
343
listener.propertyChange(event);
344
}
345
}
346
}
347
348
/**
349
* Reports a bound indexed property update to listeners
350
* that have been registered to track updates of
351
* all properties or a property with the specified name.
352
* <p>
353
* No event is fired if old and new values are equal and non-null.
354
* <p>
355
* This is merely a convenience wrapper around the more general
356
* {@link #firePropertyChange(PropertyChangeEvent)} method.
357
*
358
* @param propertyName the programmatic name of the property that was changed
359
* @param index the index of the property element that was changed
360
* @param oldValue the old value of the property
361
* @param newValue the new value of the property
362
* @since 1.5
363
*/
364
public void fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue) {
365
if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
366
firePropertyChange(new IndexedPropertyChangeEvent(source, propertyName, oldValue, newValue, index));
367
}
368
}
369
370
/**
371
* Reports an integer bound indexed property update to listeners
372
* that have been registered to track updates of
373
* all properties or a property with the specified name.
374
* <p>
375
* No event is fired if old and new values are equal.
376
* <p>
377
* This is merely a convenience wrapper around the more general
378
* {@link #fireIndexedPropertyChange(String, int, Object, Object)} method.
379
*
380
* @param propertyName the programmatic name of the property that was changed
381
* @param index the index of the property element that was changed
382
* @param oldValue the old value of the property
383
* @param newValue the new value of the property
384
* @since 1.5
385
*/
386
public void fireIndexedPropertyChange(String propertyName, int index, int oldValue, int newValue) {
387
if (oldValue != newValue) {
388
fireIndexedPropertyChange(propertyName, index, Integer.valueOf(oldValue), Integer.valueOf(newValue));
389
}
390
}
391
392
/**
393
* Reports a boolean bound indexed property update to listeners
394
* that have been registered to track updates of
395
* all properties or a property with the specified name.
396
* <p>
397
* No event is fired if old and new values are equal.
398
* <p>
399
* This is merely a convenience wrapper around the more general
400
* {@link #fireIndexedPropertyChange(String, int, Object, Object)} method.
401
*
402
* @param propertyName the programmatic name of the property that was changed
403
* @param index the index of the property element that was changed
404
* @param oldValue the old value of the property
405
* @param newValue the new value of the property
406
* @since 1.5
407
*/
408
public void fireIndexedPropertyChange(String propertyName, int index, boolean oldValue, boolean newValue) {
409
if (oldValue != newValue) {
410
fireIndexedPropertyChange(propertyName, index, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
411
}
412
}
413
414
/**
415
* Check if there are any listeners for a specific property, including
416
* those registered on all properties. If {@code propertyName}
417
* is null, only check for listeners registered on all properties.
418
*
419
* @param propertyName the property name.
420
* @return true if there are one or more listeners for the given property
421
* @since 1.2
422
*/
423
public boolean hasListeners(String propertyName) {
424
return this.map.hasListeners(propertyName);
425
}
426
427
/**
428
* Writes serializable fields to stream.
429
*
430
* @param s the {@code ObjectOutputStream} to write
431
* @throws IOException if an I/O error occurs
432
* @serialData Null terminated list of {@code PropertyChangeListeners}.
433
* <p>
434
* At serialization time we skip non-serializable listeners and
435
* only serialize the serializable listeners.
436
*/
437
@Serial
438
private void writeObject(ObjectOutputStream s) throws IOException {
439
Hashtable<String, PropertyChangeSupport> children = null;
440
PropertyChangeListener[] listeners = null;
441
synchronized (this.map) {
442
for (Entry<String, PropertyChangeListener[]> entry : this.map.getEntries()) {
443
String property = entry.getKey();
444
if (property == null) {
445
listeners = entry.getValue();
446
} else {
447
if (children == null) {
448
children = new Hashtable<>();
449
}
450
PropertyChangeSupport pcs = new PropertyChangeSupport(this.source);
451
pcs.map.set(null, entry.getValue());
452
children.put(property, pcs);
453
}
454
}
455
}
456
ObjectOutputStream.PutField fields = s.putFields();
457
fields.put("children", children);
458
fields.put("source", this.source);
459
fields.put("propertyChangeSupportSerializedDataVersion", 2);
460
s.writeFields();
461
462
if (listeners != null) {
463
for (PropertyChangeListener l : listeners) {
464
if (l instanceof Serializable) {
465
s.writeObject(l);
466
}
467
}
468
}
469
s.writeObject(null);
470
}
471
472
/**
473
* Reads the {@code ObjectInputStream}.
474
*
475
* @param s the {@code ObjectInputStream} to read
476
* @throws ClassNotFoundException if the class of a serialized object could
477
* not be found
478
* @throws IOException if an I/O error occurs
479
*/
480
@Serial
481
private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
482
this.map = new PropertyChangeListenerMap();
483
484
ObjectInputStream.GetField fields = s.readFields();
485
486
@SuppressWarnings("unchecked")
487
Hashtable<String, PropertyChangeSupport> children = (Hashtable<String, PropertyChangeSupport>) fields.get("children", null);
488
this.source = fields.get("source", null);
489
fields.get("propertyChangeSupportSerializedDataVersion", 2);
490
491
Object listenerOrNull;
492
while (null != (listenerOrNull = s.readObject())) {
493
this.map.add(null, (PropertyChangeListener)listenerOrNull);
494
}
495
if (children != null) {
496
for (Entry<String, PropertyChangeSupport> entry : children.entrySet()) {
497
for (PropertyChangeListener listener : entry.getValue().getPropertyChangeListeners()) {
498
this.map.add(entry.getKey(), listener);
499
}
500
}
501
}
502
}
503
504
/**
505
* The object to be provided as the "source" for any generated events.
506
*/
507
private Object source;
508
509
/**
510
* @serialField children Hashtable
511
* The list of {@code PropertyChangeListeners}
512
* @serialField source Object
513
* The object to be provided as the "source" for any generated
514
* events
515
* @serialField propertyChangeSupportSerializedDataVersion int
516
* The version
517
*/
518
@Serial
519
private static final ObjectStreamField[] serialPersistentFields = {
520
new ObjectStreamField("children", Hashtable.class),
521
new ObjectStreamField("source", Object.class),
522
new ObjectStreamField("propertyChangeSupportSerializedDataVersion", Integer.TYPE)
523
};
524
525
/**
526
* Use serialVersionUID from JDK 1.1 for interoperability.
527
*/
528
@Serial
529
private static final long serialVersionUID = 6401253773779951803L;
530
531
/**
532
* This is a {@link ChangeListenerMap ChangeListenerMap} implementation
533
* that works with {@link PropertyChangeListener PropertyChangeListener} objects.
534
*/
535
private static final class PropertyChangeListenerMap extends ChangeListenerMap<PropertyChangeListener> {
536
private static final PropertyChangeListener[] EMPTY = {};
537
538
/**
539
* Creates an array of {@link PropertyChangeListener PropertyChangeListener} objects.
540
* This method uses the same instance of the empty array
541
* when {@code length} equals {@code 0}.
542
*
543
* @param length the array length
544
* @return an array with specified length
545
*/
546
@Override
547
protected PropertyChangeListener[] newArray(int length) {
548
return (0 < length)
549
? new PropertyChangeListener[length]
550
: EMPTY;
551
}
552
553
/**
554
* Creates a {@link PropertyChangeListenerProxy PropertyChangeListenerProxy}
555
* object for the specified property.
556
*
557
* @param name the name of the property to listen on
558
* @param listener the listener to process events
559
* @return a {@code PropertyChangeListenerProxy} object
560
*/
561
@Override
562
protected PropertyChangeListener newProxy(String name, PropertyChangeListener listener) {
563
return new PropertyChangeListenerProxy(name, listener);
564
}
565
566
/**
567
* {@inheritDoc}
568
*/
569
public PropertyChangeListener extract(PropertyChangeListener listener) {
570
while (listener instanceof PropertyChangeListenerProxy) {
571
listener = ((PropertyChangeListenerProxy) listener).getListener();
572
}
573
return listener;
574
}
575
}
576
}
577
578