forked from kohsuke/com4j
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathComThreadSingle.java
More file actions
154 lines (133 loc) · 4.89 KB
/
ComThreadSingle.java
File metadata and controls
154 lines (133 loc) · 4.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package com4j;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* ComThread that enforces all calls to a COM object are performed on the same
* thread that created the object.
*
* <p>
* Usually {@link ComThreadMulti} is used so that COM objects can be passed
* between threads, but when a COM object is constructed outside of Com4j
* it may not be possible to marshal it to one of the Com4j threads, and
* so this class is used instead.
*
* @author Tony Roberts (tony@pyxll.com)
*/
public class ComThreadSingle implements ComThread {
/**
* Used to associate a {@link ComThreadSingle} for every thread using this class.
*/
private static final ThreadLocal<ComThreadSingle> map = new ThreadLocal<ComThreadSingle>() {
public ComThreadSingle initialValue() {
return new ComThreadSingle(Thread.currentThread());
}
};
/**
* Gets the {@link ComThreadSingle} associated with the current thread.
*/
static ComThreadSingle get() {
return map.get();
}
/**
* COM objects that this thread is managing. This thread needs to stick around until they are all gone,
* even when the peer is dead, because other threads might still want to talk to these objects.
*/
private Set<NativePointerPhantomReference> liveComObjects = new HashSet<NativePointerPhantomReference>();
/**
* Listeners attached to this thread.
*/
private final List<ComObjectListener> listeners = new ArrayList<ComObjectListener>();
/**
* The actual thread.
*/
private final Thread thread;
private ComThreadSingle(Thread thread) {
this.thread = thread;
}
public boolean isCurrentThread() {
return Thread.currentThread() == thread;
}
/**
* Keeps track of wrappers that should be IUnknown::release-d.
*/
final ReferenceQueue<Wrapper> collectableObjects = new ReferenceQueue<Wrapper>();
public ReferenceQueue<Wrapper> getCollectableObjects() {
return collectableObjects;
}
/**
* Runs the task in the current thread, and checks that current thread
* is the one associated with this object.
*/
public <T> T execute(Task<T> task) {
// Check we can execute on the current thread
if (!isCurrentThread()) {
throw new RuntimeException("Attempted to call COM object from the wrong thread");
}
// Call the task
T result = task.call();
// Collect any garbage produced by the call
// TODO call this periodically from a windows message loop for this thread
collectGarbage();
return result;
}
/**
* Adds a {@link Com4jObject} to the live objects of this {@link ComThreadMulti}
* <p>
* This method increases the live object count of this thread and fires an
* {@link ComObjectListener#onNewObject(Com4jObject)} event to all listeners.
* </p>
* @param r The new {@link Com4jObject}
*/
public synchronized void addLiveObject( Com4jObject r ) {
if(r instanceof Wrapper) {
liveComObjects.add(((Wrapper)r).ref);
}
if(!listeners.isEmpty()) {
for( int i=listeners.size()-1; i>=0; i-- )
listeners.get(i).onNewObject(r);
}
}
/**
* Adds a {@link ComObjectListener} to this {@link ComThreadSingle}
* @param listener the new listener
* @throws IllegalArgumentException if the <code>listener</code> is <code>null</code> or if the listener is already registered.
*/
public void addListener(ComObjectListener listener) {
if(listener==null)
throw new IllegalArgumentException("listener is null");
if(listeners.contains(listener))
throw new IllegalArgumentException("can't register the same listener twice");
listeners.add(listener);
}
/**
* Removes the {@link ComObjectListener} from this {@link ComThreadSingle}
* @param listener The listener to remove
* @throws IllegalArgumentException if the listener was not registered to this {@link ComThreadSingle}
*/
public void removeListener(ComObjectListener listener) {
if(!listeners.remove(listener))
throw new IllegalArgumentException("listener isn't registered");
}
/**
* Cleans up any left over references
*/
private void collectGarbage() {
// dispose unused objects if any
NativePointerPhantomReference toCollect;
while((toCollect = (NativePointerPhantomReference)collectableObjects.poll()) != null) {
liveComObjects.remove(toCollect);
toCollect.clear();
toCollect.releaseNative();
}
}
/**
* Collects uncollected garbage and removes thread local instance.
*/
static void detach() {
get().collectGarbage();
map.remove();
}
}