Skip to content

Commit 0afa666

Browse files
committed
Replace ReferenceMap with custom RefMap implementation for transparent soft/weak reference handling
- Created RefMap.java: Custom Map wrapper that transparently handles SoftReference/WeakReference with automatic dead-entry cleanup - Refactored 10 files to use RefMap with ConcurrentHashMap delegates for thread-safety - Removed all manual SoftReference wrapping/unwrapping patterns - Simplified code: simplified 27+ patterns from manual null-check chains to direct get/put operations - Updated manifests and build files with RefMap dependency
1 parent a5150d9 commit 0afa666

16 files changed

Lines changed: 336 additions & 189 deletions

File tree

core/pom.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,6 @@
101101
<artifactId>commons-codec</artifactId>
102102
<version>1.15</version>
103103
<scope>provided</scope>
104-
</dependency>
105-
<dependency>
106-
<groupId>org.apache.commons</groupId>
107-
<artifactId>commons-collections4</artifactId>
108-
<version>4.5.0</version>
109-
<scope>provided</scope>
110104
</dependency>
111105
<dependency>
112106
<groupId>org.lucee</groupId>

core/src/main/java/META-INF/MANIFEST.MF

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,6 @@ Export-Package: coldfusion,
316316
lucee.transformer.util,
317317
lucee.transformer.cfml.script.java.function
318318
Require-Bundle: org.apache.commons.commons-codec;bundle-version=1.15.0,
319-
org.apache.commons.commons-collections4;bundle-version=4.5.0,
320319
org.apache.commons.commons-fileupload2-core;bundle-version=2.0.0.M5,
321320
org.apache.commons.commons-io;bundle-version=2.21.0,
322321
org.apache.commons.commons-pool2;bundle-version=2.13.1,
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package lucee.commons.collection;
2+
3+
import java.lang.ref.Reference;
4+
import java.lang.ref.SoftReference;
5+
import java.lang.ref.WeakReference;
6+
import java.util.AbstractMap;
7+
import java.util.AbstractSet;
8+
import java.util.HashMap;
9+
import java.util.Iterator;
10+
import java.util.Map;
11+
import java.util.Set;
12+
13+
public class RefMap<K, V> extends AbstractMap<K, V> {
14+
15+
public enum ReferenceType {
16+
SOFT, WEAK
17+
}
18+
19+
private final Map<K, Reference<V>> delegate;
20+
private final ReferenceType type;
21+
22+
/**
23+
* Creates a RefMap with default HashMap delegate
24+
*/
25+
public RefMap(ReferenceType type) {
26+
this(type, 16, 0.75f);
27+
}
28+
29+
/**
30+
* Creates a RefMap with default HashMap delegate and initial capacity
31+
*/
32+
public RefMap(ReferenceType type, int initialCapacity) {
33+
this(type, initialCapacity, 0.75f);
34+
}
35+
36+
/**
37+
* Creates a RefMap with default HashMap delegate
38+
*/
39+
public RefMap(ReferenceType type, int initialCapacity, float loadFactor) {
40+
this(type, new HashMap<K, Reference<V>>(initialCapacity, loadFactor));
41+
}
42+
43+
/**
44+
* Creates a RefMap with a custom delegate Map Useful for thread-safe access: pass ConcurrentHashMap
45+
* or Collections.synchronizedMap()
46+
*/
47+
public RefMap(ReferenceType type, Map<K, Reference<V>> delegateMap) {
48+
this.type = type;
49+
this.delegate = delegateMap;
50+
}
51+
52+
@Override
53+
public V get(Object key) {
54+
Reference<V> ref = delegate.get(key);
55+
if (ref == null) return null;
56+
57+
V value = ref.get();
58+
if (value == null) {
59+
delegate.remove(key);
60+
}
61+
return value;
62+
}
63+
64+
@Override
65+
public V put(K key, V value) {
66+
if (value == null) {
67+
return remove(key);
68+
}
69+
70+
Reference<V> oldRef = delegate.put(key, createReference(value));
71+
return oldRef == null ? null : oldRef.get();
72+
}
73+
74+
@Override
75+
public V remove(Object key) {
76+
Reference<V> ref = delegate.remove(key);
77+
return ref == null ? null : ref.get();
78+
}
79+
80+
@Override
81+
public boolean containsKey(Object key) {
82+
Reference<V> ref = delegate.get(key);
83+
if (ref == null) return false;
84+
85+
V value = ref.get();
86+
if (value == null) {
87+
delegate.remove(key);
88+
return false;
89+
}
90+
return true;
91+
}
92+
93+
@Override
94+
public boolean containsValue(Object value) {
95+
for (Reference<V> ref: delegate.values()) {
96+
V v = ref.get();
97+
if (v == null) continue;
98+
if (v.equals(value)) return true;
99+
}
100+
return false;
101+
}
102+
103+
@Override
104+
public int size() {
105+
cleanupDeadEntries();
106+
return delegate.size();
107+
}
108+
109+
@Override
110+
public boolean isEmpty() {
111+
cleanupDeadEntries();
112+
return delegate.isEmpty();
113+
}
114+
115+
@Override
116+
public void clear() {
117+
delegate.clear();
118+
}
119+
120+
@Override
121+
public Set<Entry<K, V>> entrySet() {
122+
cleanupDeadEntries();
123+
return new AbstractSet<Entry<K, V>>() {
124+
@Override
125+
public Iterator<Entry<K, V>> iterator() {
126+
final Iterator<Entry<K, Reference<V>>> delegateIt = delegate.entrySet().iterator();
127+
return new Iterator<Entry<K, V>>() {
128+
private Entry<K, V> nextEntry;
129+
private Iterator<Entry<K, Reference<V>>> iter = delegateIt;
130+
131+
@Override
132+
public boolean hasNext() {
133+
while (iter.hasNext()) {
134+
Entry<K, Reference<V>> entry = iter.next();
135+
V value = entry.getValue().get();
136+
if (value != null) {
137+
nextEntry = new AbstractMap.SimpleEntry<>(entry.getKey(), value);
138+
return true;
139+
}
140+
else {
141+
delegate.remove(entry.getKey());
142+
}
143+
}
144+
return false;
145+
}
146+
147+
@Override
148+
public Entry<K, V> next() {
149+
return nextEntry;
150+
}
151+
};
152+
}
153+
154+
@Override
155+
public int size() {
156+
return RefMap.this.size();
157+
}
158+
};
159+
}
160+
161+
private void cleanupDeadEntries() {
162+
Iterator<Entry<K, Reference<V>>> it = delegate.entrySet().iterator();
163+
while (it.hasNext()) {
164+
Entry<K, Reference<V>> entry = it.next();
165+
if (entry.getValue().get() == null) {
166+
it.remove();
167+
}
168+
}
169+
}
170+
171+
private Reference<V> createReference(V value) {
172+
switch (type) {
173+
case SOFT:
174+
return new SoftReference<>(value);
175+
case WEAK:
176+
return new WeakReference<>(value);
177+
default:
178+
throw new IllegalStateException("Unknown reference type: " + type);
179+
}
180+
}
181+
182+
@Override
183+
public String toString() {
184+
return "RefMap(" + type + ")[size=" + size() + "]";
185+
}
186+
}

