Skip to content

Commit 2e7845f

Browse files
committed
Better names for the SocketOwner methods.
1 parent ce6df9c commit 2e7845f

2 files changed

Lines changed: 48 additions & 13 deletions

File tree

README.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ To use the plugin system, you can do:
111111
```kotlin
112112
Shape.Socket.availableIds(): List<String>
113113
Shape.Socket.descriptorForId(id: String): PlugDescriptor?
114-
Shape.Socket.instanceForId(id: String): Shape?
114+
Shape.Socket.singletonForId(id: String): Shape?
115115
```
116116

117-
Which are all built-in to every sublass of `SocketOwner.SingletonById`. You can add more methods too for your usecase.
117+
Which are all public methods of `SocketOwner.SingletonById`. You can add more methods too for your usecase.
118118

119119
### (Id vs Descriptor) and (Singleton vs Ephemeral)
120120

@@ -124,21 +124,32 @@ The `Socket` is responsible for:
124124
- maintaining the runtime registry of available plugins
125125
- instantiating the actual objects from their metadata
126126

127-
When it comes to the registry of available plugins, there are two obvious design points
127+
When it comes to the registry of available plugins, there are two obvious design points:
128128

129129
- declare some String which functions as a unique id => `Id`
130-
- parse the `Map<String, String>` into a descriptor class, and run filters against the set of parsed descriptors to get all the plugins which apply to the given situation => `Descriptor`.
130+
- parse the `Map<String, String>` into a descriptor class, and run filters against the set of parsed descriptors to get all the plugins which apply to a given situation => `Descriptor`.
131131

132132
When it comes to instantiating the actual objects from their metadata, there are again two obvious designs:
133133

134134
- Once a plugin is instantiated, cache it forever and return the same instance each time => `Singleton`
135-
- Call the plugin constructor each time it is instantiated, so that you may end up with multiple instances of a single plugin => `Ephemeral`
135+
- Call the plugin constructor each time it is instantiated, so that you may end up with multiple instances of a single plugin, and unused instances can be garbage collected => `Ephemeral`
136136

137137
In most cases, if a plugin has a unique id, then it also makes sense to treat that plugin as a global singleton => `SocketOwner.SingletonById`. Likewise, if plugins do not have unique ids, then their concept of identity probably doesn't matter so there's no need to cache them as singletons => `SocketOwner.EphemeralByDescriptor`.
138138

139-
Those two classes, `SingletonById` and `EphemeralByDescriptor`, are the only two options we provide out of the box - we did not fill the full 2x2 matrix. For every case we have encountered, we can easily extend one or the other and get exactly what we need.
139+
Those two classes, `SingletonById` and `EphemeralByDescriptor`, are the only two options we provide out of the box - we did not fill the full 2x2 matrix (no `SingletonByDescriptor` or `EphemeralById`) because we have not found a need anywhere in our codebase for the other cases. You are free to implement `SocketOwner` yourself from scratch if you want a different design point.
140140

141-
But you are free to implement `SocketOwner` yourself from scratch if you want a different design point.
141+
The public methods of `SingletonById` are just above this section. `EphemeralByDescriptor` doesn't have any public methods, only protected methods which you can use to build an API appropriate to your case.
142+
143+
```kotlin
144+
abstract class EphemeralByDescriptor<T, ParsedDescriptor> {
145+
protected abstract fun parse(plugDescriptor: PlugDescriptor): ParsedDescriptor
146+
protected fun <R> computeAgainstDescriptors(compute: Function<Set<ParsedDescriptor>, R>) : R
147+
protected fun <R> forEachDescriptor(forEach: Consumer<ParsedDescriptor>)
148+
protected fun descriptorsFor(predicate: Predicate<ParsedDescriptor>): List<ParsedDescriptor>
149+
protected fun instantiateFor(predicate: Predicate<ParsedDescriptor>): List<T>
150+
protected fun instantiateFirst(predicateDescriptor: Predicate<ParsedDescriptor>, order: Comparator<ParsedDescriptor>, predicateInstance: Predicate<T>): T?
151+
}
152+
```
142153

