public class ConfigSyncManager
extends java.lang.Object
WidgetConfig
) with their
respective config owners (ConfigOwner
) - usually widgets.
Do not reference this class directly, instead use SyncConfigChanges
or ConfigManager.syncConfigChanges(Runnable)
to take advantage
of the synchronization feature this class provides.
Widget state is distributed between a widget instance itself and
its associated config. Any changes performed in the config instance must
be synchronized with its owning widget. The core synchronization logic lies
in the concrete implementations of ConfigOwner.afterConfigUpdate(WidgetConfig)
.
This class frees the developer from the burden of keeping track of when
this method should be called, providing proper parameters, handling edge cases
like recursion or synchronization of complex widget structures, batching the
changes so that the method ConfigOwner.afterConfigUpdate(WidgetConfig)
is not called after every config change and determining the correct config owner.
In principal the logic implemented by this class is very simple. When the developer
starts synchronization the class will monitor all the config field assignments.
When a config field is assigned its target instance is considered "dirty" and
saved for later. When the developer stops synchronization all the
dirty instances are synchronized - for each instance its config
owner is determined and ConfigOwner.afterConfigUpdate(WidgetConfig)
called.
The code between the points when synchronization is started and stopped is called
synchronization scope. When scopes are nested, the synchronization is performed only after
the root scope is exited.
Here's an example.
@SyncConfigChanges
void methodWhereConfigsAreChanged()
{
configRef.widthChars = 10;
innerMethodWhereConfigsAreChanged();
}
void innerMethodWhereConfigsAreChanged()
{
...
yetAnotherInnerMethodWhereConfigsAreChanged();
...
}
@SyncConfigChanges
void yetAnotherInnerMethodWhereConfigsAreChanged()
{
configRef2.heightChars = 20;
}
void case1()
{
// assuming no other sync scope exists on the call stack
// methodWhereConfigsAreChanged will start the root
// sync scope
methodWhereConfigsAreChanged();
// after the method exits, the owners of configRef and configRef2
// will be synchronized (by invocation of
// ConfigOwner.afterConfigUpdate(WidgetConfig)
)
}
void case2()
{
// assuming no other sync scope exists on the call stack
// yetAnotherInnerMethodWhereConfigsAreChanged will start
// the root sync scope
innerMethodWhereConfigsAreChanged();
// after yetAnotherInnerMethodWhereConfigsAreChanged exits,
// the owner of configRef2 will be synchronized (by invocation of
// ConfigOwner.afterConfigUpdate(WidgetConfig)
)
}
Synchronization scopes don't cross thread boundaries.
@SyncConfigChanges
void methodWhereConfigsAreChanged()
{
configRef.widthChars = 10;
new Thread().run(innerMethodWhereConfigsAreChanged());
}
void innerMethodWhereConfigsAreChanged()
{
configRef2.heightChars = 20;
}
void case3()
{
methodWhereConfigsAreChanged();
// after methodWhereConfigsAreChanged exits,
// the owner of configRef2 will NOT be synchronized
}
The semantics of ConfigManager.syncConfigChanges(Runnable)
is the same as SyncConfigChanges
. The only difference
is its imperative form.
Modifier and Type | Field and Description |
---|---|
private static java.lang.ThreadLocal<java.util.Map<WidgetConfig,ConfigOwner<?>>> |
configOwnerMap
Map of config references and their respective owners.
|
private static java.lang.ThreadLocal<java.lang.Boolean> |
duplicateInProgress
Flag to prevent recursion during config duplication.
|
private static java.lang.ThreadLocal<java.util.Deque<ConfigOwner<?>>> |
ownerCallstack
This helps to prevent recursion when config update notification
causes recursive notification on the same owner.
|
private static java.lang.ThreadLocal<java.util.Map<WidgetConfig,WidgetConfig>> |
pendingConfigs
Map of configs modified in the current sync scope.
|
private static java.lang.ThreadLocal<java.lang.Integer> |
scopeDepth
The depth of sync scope on the current thread.
|
Constructor and Description |
---|
ConfigSyncManager() |
Modifier and Type | Method and Description |
---|---|
static void |
markScopeEnd()
Marks the end of config synchronization scope on the current thread.
|
static void |
markScopeStart()
Marks the start of config synchronization scope on the current thread.
|
static void |
registerConfig(ConfigOwner<?> owner,
WidgetConfig config)
Registers a modified config for state synchronization
with its owner.
|
private static java.lang.ThreadLocal<java.lang.Integer> scopeDepth
private static java.lang.ThreadLocal<java.util.Map<WidgetConfig,ConfigOwner<?>>> configOwnerMap
private static java.lang.ThreadLocal<java.util.Map<WidgetConfig,WidgetConfig>> pendingConfigs
private static java.lang.ThreadLocal<java.lang.Boolean> duplicateInProgress
private static java.lang.ThreadLocal<java.util.Deque<ConfigOwner<?>>> ownerCallstack
public static void markScopeStart()
public static void markScopeEnd()
public static void registerConfig(ConfigOwner<?> owner, WidgetConfig config)
null
it is later resolved with the help of the config id.owner
- ConfigOwner
reference, may be null.config
- WidgetConfig
reference, must not be null.