core/src/main/java/lucee/commons/i18n/FormatUtil.java

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
**/
1919
package lucee.commons.i18n;
2020

21-
import java.lang.ref.SoftReference;
2221
import java.text.DateFormat;
2322
import java.text.ParsePosition;
2423
import java.text.SimpleDateFormat;
@@ -40,9 +39,10 @@
4039
import java.util.Locale;
4140
import java.util.Map;
4241
import java.util.TimeZone;
43-
import java.util.concurrent.ConcurrentHashMap;
4442
import java.util.concurrent.CopyOnWriteArrayList;
4543

44+
import lucee.commons.collection.RefMap;
45+
import lucee.commons.collection.RefMap.ReferenceType;
4646
import lucee.commons.date.DateTimeException;
4747
import lucee.commons.date.DateTimeUtil;
4848
import lucee.commons.io.SystemUtil;
@@ -77,7 +77,7 @@ public final class FormatUtil {
7777
DEFAULT_MILLISECOND = DEFAULT_TIME.isSupported(ChronoField.MILLI_OF_SECOND) ? DEFAULT_TIME.get(ChronoField.MILLI_OF_SECOND) : 0;
7878
}
7979

80-
private final static Map<String, SoftReference<List<FormatterWrapper>>> cfmlFormats = new ConcurrentHashMap<>();
80+
private final static Map<String, List<FormatterWrapper>> cfmlFormats = new RefMap<>(ReferenceType.SOFT);
8181
// "EEEE, MMMM d, yyyy, h:mm:ss a 'Coordinated Universal Time'"
8282
private final static Pattern[] strCfmlFormats = new Pattern[] {
8383

@@ -149,18 +149,17 @@ public final class FormatUtil {
149149

150150
};
151151

152-
private static final Map<String, SoftReference<FormatterWrapper>> dateTimeFormatter = new ConcurrentHashMap<>();
152+
private static final Map<String, FormatterWrapper> dateTimeFormatter = new RefMap<>(ReferenceType.SOFT);
153153
public static final boolean debug = false;
154154
private static final char NON_BREAKING_SPACE = '\u202F';
155155

156156
public static List<FormatterWrapper> getAllFormats(Locale locale, TimeZone timeZone, boolean lenient) {
157157
String key = "all:" + locale.toString() + "-" + timeZone.getID() + ":" + lenient;
158-
SoftReference<List<FormatterWrapper>> sr = cfmlFormats.get(key);
159-
List<FormatterWrapper> formatter = null;
160-
if (sr == null || (formatter = sr.get()) == null) {
158+
List<FormatterWrapper> formatter = cfmlFormats.get(key);
159+
if (formatter == null) {
161160
synchronized (SystemUtil.createToken("all", key)) {
162-
sr = cfmlFormats.get(key);
163-
if (sr == null || (formatter = sr.get()) == null) {
161+
formatter = cfmlFormats.get(key);
162+
if (formatter == null) {
164163

165164
formatter = new CopyOnWriteArrayList<>();
166165
for (FormatterWrapper dtf: getCFMLFormats(locale, timeZone, lenient)) {
@@ -176,7 +175,7 @@ public static List<FormatterWrapper> getAllFormats(Locale locale, TimeZone timeZ
176175
formatter.add(dtf);
177176
}
178177

179-
cfmlFormats.put(key, new SoftReference(formatter));
178+
cfmlFormats.put(key, formatter);
180179
}
181180
}
182181
}
@@ -186,19 +185,18 @@ public static List<FormatterWrapper> getAllFormats(Locale locale, TimeZone timeZ
186185
public static List<FormatterWrapper> getCFMLFormats(Locale locale, TimeZone timeZone, boolean lenient) {
187186
String key = "cfml:" + locale.toString() + "-" + timeZone.getID() + ":" + lenient;
188187

189-
SoftReference<List<FormatterWrapper>> sr = cfmlFormats.get(key);
190-
List<FormatterWrapper> formatter = null;
191-
if (sr == null || (formatter = sr.get()) == null) {
188+
List<FormatterWrapper> formatter = cfmlFormats.get(key);
189+
if (formatter == null) {
192190
synchronized (SystemUtil.createToken("cfml", key)) {
193-
sr = cfmlFormats.get(key);
194-
if (sr == null || (formatter = sr.get()) == null) {
191+
formatter = cfmlFormats.get(key);
192+
if (formatter == null) {
195193
ZoneId zone = timeZone.toZoneId();
196194
formatter = new ArrayList<>();
197195
DateTimeFormatterBuilder builder;
198196
for (Pattern p: strCfmlFormats) {
199197
formatter.add(getFormatterWrapper(p.pattern, zone, locale, p.type, lenient));
200198
}
201-
cfmlFormats.put(key, new SoftReference<>(formatter));
199+
cfmlFormats.put(key, formatter);
202200
}
203201
}
204202
}
@@ -208,11 +206,10 @@ public static List<FormatterWrapper> getCFMLFormats(Locale locale, TimeZone time
208206
public static List<FormatterWrapper> getDateTimeFormats(Locale locale, TimeZone tz, boolean lenient) {
209207

210208
String key = "dt-" + locale.toString() + "-" + tz.getID() + "-" + lenient;
211-
SoftReference<List<FormatterWrapper>> tmp = cfmlFormats.get(key);
212-
List<FormatterWrapper> df = tmp == null ? null : tmp.get();
209+
List<FormatterWrapper> df = cfmlFormats.get(key);
213210
if (df == null) {
214211
synchronized (SystemUtil.createToken("dt", key)) {
215-
df = tmp == null ? null : tmp.get();
212+
df = cfmlFormats.get(key);
216213
if (df == null) {
217214
ZoneId zone = tz.toZoneId();
218215
df = new ArrayList<>();
@@ -263,7 +260,7 @@ public static List<FormatterWrapper> getDateTimeFormats(Locale locale, TimeZone
263260

264261
extractLegacyDateTimePatterns(df, locale, tz, lenient);
265262

266-
cfmlFormats.put(key, new SoftReference<List<FormatterWrapper>>(df));
263+
cfmlFormats.put(key, df);
267264
}
268265
}
269266
}
@@ -307,11 +304,10 @@ public static FormatterWrapper fromFormatToFormatter(DateFormat format, short ty
307304

308305
public static List<FormatterWrapper> getDateFormats(Locale locale, TimeZone tz, boolean lenient) {
309306
String key = "d-" + locale.toString() + "-" + tz.getID() + "-" + lenient;
310-
SoftReference<List<FormatterWrapper>> tmp = cfmlFormats.get(key);
311-
List<FormatterWrapper> df = tmp == null ? null : tmp.get();
307+
List<FormatterWrapper> df = cfmlFormats.get(key);
312308
if (df == null) {
313309
synchronized (SystemUtil.createToken("dt", key)) {
314-
df = tmp == null ? null : tmp.get();
310+
df = cfmlFormats.get(key);
315311
if (df == null) {
316312
ZoneId zone = tz.toZoneId();
317313
df = new ArrayList<>();
@@ -323,7 +319,7 @@ public static List<FormatterWrapper> getDateFormats(Locale locale, TimeZone tz,
323319

324320
extractLegacyDatePatterns(df, locale, tz, lenient);
325321

326-
cfmlFormats.put(key, new SoftReference<List<FormatterWrapper>>(df));
322+
cfmlFormats.put(key, df);
327323
}
328324
}
329325
}
@@ -455,11 +451,10 @@ private static boolean check(List<DateFormat> results, String orgPattern, Locale
455451
public static List<FormatterWrapper> getTimeFormats(Locale locale, TimeZone tz, boolean lenient) {
456452

457453
String key = "t-" + locale.toString() + "-" + tz.getID() + "-" + lenient;
458-
SoftReference<List<FormatterWrapper>> tmp = cfmlFormats.get(key);
459-
List<FormatterWrapper> df = tmp == null ? null : tmp.get();
454+
List<FormatterWrapper> df = cfmlFormats.get(key);
460455
if (df == null) {
461456
synchronized (SystemUtil.createToken("dt", key)) {
462-
df = tmp == null ? null : tmp.get();
457+
df = cfmlFormats.get(key);
463458
if (df == null) {
464459
ZoneId zone = tz.toZoneId();
465460
df = new ArrayList<>();
@@ -470,7 +465,7 @@ public static List<FormatterWrapper> getTimeFormats(Locale locale, TimeZone tz,
470465

471466
extractLegacyTimePatterns(df, locale, tz, lenient);
472467

473-
cfmlFormats.put(key, new SoftReference<List<FormatterWrapper>>(df));
468+
cfmlFormats.put(key, df);
474469
}
475470
}
476471
}
@@ -502,12 +497,10 @@ public static FormatterWrapper getDateTimeFormatter(Locale locale, String mask,
502497

503498
public static FormatterWrapper getDateTimeFormatter(Locale locale, String mask, ZoneId zone) {
504499
String key = locale + ":" + mask;
505-
SoftReference<FormatterWrapper> ref = dateTimeFormatter.get(key);
506-
FormatterWrapper fw = ref == null ? null : ref.get();
500+
FormatterWrapper fw = dateTimeFormatter.get(key);
507501
if (fw == null) {
508502
synchronized (SystemUtil.createToken("getDateTimeFormatter", key)) {
509-
ref = dateTimeFormatter.get(key);
510-
fw = ref == null ? null : ref.get();
503+
fw = dateTimeFormatter.get(key);
511504
if (fw == null) {
512505
// TODO cache
513506
DateTimeFormatter formatter;
@@ -525,7 +518,7 @@ else if (mask.equalsIgnoreCase("isoms") || mask.equalsIgnoreCase("isoMillis") ||
525518
if (locale != null) formatter = formatter.withLocale(locale);
526519

527520
fw = new FormatterWrapper(formatter, mask, FORMAT_TYPE_DATE_TIME, zone);
528-
dateTimeFormatter.put(key, new SoftReference<FormatterWrapper>(fw));
521+
dateTimeFormatter.put(key, fw);
529522
}
530523
}
531524
}

0 commit comments

Comments
 (0)