experiment.patch
| new/build.gradle 2026-09-25 06:45:17 +0000 | ||
|---|---|---|
| 232 | 232 |
** 121 TG 20260805 Aligned commons-io, commons-lang3, commons-text and commons-configuration2 |
| 233 | 233 |
** across the client, server and convert configurations. |
| 234 | 234 |
** 122 TG 20260825 Upgraded to the latest FWD-H2-1.65. |
| 235 |
** 123 TG 20260924 ASM is a client and server dependency, used for the generated widget config accessors. |
|
| 235 | 236 |
*/ |
| 236 | 237 | |
| 237 | 238 |
/* |
| ... | ... | |
| 778 | 779 |
} |
| 779 | 780 | |
| 780 | 781 |
// ============================================================================ |
| 782 |
// Widget config accessors |
|
| 783 |
// ============================================================================ |
|
| 784 |
tasks.register('generateConfigAccessors', JavaExec) {
|
|
| 785 |
dependsOn 'aspectjWeave' |
|
| 786 |
def outDir = "${layout.buildDirectory.get()}/config-accessors"
|
|
| 787 |
outputs.dir(outDir) |
|
| 788 |
classpath = files("${layout.buildDirectory.get()}/classes.aop") +
|
|
| 789 |
configurations.fwdAllRuntime + |
|
| 790 |
configurations.aspectjCompile |
|
| 791 |
mainClass.set('com.goldencode.p2j.ui.ConfigAccessorGenerator')
|
|
| 792 |
args outDir |
|
| 793 |
doFirst {
|
|
| 794 |
delete outDir |
|
| 795 |
} |
|
| 796 |
} |
|
| 797 | ||
| 798 |
// ============================================================================ |
|
| 781 | 799 |
// Lifecycle task: gates all consumers of build/classes.aop/. |
| 782 | 800 |
// Any task that writes to classes.aop should be added as a dependency here. |
| 783 | 801 |
// ============================================================================ |
| ... | ... | |
| 824 | 842 |
from("${layout.buildDirectory.get()}/admin") {
|
| 825 | 843 |
include 'com/goldencode/p2j/admin/**' |
| 826 | 844 |
} |
| 845 |
from(tasks.named('generateConfigAccessors'))
|
|
| 827 | 846 |
// Include the empty SPI directory to match reference |
| 828 | 847 |
from("${layout.buildDirectory.get()}/classes.aop") {
|
| 829 | 848 |
include 'com/goldencode/p2j/spi/' |
| ... | ... | |
| 1638 | 1657 |
fwdClientServer group: 'org.reflections', name: 'reflections', version: '0.10.2' |
| 1639 | 1658 |
fwdClientServer group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.14' |
| 1640 | 1659 |
fwdClientServer group: 'org.javassist', name: 'javassist', version: '3.30.2-GA' |
| 1660 |
fwdClientServer group: 'org.ow2.asm', name: 'asm', version: '9.8' |
|
| 1641 | 1661 |
fwdClientServer group: 'org.apache.bcel', name: 'bcel', version: '6.10.0' |
| 1642 | 1662 |
fwdClientServer group: 'com.webauthn4j', name: 'webauthn4j-core', version: '0.31.7.RELEASE' |
| 1643 | 1663 |
fwdClientServer group: 'com.google.guava', name: 'guava-gwt', version: '33.4.6-jre' |
| ... | ... | |
| 1656 | 1676 |
} |
| 1657 | 1677 |
|
| 1658 | 1678 |
fwdServer group: 'org.postgresql', name: 'postgresql', version: '42.7.8' |
| 1659 |
fwdServer group: 'org.ow2.asm', name: 'asm', version: '9.8' |
|
| 1660 | 1679 |
fwdServer group: 'com.mchange', name: 'c3p0', version: '0.14.1' |
| 1661 | 1680 |
fwdServer group: 'cglib', name: 'cglib', version: '3.3.0' |
| 1662 | 1681 |
fwdServer group: 'net.sf.ehcache', name: 'ehcache-core', version: '2.6.11' |
| new/src/com/goldencode/p2j/aspects/ui/ConfigFieldSetterAspect.java 2026-09-25 09:30:43 +0000 | ||
|---|---|---|
| 19 | 19 |
** of concrete class. This is needed to allow client-side code to be executed in |
| 20 | 20 |
** the server context. |
| 21 | 21 |
** 010 CA 20260804 Changes to allow the FWD Client to run in the same JVM as the FWD server. |
| 22 |
** 011 TG 20260925 Configs with a virtual ID are skipped; the config caches its tracking config manager. |
|
| 22 | 23 |
*/ |
| 23 | 24 |
/* |
| 24 | 25 |
** This program is free software: you can redistribute it and/or modify |
| ... | ... | |
| 130 | 131 |
"&& !within(com.goldencode.p2j.ui.*Entity) ") |
| 131 | 132 |
public void beforeSetField(WidgetConfig cfg, JoinPoint.StaticPart sp) |
| 132 | 133 |
{
|
| 134 |
if (WidgetId.virtualWidget(cfg.id)) |
|
| 135 |
{
|
|
| 136 |
// not tracked, see ConfigManager.trackConfig |
|
| 137 |
return; |
|
| 138 |
} |
|
| 139 |
|
|
| 133 | 140 |
String field = sp.getSignature().getName(); |
| 134 |
ClientConfigManager.getInstance().addDirtyConfig(cfg, field);
|
|
| 141 |
cfg.trackingManager().addDirtyConfig(cfg, field);
|
|
| 135 | 142 |
} |
| 136 | 143 |
} |
| new/src/com/goldencode/p2j/aspects/ui/LocationCacheAspect.java 2026-09-25 08:51:10 +0000 | ||
|---|---|---|
| 9 | 9 |
** 002 CA 20180209 A fix - iajc compiler requires absolute names in the aspect definition. |
| 10 | 10 |
** 003 HC 20240806 Added support for displaying frame widget in an overlay window. |
| 11 | 11 |
** 004 CA 20260804 Changes to allow the FWD Client to run in the same JVM as the FWD server. |
| 12 |
** 005 TG 20260925 The layout depth is a mutable per-context counter, read once per join point. |
|
| 12 | 13 |
*/ |
| 13 | 14 |
/* |
| 14 | 15 |
** This program is free software: you can redistribute it and/or modify |
| ... | ... | |
| 88 | 89 |
* Tracks the depth of layout-related nested API calls. When is zero, it will force the |
| 89 | 90 |
* {@link AbstractWidget#reflectLocationChanged() cached values to be reset.}
|
| 90 | 91 |
*/ |
| 91 |
private static final ContextLocal<Integer> local = new ContextLocal<>()
|
|
| 92 |
private static final ContextLocal<int[]> local = new ContextLocal<>()
|
|
| 92 | 93 |
{
|
| 93 |
protected Integer initialValue()
|
|
| 94 |
protected int[] initialValue()
|
|
| 94 | 95 |
{
|
| 95 |
return Integer.valueOf(0);
|
|
| 96 |
return new int[1];
|
|
| 96 | 97 |
} |
| 97 | 98 |
}; |
| 98 | 99 |
|
| ... | ... | |
| 118 | 119 |
Container<?> container) |
| 119 | 120 |
throws Throwable |
| 120 | 121 |
{
|
| 121 |
int layoutDepth = local.get(); |
|
| 122 |
int[] layoutDepth = local.get();
|
|
| 122 | 123 |
AbstractWidget<?> widget = (AbstractWidget<?>) container; |
| 123 | 124 |
|
| 124 |
if (layoutDepth == 0) |
|
| 125 |
if (layoutDepth[0] == 0)
|
|
| 125 | 126 |
{
|
| 126 | 127 |
// ensure nothing is cached |
| 127 | 128 |
widget.reflectLocationChanged(); |
| 128 | 129 |
} |
| 129 | 130 | |
| 130 |
layoutDepth += 1; |
|
| 131 |
layoutDepth[0] += 1;
|
|
| 131 | 132 |
|
| 132 | 133 |
try |
| 133 | 134 |
{
|
| 134 |
local.set(layoutDepth); |
|
| 135 | ||
| 136 | 135 |
return thisJoinPoint.proceed(); |
| 137 | 136 |
} |
| 138 | 137 |
finally |
| 139 | 138 |
{
|
| 140 | 139 |
// no need to reflectLocationChange, as nothing gets cached while the depth is non-zero |
| 141 |
layoutDepth -= 1; |
|
| 142 | ||
| 143 |
local.set(layoutDepth); |
|
| 140 |
layoutDepth[0] -= 1; |
|
| 144 | 141 |
} |
| 145 | 142 |
} |
| 146 | 143 | |
| ... | ... | |
| 163 | 160 |
public Object disableLocationCacheInContainer(ProceedingJoinPoint thisJoinPoint) |
| 164 | 161 |
throws Throwable |
| 165 | 162 |
{
|
| 166 |
int layoutDepth = local.get(); |
|
| 163 |
int[] layoutDepth = local.get();
|
|
| 167 | 164 |
|
| 168 | 165 |
AbstractWidget<?> widget = (AbstractWidget<?>) thisJoinPoint.getThis(); |
| 169 | 166 | |
| 170 |
if (layoutDepth == 0) |
|
| 167 |
if (layoutDepth[0] == 0)
|
|
| 171 | 168 |
{
|
| 172 | 169 |
// ensure nothing is cached |
| 173 | 170 |
widget.reflectLocationChanged(); |
| 174 | 171 |
} |
| 175 | 172 | |
| 176 |
layoutDepth += 1; |
|
| 177 | ||
| 173 |
layoutDepth[0] += 1;
|
|
| 174 |
|
|
| 178 | 175 |
try |
| 179 | 176 |
{
|
| 180 |
local.set(layoutDepth); |
|
| 181 |
|
|
| 182 | 177 |
return thisJoinPoint.proceed(); |
| 183 | 178 |
} |
| 184 | 179 |
finally |
| 185 | 180 |
{
|
| 186 | 181 |
// no need to reflectLocationChange, as nothing gets cached while the depth is non-zero |
| 187 |
layoutDepth -= 1; |
|
| 188 |
|
|
| 189 |
local.set(layoutDepth); |
|
| 182 |
layoutDepth[0] -= 1; |
|
| 190 | 183 |
} |
| 191 | 184 |
} |
| 192 | 185 |
|
| ... | ... | |
| 208 | 201 |
public Object cacheScreenLocation(ProceedingJoinPoint thisJoinPoint) |
| 209 | 202 |
throws Throwable |
| 210 | 203 |
{
|
| 211 |
int layoutDepth = local.get(); |
|
| 204 |
int layoutDepth = local.get()[0];
|
|
| 212 | 205 |
|
| 213 | 206 |
AbstractWidget<?> widget = (AbstractWidget<?>) thisJoinPoint.getThis(); |
| 214 | 207 | |
| ... | ... | |
| 249 | 242 |
public Object cacheScreenPhysicalLocation(ProceedingJoinPoint thisJoinPoint) |
| 250 | 243 |
throws Throwable |
| 251 | 244 |
{
|
| 252 |
int layoutDepth = local.get(); |
|
| 245 |
int layoutDepth = local.get()[0];
|
|
| 253 | 246 |
|
| 254 | 247 |
AbstractWidget<?> widget = (AbstractWidget<?>) thisJoinPoint.getThis(); |
| 255 | 248 |
|
| new/src/com/goldencode/p2j/aspects/ui/SyncConfigChangesAspect.java 2026-09-25 09:30:39 +0000 | ||
|---|---|---|
| 13 | 13 |
** of concrete class. This is needed to allow client-side code to be executed in |
| 14 | 14 |
** the server context. |
| 15 | 15 |
** 005 RNC 20260402 Fixed javadoc warnings. |
| 16 |
** 006 TG 20260925 The trackingManager() cache assignment is not a config value change. |
|
| 16 | 17 |
*/ |
| 17 | 18 |
/* |
| 18 | 19 |
** This program is free software: you can redistribute it and/or modify |
| ... | ... | |
| 151 | 152 |
*/ |
| 152 | 153 |
@Before("target(config) && set(* *..WidgetConfig+.*)" +
|
| 153 | 154 |
"&& !withincode(void readExternal(..)) " + |
| 155 |
"&& !withincode(* com.goldencode.p2j.ui.WidgetConfig.trackingManager()) " + |
|
| 154 | 156 |
"&& !withincode(void applyConfig(..)) " + |
| 155 | 157 |
"&& !withincode(private void com.goldencode.p2j.ui.LabelConfig.setDefaults(..)) " + |
| 156 | 158 |
"&& !withincode(com.goldencode.p2j.ui.*Config.new(..))" + |
| new/src/com/goldencode/p2j/main/StandardServer.java 2026-09-25 09:00:59 +0000 | ||
|---|---|---|
| 297 | 297 |
** 146 TG 20260703 Registered ChunkTransfer as its own network group so a peer (e.g. an |
| 298 | 298 |
** appserver agent) can pull streamed binary payloads from this server. |
| 299 | 299 |
** 147 CA 20260803 FWD Client can run entirely in the FWD Server JVM now, even in non-MSA mode. |
| 300 |
** 148 TG 20260925 Server-side batch client thread uses the ContextLocal read cache. |
|
| 300 | 301 |
*/ |
| 301 | 302 | |
| 302 | 303 |
/* |
| ... | ... | |
| 1062 | 1063 |
} |
| 1063 | 1064 | |
| 1064 | 1065 |
session = SessionManager.get().initializeServerSession(context); |
| 1066 |
|
|
| 1067 |
// this thread stays in the new session's context until it terminates |
|
| 1068 |
ContextLocal.setReadCache(true); |
|
| 1065 | 1069 |
} |
| 1066 | 1070 |
catch (Exception e) |
| 1067 | 1071 |
{
|
| ... | ... | |
| 1158 | 1162 |
} |
| 1159 | 1163 |
finally |
| 1160 | 1164 |
{
|
| 1165 |
ContextLocal.setReadCache(false); |
|
| 1166 |
|
|
| 1161 | 1167 |
try |
| 1162 | 1168 |
{
|
| 1163 | 1169 |
// needs to be done first |
| ... | ... | |
| 1202 | 1208 |
} |
| 1203 | 1209 |
}; |
| 1204 | 1210 |
|
| 1205 |
Thread launcher = new Thread(runner); |
|
| 1211 |
Thread launcher = new ContextLocal.DedicatedThread(runner);
|
|
| 1206 | 1212 |
launcher.setName("Launcher for server-side batch process " + account);
|
| 1207 | 1213 |
launcher.setDaemon(true); |
| 1208 | 1214 |
launcher.start(); |
| new/src/com/goldencode/p2j/net/SessionManager.java 2026-09-25 09:42:07 +0000 | ||
|---|---|---|
| 105 | 105 |
** 047 AS 20260617 Harden session context ID seeding with SecureRandom. |
| 106 | 106 |
** 048 CA 20260803 When a FWD Client runs in the same JVM as the FWD Server, a mock net session is |
| 107 | 107 |
** required to allow session-level listeners to execute. |
| 108 |
** 049 TG 20260925 get() and isAsyncThread() do not use JVM-wide locks. |
|
| 108 | 109 |
*/ |
| 109 | 110 | |
| 110 | 111 |
/* |
| ... | ... | |
| 223 | 224 |
/** Singleton instance of this class */ |
| 224 | 225 |
private static SessionManager instance = null; |
| 225 | 226 |
|
| 227 |
/** The {@link #instance} after it was fully constructed, read without locking by {@link #get}. */
|
|
| 228 |
private static volatile SessionManager published = null; |
|
| 229 |
|
|
| 226 | 230 |
/** Topology database for this network node */ |
| 227 | 231 |
protected final Map<InetSocketAddress, Queue> topology = new HashMap<>(); |
| 228 | 232 |
|
| ... | ... | |
| 275 | 279 |
protected BootstrapConfig config = null; |
| 276 | 280 | |
| 277 | 281 |
/** A set containing threads which always send async requests. */ |
| 278 |
private final Set<Thread> asyncThreads = Collections.newSetFromMap(new IdentityHashMap<>());
|
|
| 282 |
private final Set<Thread> asyncThreads = ConcurrentHashMap.newKeySet();
|
|
| 279 | 283 |
|
| 280 | 284 |
/** The handler used to process interrupt requests. */ |
| 281 | 285 |
private InterruptHandler interruptHandler; |
| ... | ... | |
| 335 | 339 |
* @return Singleton session manager, if it has been created; otherwise, |
| 336 | 340 |
* <code>null</code>. |
| 337 | 341 |
*/ |
| 338 |
public synchronized static SessionManager get()
|
|
| 342 |
public static SessionManager get() |
|
| 339 | 343 |
{
|
| 340 |
return instance; |
|
| 344 |
SessionManager sm = published; |
|
| 345 |
if (sm != null) |
|
| 346 |
{
|
|
| 347 |
return sm; |
|
| 348 |
} |
|
| 349 |
|
|
| 350 |
// the instance is created while holding this lock; do not publish it if this thread is the one |
|
| 351 |
// constructing it |
|
| 352 |
boolean nested = Thread.holdsLock(SessionManager.class); |
|
| 353 |
synchronized (SessionManager.class) |
|
| 354 |
{
|
|
| 355 |
sm = instance; |
|
| 356 |
if (!nested) |
|
| 357 |
{
|
|
| 358 |
published = sm; |
|
| 359 |
} |
|
| 360 |
} |
|
| 361 |
|
|
| 362 |
return sm; |
|
| 341 | 363 |
} |
| 342 | 364 |
|
| 343 | 365 |
/** |
| ... | ... | |
| 348 | 370 |
*/ |
| 349 | 371 |
public final void registerAsyncThread(Thread thread) |
| 350 | 372 |
{
|
| 351 |
synchronized (asyncThreads) |
|
| 352 |
{
|
|
| 353 |
asyncThreads.add(thread); |
|
| 354 |
} |
|
| 373 |
asyncThreads.add(thread); |
|
| 355 | 374 |
} |
| 356 | 375 |
|
| 357 | 376 |
/** |
| ... | ... | |
| 362 | 381 |
*/ |
| 363 | 382 |
public final void deregisterAsyncThread(Thread thread) |
| 364 | 383 |
{
|
| 365 |
synchronized (asyncThreads) |
|
| 366 |
{
|
|
| 367 |
asyncThreads.remove(thread); |
|
| 368 |
} |
|
| 384 |
asyncThreads.remove(thread); |
|
| 369 | 385 |
} |
| 370 | 386 |
|
| 371 | 387 |
/** |
| ... | ... | |
| 379 | 395 |
*/ |
| 380 | 396 |
public final boolean isAsyncThread(Thread thread) |
| 381 | 397 |
{
|
| 382 |
synchronized (asyncThreads) |
|
| 383 |
{
|
|
| 384 |
return asyncThreads.contains(thread); |
|
| 385 |
} |
|
| 398 |
// checked twice for each call of an in-JVM client, without a JVM-wide lock |
|
| 399 |
return !asyncThreads.isEmpty() && asyncThreads.contains(thread); |
|
| 386 | 400 |
} |
| 387 | 401 |
|
| 388 | 402 |
/** |
| new/src/com/goldencode/p2j/security/ContextLocal.java 2026-09-25 09:08:04 +0000 | ||
|---|---|---|
| 88 | 88 |
** 029 CA 20240903 For standalone mode, 'fallback' thread-local var must be assigned on first usage |
| 89 | 89 |
** in get/set. |
| 90 | 90 |
** 030 CA 20260803 Standalone FWD Client JVMs must also clean up this data. |
| 91 |
** 031 TG 20260925 Added DedicatedThread, which can use the read cache like conversation threads. |
|
| 91 | 92 |
*/ |
| 92 | 93 | |
| 93 | 94 |
/* |
| ... | ... | |
| 811 | 812 |
*/ |
| 812 | 813 |
private static boolean hasReadCache() |
| 813 | 814 |
{
|
| 814 |
return SessionManager.isConversationThread(); |
|
| 815 |
if (SessionManager.isConversationThread()) |
|
| 816 |
{
|
|
| 817 |
return true; |
|
| 818 |
} |
|
| 819 |
|
|
| 820 |
Thread t = Thread.currentThread(); |
|
| 821 |
return t instanceof DedicatedThread && ((DedicatedThread) t).readCache; |
|
| 822 |
} |
|
| 823 |
|
|
| 824 |
/** |
|
| 825 |
* Enable or disable the read cache for the current thread, which must be a {@link DedicatedThread}.
|
|
| 826 |
* The cache must be enabled only while the thread stays in the same security context. |
|
| 827 |
* |
|
| 828 |
* @param on |
|
| 829 |
* <code>true</code> to enable the read cache. |
|
| 830 |
*/ |
|
| 831 |
public static void setReadCache(boolean on) |
|
| 832 |
{
|
|
| 833 |
Thread t = Thread.currentThread(); |
|
| 834 |
if (!(t instanceof DedicatedThread)) |
|
| 835 |
{
|
|
| 836 |
throw new IllegalStateException("The read cache can be enabled only on a DedicatedThread.");
|
|
| 837 |
} |
|
| 838 |
|
|
| 839 |
((DedicatedThread) t).readCache = on; |
|
| 840 |
} |
|
| 841 |
|
|
| 842 |
/** |
|
| 843 |
* A thread which is dedicated to a single security context for (a part of) its lifetime, like a |
|
| 844 |
* conversation thread. While it is in that context, it can enable the read cache used to optimize |
|
| 845 |
* {@link ContextLocal#get()}, via {@link ContextLocal#setReadCache}.
|
|
| 846 |
*/ |
|
| 847 |
public static class DedicatedThread |
|
| 848 |
extends Thread |
|
| 849 |
{
|
|
| 850 |
/** Flag indicating if the read cache is enabled; accessed only by this thread. */ |
|
| 851 |
private boolean readCache = false; |
|
| 852 |
|
|
| 853 |
/** |
|
| 854 |
* Create a new thread. |
|
| 855 |
* |
|
| 856 |
* @param target |
|
| 857 |
* The code to run. |
|
| 858 |
*/ |
|
| 859 |
public DedicatedThread(Runnable target) |
|
| 860 |
{
|
|
| 861 |
super(target); |
|
| 862 |
} |
|
| 815 | 863 |
} |
| 816 | 864 |
|
| 817 | 865 |
/** |
| new/src/com/goldencode/p2j/ui/ClientConfigManager.java 2026-09-25 10:20:48 +0000 | ||
|---|---|---|
| 4 | 4 |
** |
| 5 | 5 |
** Copyright (c) 2014-2026, Golden Code Development Corporation. |
| 6 | 6 |
** |
| 7 |
** -#- -I- --Date-- ---------------------------------Description----------------------------------
|
|
| 7 |
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
|
|
| 8 | 8 |
** 001 CA 20140929 Created initial version. |
| 9 | 9 |
** 002 CA 20141014 Separated data which needs to survive the appserver agent's context reset into |
| 10 | 10 |
** a different container. |
| ... | ... | |
| 41 | 41 |
** 023 TG 20260414 Replaced ReflectASM with MethodHandles. |
| 42 | 42 |
** 024 CA 20260803 FWD Client can run entirely in the FWD Server JVM now - so any constant field is marked as |
| 43 | 43 |
** an explicit constant. |
| 44 |
** 025 TG 20260924 Field access via generated ConfigAccessor; added setConfigFields for batched updates. |
|
| 45 |
** 026 TG 20260925 Added setBackupField(s), to apply server-pushed values to a backup config. |
|
| 46 |
** 027 TG 20260925 Added snapshotConfig and firstDifference, used by FrameLayoutCache. |
|
| 44 | 47 |
*/ |
| 45 | 48 | |
| 46 | 49 |
/* |
| ... | ... | |
| 198 | 201 |
} |
| 199 | 202 | |
| 200 | 203 |
/** |
| 204 |
* Set the widget config fields to the specified values. |
|
| 205 |
* |
|
| 206 |
* @param wid |
|
| 207 |
* The widget ID. |
|
| 208 |
* @param wcfg |
|
| 209 |
* The widget configuration; if this is a down widget, this will be used. |
|
| 210 |
* @param fids |
|
| 211 |
* The config field ids. |
|
| 212 |
* @param values |
|
| 213 |
* The new config field values. |
|
| 214 |
*/ |
|
| 215 |
public void setConfigFields(WidgetId wid, WidgetConfig wcfg, Integer[] fids, Object[] values) |
|
| 216 |
{
|
|
| 217 |
if (!(wid instanceof WidgetDownId)) |
|
| 218 |
{
|
|
| 219 |
wcfg = getActiveConfig(wid); |
|
| 220 |
} |
|
| 221 |
WidgetConfigDef wdef = getConfigDef(wid.asInt()); |
|
| 222 | ||
| 223 |
int[] ids = new int[fids.length]; |
|
| 224 |
for (int i = 0; i < fids.length; i++) |
|
| 225 |
{
|
|
| 226 |
ids[i] = fids[i]; |
|
| 227 |
} |
|
| 228 | ||
| 229 |
setWidgetConfigs(wcfg, wdef, ids, values); |
|
| 230 |
} |
|
| 231 | ||
| 232 |
/** |
|
| 233 |
* Apply the values of the specified fields, pushed by the server-side, to the backup config of a |
|
| 234 |
* (non-down) widget, so the backup config reflects what the server-side knows. Only fields tracked |
|
| 235 |
* for changed values (immutable types) are applied; the other fields are always sent when dirty. |
|
| 236 |
* |
|
| 237 |
* @param wid |
|
| 238 |
* The widget ID. |
|
| 239 |
* @param fname |
|
| 240 |
* The config field name. |
|
| 241 |
* @param value |
|
| 242 |
* The value pushed by the server-side. |
|
| 243 |
*/ |
|
| 244 |
public void setBackupField(WidgetId wid, String fname, Object value) |
|
| 245 |
{
|
|
| 246 |
WidgetConfig backup = (wid instanceof WidgetDownId) ? null : backupConfigs.get(wid); |
|
| 247 |
if (backup == null) |
|
| 248 |
{
|
|
| 249 |
return; |
|
| 250 |
} |
|
| 251 |
|
|
| 252 |
WidgetConfigDef wdef = getConfigDef(wid.asInt()); |
|
| 253 |
Integer fid = wdef.fieldIds.get(fname); |
|
| 254 |
if (fid != null && wdef.trackChanged[fid]) |
|
| 255 |
{
|
|
| 256 |
// the backup config has no owner, it must not be registered for state synchronization |
|
| 257 |
super.setWidgetConfig(backup, value, wdef, fid); |
|
| 258 |
} |
|
| 259 |
} |
|
| 260 | ||
| 261 |
/** |
|
| 262 |
* Apply the values of the specified fields, pushed by the server-side, to the backup config of a |
|
| 263 |
* (non-down) widget; see {@link #setBackupField}.
|
|
| 264 |
* |
|
| 265 |
* @param wid |
|
| 266 |
* The widget ID. |
|
| 267 |
* @param fids |
|
| 268 |
* The config field ids. |
|
| 269 |
* @param values |
|
| 270 |
* The values pushed by the server-side. |
|
| 271 |
*/ |
|
| 272 |
public void setBackupFields(WidgetId wid, Integer[] fids, Object[] values) |
|
| 273 |
{
|
|
| 274 |
WidgetConfig backup = (wid instanceof WidgetDownId) ? null : backupConfigs.get(wid); |
|
| 275 |
if (backup == null) |
|
| 276 |
{
|
|
| 277 |
return; |
|
| 278 |
} |
|
| 279 |
|
|
| 280 |
WidgetConfigDef wdef = getConfigDef(wid.asInt()); |
|
| 281 |
for (int i = 0; i < fids.length; i++) |
|
| 282 |
{
|
|
| 283 |
int fid = fids[i]; |
|
| 284 |
if (wdef.trackChanged[fid]) |
|
| 285 |
{
|
|
| 286 |
super.setWidgetConfig(backup, values[i], wdef, fid); |
|
| 287 |
} |
|
| 288 |
} |
|
| 289 |
} |
|
| 290 | ||
| 291 |
/** |
|
| 292 |
* Create a copy of the given config, which is not registered anywhere and has no ID. It is meant |
|
| 293 |
* for comparisons, see {@link #firstDifference}.
|
|
| 294 |
* |
|
| 295 |
* @param cfg |
|
| 296 |
* The config to copy. |
|
| 297 |
* |
|
| 298 |
* @return The copy. |
|
| 299 |
*/ |
|
| 300 |
public WidgetConfig snapshotConfig(WidgetConfig cfg) |
|
| 301 |
{
|
|
| 302 |
WidgetConfig copy = getConfigDef(cfg.cfgClass).newInstance(); |
|
| 303 |
ConfigSyncManager.applyUnregistered(copy, cfg); |
|
| 304 |
|
|
| 305 |
return copy; |
|
| 306 |
} |
|
| 307 |
|
|
| 308 |
/** |
|
| 309 |
* Find the first field which has different values in the given configs of the same class. |
|
| 310 |
* |
|
| 311 |
* @param a |
|
| 312 |
* The first config. |
|
| 313 |
* @param b |
|
| 314 |
* The second config. |
|
| 315 |
* @param ignored |
|
| 316 |
* The names of the fields which are not compared. |
|
| 317 |
* |
|
| 318 |
* @return The name of the first different field, or <code>null</code> if all compared fields have |
|
| 319 |
* the same values. |
|
| 320 |
*/ |
|
| 321 |
public String firstDifference(WidgetConfig a, WidgetConfig b, Set<String> ignored) |
|
| 322 |
{
|
|
| 323 |
if (a.cfgClass != b.cfgClass) |
|
| 324 |
{
|
|
| 325 |
return "<class>"; |
|
| 326 |
} |
|
| 327 |
|
|
| 328 |
WidgetConfigDef wdef = getConfigDef(a.cfgClass); |
|
| 329 |
for (int fid = 0; fid < wdef.fieldCount; fid++) |
|
| 330 |
{
|
|
| 331 |
if (!wdef.accessor.same(a, b, fid)) |
|
| 332 |
{
|
|
| 333 |
String name = wdef.fieldNames.get(fid); |
|
| 334 |
if (!ignored.contains(name)) |
|
| 335 |
{
|
|
| 336 |
return name; |
|
| 337 |
} |
|
| 338 |
} |
|
| 339 |
} |
|
| 340 |
|
|
| 341 |
return null; |
|
| 342 |
} |
|
| 343 | ||
| 344 |
/** |
|
| 201 | 345 |
* Inform the client-side that it can no longer send config updates. |
| 202 | 346 |
*/ |
| 203 | 347 |
public void deactivateConfigUpdates() |
| ... | ... | |
| 289 | 433 | |
| 290 | 434 |
try |
| 291 | 435 |
{
|
| 292 |
Object activeVal = wdef.fields[fid].get(cfg); |
|
| 293 |
Object backupVal = wdef.fields[fid].get(backup); |
|
| 294 | ||
| 295 |
boolean changed = !((activeVal == null && backupVal == null) || |
|
| 296 |
(activeVal != null && activeVal.equals(backupVal))); |
|
| 297 |
|
|
| 298 |
if (changed) |
|
| 436 |
if (!wdef.accessor.same(cfg, backup, fid)) |
|
| 299 | 437 |
{
|
| 300 | 438 |
addDirtyConfig(cfg, fname); |
| 301 | 439 |
} |
| ... | ... | |
| 367 | 505 |
ConfigSyncManager.registerConfig(null, cfg); |
| 368 | 506 |
super.setWidgetConfig(cfg, value, wdef, fid); |
| 369 | 507 |
} |
| 508 | ||
| 509 |
/** |
|
| 510 |
* Set the specified widget configuration fields to the given values. |
|
| 511 |
* |
|
| 512 |
* @param cfg |
|
| 513 |
* The widget configuration to be updated. |
|
| 514 |
* @param wdef |
|
| 515 |
* The definition of the widget configuration. |
|
| 516 |
* @param fids |
|
| 517 |
* The IDs of the changed fields. |
|
| 518 |
* @param values |
|
| 519 |
* The new field values. |
|
| 520 |
*/ |
|
| 521 |
@Override |
|
| 522 |
<T extends WidgetConfig> void setWidgetConfigs(T cfg, |
|
| 523 |
WidgetConfigDef wdef, |
|
| 524 |
int[] fids, |
|
| 525 |
Object[] values) |
|
| 526 |
{
|
|
| 527 |
ConfigSyncManager.registerConfig(null, cfg); |
|
| 528 |
super.setWidgetConfigs(cfg, wdef, fids, values); |
|
| 529 |
} |
|
| 370 | 530 |
|
| 371 | 531 |
/** |
| 372 | 532 |
* Helper method to replace the entry in the given map, to use the new key. |
| new/src/com/goldencode/p2j/ui/ConfigAccessor.java 2026-09-25 06:45:17 +0000 | ||
|---|---|---|
| 1 |
/* |
|
| 2 |
** Module : ConfigAccessor.java |
|
| 3 |
** Abstract : Indexed access to the tracked fields of a widget configuration class. |
|
| 4 |
** |
|
| 5 |
** Copyright (c) 2026, Golden Code Development Corporation. |
|
| 6 |
** |
|
| 7 |
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------- |
|
| 8 |
** 001 TG 20260924 Created initial version. |
|
| 9 |
*/ |
|
| 10 | ||
| 11 |
/* |
|
| 12 |
** This program is free software: you can redistribute it and/or modify |
|
| 13 |
** it under the terms of the GNU Affero General Public License as |
|
| 14 |
** published by the Free Software Foundation, either version 3 of the |
|
| 15 |
** License, or (at your option) any later version. |
|
| 16 |
** |
|
| 17 |
** This program is distributed in the hope that it will be useful, |
|
| 18 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 19 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 20 |
** GNU Affero General Public License for more details. |
|
| 21 |
** |
|
| 22 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
| 23 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
| 24 |
** |
|
| 25 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
| 26 |
** |
|
| 27 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
| 28 |
** terms apply to the works covered under the License. These additional terms |
|
| 29 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
| 30 |
** Affero GPL version 3 and may not be removed by you. |
|
| 31 |
** |
|
| 32 |
** 0. Attribution Requirement. |
|
| 33 |
** |
|
| 34 |
** You must preserve all legal notices or author attributions in the covered |
|
| 35 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
| 36 |
** work. You may not remove from the covered work any author or developer |
|
| 37 |
** credit already included within the covered work. |
|
| 38 |
** |
|
| 39 |
** 1. No License To Use Trademarks. |
|
| 40 |
** |
|
| 41 |
** This license does not grant any license or rights to use the trademarks |
|
| 42 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
| 43 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
| 44 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
| 45 |
** publicity purposes without written authorization. |
|
| 46 |
** |
|
| 47 |
** 2. No Misrepresentation of Affiliation. |
|
| 48 |
** |
|
| 49 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
| 50 |
** |
|
| 51 |
** You may not represent yourself for publicity purposes as associated with |
|
| 52 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
| 53 |
** the covered work, without written authorization. |
|
| 54 |
** |
|
| 55 |
** 3. No Misrepresentation of Source or Origin. |
|
| 56 |
** |
|
| 57 |
** You may not represent the covered work as solely your work. All modified |
|
| 58 |
** versions of the covered work must be marked in a reasonable way to make it |
|
| 59 |
** clear that the modified work is not originating from Golden Code Development |
|
| 60 |
** Corporation or FWD. All modified versions must contain the notices of |
|
| 61 |
** attribution required in this license. |
|
| 62 |
*/ |
|
| 63 | ||
| 64 | ||
| 65 |
package com.goldencode.p2j.ui; |
|
| 66 | ||
| 67 |
import java.util.*; |
|
| 68 | ||
| 69 |
/** |
|
| 70 |
* Provides access to the tracked fields of a single {@link WidgetConfig} class, using the field IDs
|
|
| 71 |
* defined by the associated {@link WidgetConfigDef}.
|
|
| 72 |
* <p> |
|
| 73 |
* Implementations are generated by {@link ConfigAccessorGenerator}, one class for each widget
|
|
| 74 |
* configuration class, so that each field access compiles to a direct field instruction. The |
|
| 75 |
* accessors of the built-in configuration classes are generated at build time; see |
|
| 76 |
* {@link ConfigAccessorFactory}.
|
|
| 77 |
* The bulk operations run their loop inside the generated class, so that the per-field calls are |
|
| 78 |
* bound to a single receiver type. |
|
| 79 |
* <p> |
|
| 80 |
* The public static helpers are invoked by the generated code and reproduce the conversions |
|
| 81 |
* applied by a {@link java.lang.invoke.VarHandle} invoked with {@code Object} values, and the
|
|
| 82 |
* equality applied by {@link Objects#equals(Object, Object)} on boxed values.
|
|
| 83 |
*/ |
|
| 84 |
public abstract class ConfigAccessor |
|
| 85 |
{
|
|
| 86 |
/** |
|
| 87 |
* Constructor for the sub-classes. |
|
| 88 |
*/ |
|
| 89 |
protected ConfigAccessor() |
|
| 90 |
{
|
|
| 91 |
} |
|
| 92 | ||
| 93 |
/** |
|
| 94 |
* Unbox a value assigned to a {@code boolean} field.
|
|
| 95 |
* |
|
| 96 |
* @param v |
|
| 97 |
* The value. |
|
| 98 |
* |
|
| 99 |
* @return See above. |
|
| 100 |
* |
|
| 101 |
* @throws NullPointerException |
|
| 102 |
* If the value is {@code null}.
|
|
| 103 |
* @throws ClassCastException |
|
| 104 |
* If the value can not be converted. |
|
| 105 |
*/ |
|
| 106 |
public static boolean unboxBoolean(Object v) |
|
| 107 |
{
|
|
| 108 |
if (v instanceof Boolean) |
|
| 109 |
{
|
|
| 110 |
return ((Boolean) v).booleanValue(); |
|
| 111 |
} |
|
| 112 | ||
| 113 |
throw wrongType(v, boolean.class); |
|
| 114 |
} |
|
| 115 | ||
| 116 |
/** |
|
| 117 |
* Unbox a value assigned to a {@code byte} field.
|
|
| 118 |
* |
|
| 119 |
* @param v |
|
| 120 |
* The value. |
|
| 121 |
* |
|
| 122 |
* @return See above. |
|
| 123 |
* |
|
| 124 |
* @throws NullPointerException |
|
| 125 |
* If the value is {@code null}.
|
|
| 126 |
* @throws ClassCastException |
|
| 127 |
* If the value can not be converted. |
|
| 128 |
*/ |
|
| 129 |
public static byte unboxByte(Object v) |
|
| 130 |
{
|
|
| 131 |
if (v instanceof Byte) |
|
| 132 |
{
|
|
| 133 |
return ((Byte) v).byteValue(); |
|
| 134 |
} |
|
| 135 | ||
| 136 |
throw wrongType(v, byte.class); |
|
| 137 |
} |
|
| 138 | ||
| 139 |
/** |
|
| 140 |
* Unbox a value assigned to a {@code short} field, allowing primitive widening.
|
|
| 141 |
* |
|
| 142 |
* @param v |
|
| 143 |
* The value. |
|
| 144 |
* |
|
| 145 |
* @return See above. |
|
| 146 |
* |
|
| 147 |
* @throws NullPointerException |
|
| 148 |
* If the value is {@code null}.
|
|
| 149 |
* @throws ClassCastException |
|
| 150 |
* If the value can not be converted. |
|
| 151 |
*/ |
|
| 152 |
public static short unboxShort(Object v) |
|
| 153 |
{
|
|
| 154 |
if (v instanceof Short) |
|
| 155 |
{
|
|
| 156 |
return ((Short) v).shortValue(); |
|
| 157 |
} |
|
| 158 | ||
| 159 |
if (v instanceof Byte) |
|
| 160 |
{
|
|
| 161 |
return ((Byte) v).byteValue(); |
|
| 162 |
} |
|
| 163 | ||
| 164 |
throw wrongType(v, short.class); |
|
| 165 |
} |
|
| 166 | ||
| 167 |
/** |
|
| 168 |
* Unbox a value assigned to a {@code char} field.
|
|
| 169 |
* |
|
| 170 |
* @param v |
|
| 171 |
* The value. |
|
| 172 |
* |
|
| 173 |
* @return See above. |
|
| 174 |
* |
|
| 175 |
* @throws NullPointerException |
|
| 176 |
* If the value is {@code null}.
|
|
| 177 |
* @throws ClassCastException |
|
| 178 |
* If the value can not be converted. |
|
| 179 |
*/ |
|
| 180 |
public static char unboxChar(Object v) |
|
| 181 |
{
|
|
| 182 |
if (v instanceof Character) |
|
| 183 |
{
|
|
| 184 |
return ((Character) v).charValue(); |
|
| 185 |
} |
|
| 186 | ||
| 187 |
throw wrongType(v, char.class); |
|
| 188 |
} |
|
| 189 | ||
| 190 |
/** |
|
| 191 |
* Unbox a value assigned to an {@code int} field, allowing primitive widening.
|
|
| 192 |
* |
|
| 193 |
* @param v |
|
| 194 |
* The value. |
|
| 195 |
* |
|
| 196 |
* @return See above. |
|
| 197 |
* |
|
| 198 |
* @throws NullPointerException |
|
| 199 |
* If the value is {@code null}.
|
|
| 200 |
* @throws ClassCastException |
|
| 201 |
* If the value can not be converted. |
|
| 202 |
*/ |
|
| 203 |
public static int unboxInt(Object v) |
|
| 204 |
{
|
|
| 205 |
if (v instanceof Integer) |
|
| 206 |
{
|
|
| 207 |
return ((Integer) v).intValue(); |
|
| 208 |
} |
|
| 209 | ||
| 210 |
if (v instanceof Short || v instanceof Byte) |
|
| 211 |
{
|
|
| 212 |
return ((Number) v).intValue(); |
|
| 213 |
} |
|
| 214 | ||
| 215 |
if (v instanceof Character) |
|
| 216 |
{
|
|
| 217 |
return ((Character) v).charValue(); |
|
| 218 |
} |
|
| 219 | ||
| 220 |
throw wrongType(v, int.class); |
|
| 221 |
} |
|
| 222 | ||
| 223 |
/** |
|
| 224 |
* Unbox a value assigned to a {@code long} field, allowing primitive widening.
|
|
| 225 |
* |
|
| 226 |
* @param v |
|
| 227 |
* The value. |
|
| 228 |
* |
|
| 229 |
* @return See above. |
|
| 230 |
* |
|
| 231 |
* @throws NullPointerException |
|
| 232 |
* If the value is {@code null}.
|
|
| 233 |
* @throws ClassCastException |
|
| 234 |
* If the value can not be converted. |
|
| 235 |
*/ |
|
| 236 |
public static long unboxLong(Object v) |
|
| 237 |
{
|
|
| 238 |
if (v instanceof Long) |
|
| 239 |
{
|
|
| 240 |
return ((Long) v).longValue(); |
|
| 241 |
} |
|
| 242 | ||
| 243 |
if (v instanceof Integer || v instanceof Short || v instanceof Byte) |
|
| 244 |
{
|
|
| 245 |
return ((Number) v).longValue(); |
|
| 246 |
} |
|
| 247 | ||
| 248 |
if (v instanceof Character) |
|
| 249 |
{
|
|
| 250 |
return ((Character) v).charValue(); |
|
| 251 |
} |
|
| 252 | ||
| 253 |
throw wrongType(v, long.class); |
|
| 254 |
} |
|
| 255 | ||
| 256 |
/** |
|
| 257 |
* Unbox a value assigned to a {@code float} field, allowing primitive widening.
|
|
| 258 |
* |
|
| 259 |
* @param v |
|
| 260 |
* The value. |
|
| 261 |
* |
|
| 262 |
* @return See above. |
|
| 263 |
* |
|
| 264 |
* @throws NullPointerException |
|
| 265 |
* If the value is {@code null}.
|
|
| 266 |
* @throws ClassCastException |
|
| 267 |
* If the value can not be converted. |
|
| 268 |
*/ |
|
| 269 |
public static float unboxFloat(Object v) |
|
| 270 |
{
|
|
| 271 |
if (v instanceof Float) |
|
| 272 |
{
|
|
| 273 |
return ((Float) v).floatValue(); |
|
| 274 |
} |
|
| 275 | ||
| 276 |
if (v instanceof Long || v instanceof Integer || v instanceof Short || v instanceof Byte) |
|
| 277 |
{
|
|
| 278 |
return ((Number) v).floatValue(); |
|
| 279 |
} |
|
| 280 | ||
| 281 |
if (v instanceof Character) |
|
| 282 |
{
|
|
| 283 |
return ((Character) v).charValue(); |
|
| 284 |
} |
|
| 285 | ||
| 286 |
throw wrongType(v, float.class); |
|
| 287 |
} |
|
| 288 | ||
| 289 |
/** |
|
| 290 |
* Unbox a value assigned to a {@code double} field, allowing primitive widening.
|
|
| 291 |
* |
|
| 292 |
* @param v |
|
| 293 |
* The value. |
|
| 294 |
* |
|
| 295 |
* @return See above. |
|
| 296 |
* |
|
| 297 |
* @throws NullPointerException |
|
| 298 |
* If the value is {@code null}.
|
|
| 299 |
* @throws ClassCastException |
|
| 300 |
* If the value can not be converted. |
|
| 301 |
*/ |
|
| 302 |
public static double unboxDouble(Object v) |
|
| 303 |
{
|
|
| 304 |
if (v instanceof Double) |
|
| 305 |
{
|
|
| 306 |
return ((Double) v).doubleValue(); |
|
| 307 |
} |
|
| 308 | ||
| 309 |
if (v instanceof Float || |
|
| 310 |
v instanceof Long || |
|
| 311 |
v instanceof Integer || |
|
| 312 |
v instanceof Short || |
|
| 313 |
v instanceof Byte) |
|
| 314 |
{
|
|
| 315 |
return ((Number) v).doubleValue(); |
|
| 316 |
} |
|
| 317 | ||
| 318 |
if (v instanceof Character) |
|
| 319 |
{
|
|
| 320 |
return ((Character) v).charValue(); |
|
| 321 |
} |
|
| 322 | ||
| 323 |
throw wrongType(v, double.class); |
|
| 324 |
} |
|
| 325 | ||
| 326 |
/** |
|
| 327 |
* Compare two {@code int} compatible field values.
|
|
| 328 |
* |
|
| 329 |
* @param a |
|
| 330 |
* The first value. |
|
| 331 |
* @param b |
|
| 332 |
* The second value. |
|
| 333 |
* |
|
| 334 |
* @return {@code true} if the values are equal.
|
|
| 335 |
*/ |
|
| 336 |
public static boolean same(int a, int b) |
|
| 337 |
{
|
|
| 338 |
return a == b; |
|
| 339 |
} |
|
| 340 | ||
| 341 |
/** |
|
| 342 |
* Compare two {@code long} field values.
|
|
| 343 |
* |
|
| 344 |
* @param a |
|
| 345 |
* The first value. |
|
| 346 |
* @param b |
|
| 347 |
* The second value. |
|
| 348 |
* |
|
| 349 |
* @return {@code true} if the values are equal.
|
|
| 350 |
*/ |
|
| 351 |
public static boolean same(long a, long b) |
|
| 352 |
{
|
|
| 353 |
return a == b; |
|
| 354 |
} |
|
| 355 | ||
| 356 |
/** |
|
| 357 |
* Compare two {@code float} field values, as {@link Float#equals(Object)} does.
|
|
| 358 |
* |
|
| 359 |
* @param a |
|
| 360 |
* The first value. |
|
| 361 |
* @param b |
|
| 362 |
* The second value. |
|
| 363 |
* |
|
| 364 |
* @return {@code true} if the values are equal.
|
|
| 365 |
*/ |
|
| 366 |
public static boolean same(float a, float b) |
|
| 367 |
{
|
|
| 368 |
return Float.floatToIntBits(a) == Float.floatToIntBits(b); |
|
| 369 |
} |
|
| 370 | ||
| 371 |
/** |
|
| 372 |
* Compare two {@code double} field values, as {@link Double#equals(Object)} does.
|
|
| 373 |
* |
|
| 374 |
* @param a |
|
| 375 |
* The first value. |
|
| 376 |
* @param b |
|
| 377 |
* The second value. |
|
| 378 |
* |
|
| 379 |
* @return {@code true} if the values are equal.
|
|
| 380 |
*/ |
|
| 381 |
public static boolean same(double a, double b) |
|
| 382 |
{
|
|
| 383 |
return Double.doubleToLongBits(a) == Double.doubleToLongBits(b); |
|
| 384 |
} |
|
| 385 | ||
| 386 |
/** |
|
| 387 |
* Compare two reference field values. |
|
| 388 |
* |
|
| 389 |
* @param a |
|
| 390 |
* The first value. |
|
| 391 |
* @param b |
|
| 392 |
* The second value. |
|
| 393 |
* |
|
| 394 |
* @return {@code true} if the values are equal.
|
|
| 395 |
*/ |
|
| 396 |
public static boolean same(Object a, Object b) |
|
| 397 |
{
|
|
| 398 |
return Objects.equals(a, b); |
|
| 399 |
} |
|
| 400 | ||
| 401 |
/** |
|
| 402 |
* Create the exception thrown for a field ID which is not defined by the config class. |
|
| 403 |
* |
|
| 404 |
* @param fid |
|
| 405 |
* The field ID. |
|
| 406 |
* |
|
| 407 |
* @return See above. |
|
| 408 |
*/ |
|
| 409 |
public static RuntimeException unknownField(int fid) |
|
| 410 |
{
|
|
| 411 |
return new IllegalArgumentException("Unknown widget config field ID " + fid);
|
|
| 412 |
} |
|
| 413 | ||
| 414 |
/** |
|
| 415 |
* Get the fingerprint of the fields this accessor was generated for, as computed by |
|
| 416 |
* {@link ConfigAccessorFactory#fingerprint}.
|
|
| 417 |
* |
|
| 418 |
* @return See above. |
|
| 419 |
*/ |
|
| 420 |
public abstract long fingerprint(); |
|
| 421 | ||
| 422 |
/** |
|
| 423 |
* Get the value of a field. |
|
| 424 |
* |
|
| 425 |
* @param cfg |
|
| 426 |
* The widget config. |
|
| 427 |
* @param fid |
|
| 428 |
* The field ID. |
|
| 429 |
* |
|
| 430 |
* @return The field value, boxed if the field is primitive. |
|
| 431 |
*/ |
|
| 432 |
public abstract Object get(WidgetConfig cfg, int fid); |
|
| 433 | ||
| 434 |
/** |
|
| 435 |
* Set the value of a field. |
|
| 436 |
* |
|
| 437 |
* @param cfg |
|
| 438 |
* The widget config. |
|
| 439 |
* @param fid |
|
| 440 |
* The field ID. |
|
| 441 |
* @param value |
|
| 442 |
* The new value, boxed if the field is primitive. |
|
| 443 |
*/ |
|
| 444 |
public abstract void set(WidgetConfig cfg, int fid, Object value); |
|
| 445 | ||
| 446 |
/** |
|
| 447 |
* Check if a field has the same value in two configs. |
|
| 448 |
* |
|
| 449 |
* @param a |
|
| 450 |
* The first widget config. |
|
| 451 |
* @param b |
|
| 452 |
* The second widget config. |
|
| 453 |
* @param fid |
|
| 454 |
* The field ID. |
|
| 455 |
* |
|
| 456 |
* @return {@code true} if the values are equal.
|
|
| 457 |
*/ |
|
| 458 |
public abstract boolean same(WidgetConfig a, WidgetConfig b, int fid); |
|
| 459 | ||
| 460 |
/** |
|
| 461 |
* Collect the values of the given fields from the active config. A field which is tracked |
|
| 462 |
* for changed value is skipped when its value is the same in the backup config. |
|
| 463 |
* |
|
| 464 |
* @param active |
|
| 465 |
* The active widget config. |
|
| 466 |
* @param backup |
|
| 467 |
* The backup widget config. |
|
| 468 |
* @param fids |
|
| 469 |
* The IDs of the candidate fields. |
|
| 470 |
* @param count |
|
| 471 |
* The number of candidate fields in {@code fids}.
|
|
| 472 |
* @param track |
|
| 473 |
* Flags indicating the fields tracked for changed value, indexed by field ID, or |
|
| 474 |
* {@code null} to collect all candidate fields.
|
|
| 475 |
* @param outFids |
|
| 476 |
* Receives the IDs of the collected fields. |
|
| 477 |
* @param outVals |
|
| 478 |
* Receives the values of the collected fields. |
|
| 479 |
* |
|
| 480 |
* @return The number of collected fields. |
|
| 481 |
*/ |
|
| 482 |
public abstract int collectChanges(WidgetConfig active, |
|
| 483 |
WidgetConfig backup, |
|
| 484 |
int[] fids, |
|
| 485 |
int count, |
|
| 486 |
boolean[] track, |
|
| 487 |
int[] outFids, |
|
| 488 |
Object[] outVals); |
|
| 489 | ||
| 490 |
/** |
|
| 491 |
* Set the values of the given fields. |
|
| 492 |
* |
|
| 493 |
* @param cfg |
|
| 494 |
* The widget config. |
|
| 495 |
* @param fids |
|
| 496 |
* The field IDs. |
|
| 497 |
* @param values |
|
| 498 |
* The new values, boxed for primitive fields. |
|
| 499 |
* @param count |
|
| 500 |
* The number of fields to set. |
|
| 501 |
*/ |
|
| 502 |
public abstract void applyUpdates(WidgetConfig cfg, int[] fids, Object[] values, int count); |
|
| 503 | ||
| 504 |
/** |
|
| 505 |
* Create the exception thrown when a value can not be assigned to a primitive field. |
|
| 506 |
* |
|
| 507 |
* @param v |
|
| 508 |
* The value. |
|
| 509 |
* @param type |
|
| 510 |
* The primitive field type. |
|
| 511 |
* |
|
| 512 |
* @return See above. |
|
| 513 |
* |
|
| 514 |
* @throws NullPointerException |
|
| 515 |
* If the value is {@code null}.
|
|
| 516 |
*/ |
|
| 517 |
private static RuntimeException wrongType(Object v, Class<?> type) |
|
| 518 |
{
|
|
| 519 |
if (v == null) |
|
| 520 |
{
|
|
| 521 |
throw new NullPointerException("Can not assign null to a " + type.getName() + " field");
|
|
| 522 |
} |
|
| 523 | ||
| 524 |
return new ClassCastException("Cannot convert " + v.getClass().getName() + " to " + type.getName());
|
|
| 525 |
} |
|
| 526 |
} |
|
| new/src/com/goldencode/p2j/ui/ConfigAccessorFactory.java 2026-09-25 06:45:17 +0000 | ||
|---|---|---|
| 1 |
/* |
|
| 2 |
** Module : ConfigAccessorFactory.java |
|
| 3 |
** Abstract : Resolution of the prebuilt or runtime generated widget configuration field accessors. |
|
| 4 |
** |
|
| 5 |
** Copyright (c) 2026, Golden Code Development Corporation. |
|
| 6 |
** |
|
| 7 |
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------- |
|
| 8 |
** 001 TG 20260924 Created initial version. |
|
| 9 |
*/ |
|
| 10 | ||
| 11 |
/* |
|
| 12 |
** This program is free software: you can redistribute it and/or modify |
|
| 13 |
** it under the terms of the GNU Affero General Public License as |
|
| 14 |
** published by the Free Software Foundation, either version 3 of the |
|
| 15 |
** License, or (at your option) any later version. |
|
| 16 |
** |
|
| 17 |
** This program is distributed in the hope that it will be useful, |
|
| 18 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 19 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 20 |
** GNU Affero General Public License for more details. |
|
| 21 |
** |
|
| 22 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
| 23 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
| 24 |
** |
|
| 25 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
| 26 |
** |
|
| 27 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
| 28 |
** terms apply to the works covered under the License. These additional terms |
|
| 29 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
| 30 |
** Affero GPL version 3 and may not be removed by you. |
|
| 31 |
** |
|
| 32 |
** 0. Attribution Requirement. |
|
| 33 |
** |
|
| 34 |
** You must preserve all legal notices or author attributions in the covered |
|
| 35 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
| 36 |
** work. You may not remove from the covered work any author or developer |
|
| 37 |
** credit already included within the covered work. |
|
| 38 |
** |
|
| 39 |
** 1. No License To Use Trademarks. |
|
| 40 |
** |
|
| 41 |
** This license does not grant any license or rights to use the trademarks |
|
| 42 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
| 43 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
| 44 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
| 45 |
** publicity purposes without written authorization. |
|
| 46 |
** |
|
| 47 |
** 2. No Misrepresentation of Affiliation. |
|
| 48 |
** |
|
| 49 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
| 50 |
** |
|
| 51 |
** You may not represent yourself for publicity purposes as associated with |
|
| 52 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
| 53 |
** the covered work, without written authorization. |
|
| 54 |
** |
|
| 55 |
** 3. No Misrepresentation of Source or Origin. |
|
| 56 |
** |
|
| 57 |
** You may not represent the covered work as solely your work. All modified |
|
| 58 |
** versions of the covered work must be marked in a reasonable way to make it |
|
| 59 |
** clear that the modified work is not originating from Golden Code Development |
|
| 60 |
** Corporation or FWD. All modified versions must contain the notices of |
|
| 61 |
** attribution required in this license. |
|
| 62 |
*/ |
|
| 63 | ||
| 64 | ||
| 65 |
package com.goldencode.p2j.ui; |
|
| 66 | ||
| 67 |
import java.lang.reflect.*; |
|
| 68 |
import java.nio.charset.*; |
|
| 69 | ||
| 70 |
import com.goldencode.p2j.util.logging.*; |
|
| 71 | ||
| 72 |
/** |
|
| 73 |
* Resolves the {@link ConfigAccessor} for a widget configuration class.
|
|
| 74 |
* <p> |
|
| 75 |
* The accessors of the built-in configuration classes are generated at build time by |
|
| 76 |
* {@link ConfigAccessorGenerator} and packaged in the FWD jar, named with the
|
|
| 77 |
* {@link #PREBUILT_PREFIX} prefix followed by the configuration class name. Being regular classes
|
|
| 78 |
* loaded from the jar, they can be included in an AppCDS archive. A prebuilt accessor is used only |
|
| 79 |
* if its {@link ConfigAccessor#fingerprint() fingerprint} matches the fields of the configuration
|
|
| 80 |
* class; otherwise, and for the configuration classes without a prebuilt accessor, the accessor is |
|
| 81 |
* generated at runtime as a hidden class. |
|
| 82 |
* <p> |
|
| 83 |
* This class does not reference the bytecode generation library, so it is loaded only when an |
|
| 84 |
* accessor needs to be generated at runtime. |
|
| 85 |
*/ |
|
| 86 |
final class ConfigAccessorFactory |
|
| 87 |
{
|
|
| 88 |
/** The class name prefix of the prebuilt accessors. */ |
|
| 89 |
static final String PREBUILT_PREFIX = "com.goldencode.p2j.ui.ConfigAccessors$"; |
|
| 90 | ||
| 91 |
/** Logger */ |
|
| 92 |
private static final CentralLogger LOG = CentralLogger.get(ConfigAccessorFactory.class.getSimpleName()); |
|
| 93 | ||
| 94 |
/** The package of the built-in widget configuration classes. */ |
|
| 95 |
private static final String CONFIG_PACKAGE = WidgetConfig.class.getPackageName(); |
|
| 96 | ||
| 97 |
/** |
|
| 98 |
* Private constructor, this class has only static members. |
|
| 99 |
*/ |
|
| 100 |
private ConfigAccessorFactory() |
|
| 101 |
{
|
|
| 102 |
} |
|
| 103 | ||
| 104 |
/** |
|
| 105 |
* Get the accessor for the given widget configuration class. |
|
| 106 |
* |
|
| 107 |
* @param cfgCls |
|
| 108 |
* The widget configuration class. |
|
| 109 |
* @param fields |
|
| 110 |
* The tracked fields, indexed by field ID. |
|
| 111 |
* |
|
| 112 |
* @return See above. |
|
| 113 |
*/ |
|
| 114 |
static ConfigAccessor create(Class<? extends WidgetConfig> cfgCls, Field[] fields) |
|
| 115 |
{
|
|
| 116 |
long fp = fingerprint(fields); |
|
| 117 |
ConfigAccessor acc = loadPrebuilt(cfgCls); |
|
| 118 | ||
| 119 |
if (acc != null) |
|
| 120 |
{
|
|
| 121 |
if (acc.fingerprint() == fp) |
|
| 122 |
{
|
|
| 123 |
return acc; |
|
| 124 |
} |
|
| 125 | ||
| 126 |
LOG.warning("The prebuilt config accessor for " + cfgCls.getName() +
|
|
| 127 |
" does not match the config fields, generating it at runtime."); |
|
| 128 |
} |
|
| 129 | ||
| 130 |
return ConfigAccessorGenerator.define(cfgCls, fields, fp); |
|
| 131 |
} |
|
| 132 | ||
| 133 |
/** |
|
| 134 |
* Get the name of the prebuilt accessor of the given widget configuration class. |
|
| 135 |
* |
|
| 136 |
* @param cfgCls |
|
| 137 |
* The widget configuration class. |
|
| 138 |
* |
|
| 139 |
* @return The class name, or {@code null} if the class is not a built-in configuration class.
|
|
| 140 |
*/ |
|
| 141 |
static String prebuiltName(Class<?> cfgCls) |
|
| 142 |
{
|
|
| 143 |
if (!CONFIG_PACKAGE.equals(cfgCls.getPackageName())) |
|
| 144 |
{
|
|
| 145 |
return null; |
|
| 146 |
} |
|
| 147 | ||
| 148 |
return PREBUILT_PREFIX + cfgCls.getName().substring(CONFIG_PACKAGE.length() + 1); |
|
| 149 |
} |
|
| 150 | ||
| 151 |
/** |
|
| 152 |
* Compute the fingerprint of the tracked fields: a 64-bit FNV-1a hash of the field names and |
|
| 153 |
* type descriptors, in field ID order. |
|
| 154 |
* |
|
| 155 |
* @param fields |
|
| 156 |
* The tracked fields, indexed by field ID. |
|
| 157 |
* |
|
| 158 |
* @return See above. |
|
| 159 |
*/ |
|
| 160 |
static long fingerprint(Field[] fields) |
|
| 161 |
{
|
|
| 162 |
long hash = 0xcbf29ce484222325L; |
|
| 163 |
for (Field f : fields) |
|
| 164 |
{
|
|
| 165 |
String sig = f.getName() + ':' + f.getType().descriptorString() + ';'; |
|
| 166 |
for (byte b : sig.getBytes(StandardCharsets.UTF_8)) |
|
| 167 |
{
|
|
| 168 |
hash ^= (b & 0xff); |
|
| 169 |
hash *= 0x100000001b3L; |
|
| 170 |
} |
|
| 171 |
} |
|
| 172 | ||
| 173 |
return hash; |
|
| 174 |
} |
|
| 175 | ||
| 176 |
/** |
|
| 177 |
* Load and instantiate the prebuilt accessor of the given widget configuration class. |
|
| 178 |
* |
|
| 179 |
* @param cfgCls |
|
| 180 |
* The widget configuration class. |
|
| 181 |
* |
|
| 182 |
* @return The accessor, or {@code null} if there is no prebuilt accessor for the class.
|
|
| 183 |
*/ |
|
| 184 |
private static ConfigAccessor loadPrebuilt(Class<? extends WidgetConfig> cfgCls) |
|
| 185 |
{
|
|
| 186 |
String name = prebuiltName(cfgCls); |
|
| 187 |
if (name == null) |
|
| 188 |
{
|
|
| 189 |
return null; |
|
| 190 |
} |
|
| 191 | ||
| 192 |
Class<?> cls; |
|
| 193 |
try |
|
| 194 |
{
|
|
| 195 |
cls = Class.forName(name, true, ConfigAccessor.class.getClassLoader()); |
|
| 196 |
} |
|
| 197 |
catch (ClassNotFoundException e) |
|
| 198 |
{
|
|
| 199 |
return null; |
|
| 200 |
} |
|
| 201 | ||
| 202 |
try |
|
| 203 |
{
|
|
| 204 |
return (ConfigAccessor) cls.getDeclaredConstructor().newInstance(); |
|
| 205 |
} |
|
| 206 |
catch (ReflectiveOperationException e) |
|
| 207 |
{
|
|
| 208 |
throw new IllegalStateException("Could not instantiate the config accessor " + name, e);
|
|
| 209 |
} |
|
| 210 |
} |
|
| 211 |
} |
|
| new/src/com/goldencode/p2j/ui/ConfigAccessorGenerator.java 2026-09-25 06:45:17 +0000 | ||
|---|---|---|
| 1 |
/* |
|
| 2 |
** Module : ConfigAccessorGenerator.java |
|
| 3 |
** Abstract : Bytecode generation of the widget configuration field accessors. |
|
| 4 |
** |
|
| 5 |
** Copyright (c) 2026, Golden Code Development Corporation. |
|
| 6 |
** |
|
| 7 |
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------- |
|
| 8 |
** 001 TG 20260924 Created initial version. |
|
| 9 |
*/ |
|
| 10 | ||
| 11 |
/* |
|
| 12 |
** This program is free software: you can redistribute it and/or modify |
|
| 13 |
** it under the terms of the GNU Affero General Public License as |
|
| 14 |
** published by the Free Software Foundation, either version 3 of the |
|
| 15 |
** License, or (at your option) any later version. |
|
| 16 |
** |
|
| 17 |
** This program is distributed in the hope that it will be useful, |
|
| 18 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 19 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 20 |
** GNU Affero General Public License for more details. |
|
| 21 |
** |
|
| 22 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
| 23 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
| 24 |
** |
|
| 25 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
| 26 |
** |
|
| 27 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
| 28 |
** terms apply to the works covered under the License. These additional terms |
|
| 29 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
| 30 |
** Affero GPL version 3 and may not be removed by you. |
|
| 31 |
** |
|
| 32 |
** 0. Attribution Requirement. |
|
| 33 |
** |
|
| 34 |
** You must preserve all legal notices or author attributions in the covered |
|
| 35 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
| 36 |
** work. You may not remove from the covered work any author or developer |
|
| 37 |
** credit already included within the covered work. |
|
| 38 |
** |
|
| 39 |
** 1. No License To Use Trademarks. |
|
| 40 |
** |
|
| 41 |
** This license does not grant any license or rights to use the trademarks |
|
| 42 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
| 43 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
| 44 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
| 45 |
** publicity purposes without written authorization. |
|
| 46 |
** |
|
| 47 |
** 2. No Misrepresentation of Affiliation. |
|
| 48 |
** |
|
| 49 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
| 50 |
** |
|
| 51 |
** You may not represent yourself for publicity purposes as associated with |
|
| 52 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
| 53 |
** the covered work, without written authorization. |
|
| 54 |
** |
|
| 55 |
** 3. No Misrepresentation of Source or Origin. |
|
| 56 |
** |
|
| 57 |
** You may not represent the covered work as solely your work. All modified |
|
| 58 |
** versions of the covered work must be marked in a reasonable way to make it |
|
| 59 |
** clear that the modified work is not originating from Golden Code Development |
|
| 60 |
** Corporation or FWD. All modified versions must contain the notices of |
|
| 61 |
** attribution required in this license. |
|
| 62 |
*/ |
|
| 63 | ||
| 64 | ||
| 65 |
package com.goldencode.p2j.ui; |
|
| 66 | ||
| 67 |
import java.io.*; |
|
| 68 |
import java.lang.invoke.*; |
|
| 69 |
import java.lang.reflect.*; |
|
| 70 |
import java.nio.file.*; |
|
| 71 |
import java.util.*; |
|
| 72 | ||
| 73 |
import org.objectweb.asm.*; |
|
| 74 |
import org.objectweb.asm.Type; |
|
| 75 | ||
| 76 |
/** |
|
| 77 |
* Generates the bytecode of the {@link ConfigAccessor} for a widget configuration class.
|
|
| 78 |
* <p> |
|
| 79 |
* Each operation dispatches the field ID with a {@code tableswitch} to a direct {@code getfield} or
|
|
| 80 |
* {@code putfield} instruction, and the bulk operations invoke the per-field operations of the same
|
|
| 81 |
* final class, so the JIT can compile them without any dynamic dispatch. |
|
| 82 |
* <p> |
|
| 83 |
* The accessors of the {@link ConfigManager#CONFIG_CLASSES built-in configuration classes} are
|
|
| 84 |
* generated at build time by {@link #main}, as regular classes packaged in the FWD jar, so they
|
|
| 85 |
* can be included in an AppCDS archive. The accessors of any other configuration class (external |
|
| 86 |
* widgets) are {@link #define defined} at runtime as hidden classes.
|
|
| 87 |
*/ |
|
| 88 |
final class ConfigAccessorGenerator |
|
| 89 |
{
|
|
| 90 |
/** Internal name of the {@link ConfigAccessor} class. */
|
|
| 91 |
private static final String ACCESSOR = Type.getInternalName(ConfigAccessor.class); |
|
| 92 | ||
| 93 |
/** Descriptor of the {@link ConfigAccessor#get} method. */
|
|
| 94 |
private static final String GET_DESC = |
|
| 95 |
"(Lcom/goldencode/p2j/ui/WidgetConfig;I)Ljava/lang/Object;"; |
|
| 96 | ||
| 97 |
/** Descriptor of the {@link ConfigAccessor#set} method. */
|
|
| 98 |
private static final String SET_DESC = |
|
| 99 |
"(Lcom/goldencode/p2j/ui/WidgetConfig;ILjava/lang/Object;)V"; |
|
| 100 | ||
| 101 |
/** Descriptor of the {@link ConfigAccessor#same(WidgetConfig, WidgetConfig, int)} method. */
|
|
| 102 |
private static final String SAME_DESC = |
|
| 103 |
"(Lcom/goldencode/p2j/ui/WidgetConfig;Lcom/goldencode/p2j/ui/WidgetConfig;I)Z"; |
|
| 104 | ||
| 105 |
/** Descriptor of the {@link ConfigAccessor#collectChanges} method. */
|
|
| 106 |
private static final String COLLECT_DESC = |
|
| 107 |
"(Lcom/goldencode/p2j/ui/WidgetConfig;Lcom/goldencode/p2j/ui/WidgetConfig;[II[Z[I[Ljava/lang/Object;)I"; |
|