143154
### Working from Java
144155

atplug-runtime/src/main/java/com/diffplug/atplug/SocketOwner.kt

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import java.lang.Exception
1010
import java.lang.IllegalArgumentException
1111
import java.lang.RuntimeException
1212
import java.lang.reflect.Modifier
13+
import java.util.function.Consumer
1314
import java.util.function.Function
1415
import java.util.function.Predicate
1516

@@ -50,15 +51,23 @@ abstract class SocketOwner<T>(val socketClass: Class<T>) {
5051

5152
protected abstract fun parse(plugDescriptor: PlugDescriptor): ParsedDescriptor
5253

53-
protected fun availableDescriptors() = descriptors.keys
54+
protected fun <R> computeAgainstDescriptors(compute: Function<Set<ParsedDescriptor>, R>): R {
55+
synchronized(this) {
56+
return compute.apply(descriptors.keys)
57+
}
58+
}
59+
60+
protected fun <R> forEachDescriptor(forEach: Consumer<ParsedDescriptor>) {
61+
synchronized(this) { descriptors.keys.forEach(forEach) }
62+
}
5463

5564
protected fun descriptorsFor(predicate: Predicate<ParsedDescriptor>): List<ParsedDescriptor> {
5665
synchronized(this) {
5766
return descriptors.keys.filter { predicate.test(it) }
5867
}
5968
}
6069

61-
protected fun instancesFor(predicate: Predicate<ParsedDescriptor>): List<T> {
70+
protected fun instantiateFor(predicate: Predicate<ParsedDescriptor>): List<T> {
6271
val result = mutableListOf<T>()
6372
synchronized(this) {
6473
descriptors.forEach { (parsed, descriptor) ->
@@ -70,6 +79,21 @@ abstract class SocketOwner<T>(val socketClass: Class<T>) {
7079
return result
7180
}
7281

82+
protected fun instantiateFirst(
83+
predicateDescriptor: Predicate<ParsedDescriptor>,
84+
order: Comparator<ParsedDescriptor>,
85+
predicateInstance: Predicate<T>
86+
): T? {
87+
synchronized(this) {
88+
return descriptors
89+
.keys
90+
.filter { predicateDescriptor.test(it) }
91+
.sortedWith(order)
92+
.map { instantiatePlug(descriptors[it]!!) }
93+
.firstOrNull { predicateInstance.test(it) }
94+
}
95+
}
96+
7397
override fun register(plugDescriptor: PlugDescriptor) {
7498
synchronized(this) {
7599
val parsed = parse(plugDescriptor)
@@ -98,7 +122,7 @@ abstract class SocketOwner<T>(val socketClass: Class<T>) {
98122

99123
abstract class SingletonById<T>(socketClass: Class<T>) : SocketOwner<T>(socketClass) {
100124
private val descriptorById = mutableMapOf<String, PlugDescriptor>()
101-
private val instanceById = mutableMapOf<String, T>()
125+
private val singletonById = mutableMapOf<String, T>()
102126
init {
103127
PlugRegistry.registerSocket(socketClass, this)
104128
}
@@ -119,7 +143,7 @@ abstract class SocketOwner<T>(val socketClass: Class<T>) {
119143
val id = plugDescriptor.properties[KEY_ID]!!
120144
val removed = descriptorById.remove(id)
121145
assert(removed != null)
122-
instanceById.remove(id)
146+
singletonById.remove(id)
123147
removeHook(plugDescriptor)
124148
}
125149
}
@@ -141,10 +165,10 @@ abstract class SocketOwner<T>(val socketClass: Class<T>) {
141165
}
142166
}
143167

144-
fun instanceForId(id: String): T? {
168+
fun singletonForId(id: String): T? {
145169
synchronized(this) {
146170
return try {
147-
instanceById.computeIfAbsent(id) { instantiatePlug(descriptorById[it]!!) }
171+
singletonById.computeIfAbsent(id) { instantiatePlug(descriptorById[it]!!) }
148172
} catch (e: NullPointerException) {
149173
null
150174
}

0 commit comments

Comments
 (0)