Skip to content

Commit 85befc5

Browse files
author
Guido Medina
committed
Post migration to JDK 8: Functions, lambdas and use of computeIfAbsent to behave like a true cache
1 parent e28e87d commit 85befc5

File tree

22 files changed

+221
-249
lines changed

22 files changed

+221
-249
lines changed

quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ private void writePackageDocumentation(String outputDirectory, String descriptio
241241
}
242242

243243
private List<String> getNames(Element element, String path) {
244-
return getNames(element, path, new ArrayList<String>());
244+
return getNames(element, path, new ArrayList<>());
245245
}
246246

247247
private List<String> getNames(Element element, String path, List<String> names) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*******************************************************************************
2+
* Copyright (c) quickfixengine.org All rights reserved.
3+
*
4+
* This file is part of the QuickFIX FIX Engine
5+
*
6+
* This file may be distributed under the terms of the quickfixengine.org
7+
* license as defined by quickfixengine.org and appearing in the file
8+
* LICENSE included in the packaging of this file.
9+
*
10+
* This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
11+
* THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
12+
* PARTICULAR PURPOSE.
13+
*
14+
* See http://www.quickfixengine.org/LICENSE for licensing information.
15+
*
16+
* Contact ask@quickfixengine.org if any conditions of this licensing
17+
* are not clear to you.
18+
******************************************************************************/
19+
20+
package org.quickfixj;
21+
22+
import java.util.concurrent.ConcurrentHashMap;
23+
import java.util.function.Function;
24+
25+
public class SimpleCache<K, V> extends ConcurrentHashMap<K, V> {
26+
static final int CPUs = Runtime.getRuntime().availableProcessors();
27+
28+
final Function<K, V> loadingFunction;
29+
30+
public SimpleCache(Function<K, V> loadingFunction) {
31+
super(CPUs, 0.7f, CPUs);
32+
this.loadingFunction = loadingFunction;
33+
}
34+
35+
public V computeIfAbsent(K key) {
36+
/*
37+
* We could computeIfAbsent directly but for CPUs < 32 pre-scanning is faster.
38+
*/
39+
final V value = get(key);
40+
return value != null ? value : computeIfAbsent(key, loadingFunction);
41+
}
42+
}

quickfixj-core/src/main/java/org/quickfixj/jmx/mbean/connector/ConnectorAdmin.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@
3737
import javax.management.ObjectName;
3838
import javax.management.openmbean.OpenDataException;
3939
import javax.management.openmbean.TabularData;
40-
import java.beans.PropertyChangeEvent;
41-
import java.beans.PropertyChangeListener;
4240
import java.io.IOException;
4341
import java.net.InetAddress;
4442
import java.net.UnknownHostException;
@@ -167,11 +165,9 @@ public ObjectName preRegister(MBeanServer server, ObjectName name) throws Except
167165

168166
public void postRegister(Boolean registrationDone) {
169167
if (connector instanceof SessionConnector) {
170-
((SessionConnector) connector).addPropertyChangeListener(new PropertyChangeListener() {
171-
public void propertyChange(PropertyChangeEvent evt) {
172-
if (SessionConnector.SESSIONS_PROPERTY.equals(evt.getPropertyName())) {
173-
registerSessions();
174-
}
168+
((SessionConnector) connector).addPropertyChangeListener(evt -> {
169+
if (SessionConnector.SESSIONS_PROPERTY.equals(evt.getPropertyName())) {
170+
registerSessions();
175171
}
176172
});
177173
}

quickfixj-core/src/main/java/quickfix/DefaultDataDictionaryProvider.java

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,54 +20,51 @@
2020
package quickfix;
2121

2222
import org.quickfixj.QFJException;
23+
import org.quickfixj.SimpleCache;
2324
import quickfix.field.ApplVerID;
2425

25-
import java.util.Map;
26-
import java.util.concurrent.ConcurrentHashMap;
27-
2826
import static quickfix.MessageUtils.toBeginString;
2927

3028
public class DefaultDataDictionaryProvider implements DataDictionaryProvider {
31-
private final Map<String, DataDictionary> transportDictionaries = new ConcurrentHashMap<>();
32-
private final Map<AppVersionKey, DataDictionary> applicationDictionaries = new ConcurrentHashMap<>();
33-
private final boolean findDataDictionaries;
29+
private final SimpleCache<String, DataDictionary> transportDictionaries;
30+
private final SimpleCache<AppVersionKey, DataDictionary> applicationDictionaries;
3431

3532
public DefaultDataDictionaryProvider() {
36-
findDataDictionaries = true;
33+
this(true);
3734
}
3835

3936
public DefaultDataDictionaryProvider(boolean findDataDictionaries) {
40-
this.findDataDictionaries = findDataDictionaries;
37+
transportDictionaries = new SimpleCache<>(beginString -> {
38+
if (findDataDictionaries) {
39+
final String path = beginString.replace(".", "") + ".xml";
40+
try {
41+
return new DataDictionary(path);
42+
} catch (ConfigError e) {
43+
throw new QFJException(e);
44+
}
45+
}
46+
return null;
47+
});
48+
applicationDictionaries = new SimpleCache<>(appVersionKey -> {
49+
if (findDataDictionaries) {
50+
final String beginString = toBeginString(appVersionKey.applVerID);
51+
final String path = beginString.replace(".", "") + ".xml";
52+
try {
53+
return new DataDictionary(path);
54+
} catch (ConfigError e) {
55+
throw new QFJException(e);
56+
}
57+
}
58+
return null;
59+
});
4160
}
4261

43-
public synchronized DataDictionary getSessionDataDictionary(String beginString) {
44-
DataDictionary dd = transportDictionaries.get(beginString);
45-
if (dd == null && findDataDictionaries) {
46-
String path = beginString.replace(".", "") + ".xml";
47-
try {
48-
dd = new DataDictionary(path);
49-
transportDictionaries.put(beginString, dd);
50-
} catch (ConfigError e) {
51-
throw new QFJException(e);
52-
}
53-
}
54-
return dd;
62+
public DataDictionary getSessionDataDictionary(String beginString) {
63+
return transportDictionaries.computeIfAbsent(beginString);
5564
}
5665

5766
public DataDictionary getApplicationDataDictionary(ApplVerID applVerID) {
58-
AppVersionKey appVersionKey = new AppVersionKey(applVerID);
59-
DataDictionary dd = applicationDictionaries.get(appVersionKey);
60-
if (dd == null && findDataDictionaries) {
61-
String beginString = toBeginString(applVerID);
62-
String path = beginString.replace(".", "") + ".xml";
63-
try {
64-
dd = new DataDictionary(path);
65-
applicationDictionaries.put(appVersionKey, dd);
66-
} catch (ConfigError e) {
67-
throw new QFJException(e);
68-
}
69-
}
70-
return dd;
67+
return applicationDictionaries.computeIfAbsent(new AppVersionKey(applVerID));
7168
}
7269

7370
public void addTransportDictionary(String beginString, DataDictionary dd) {

quickfixj-core/src/main/java/quickfix/DefaultSessionFactory.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,29 @@
1919

2020
package quickfix;
2121

22+
import org.quickfixj.QFJException;
23+
import org.quickfixj.SimpleCache;
2224
import quickfix.field.ApplVerID;
2325
import quickfix.field.DefaultApplVerID;
2426

2527
import java.net.InetAddress;
2628
import java.util.Enumeration;
27-
import java.util.Map;
2829
import java.util.Properties;
2930
import java.util.Set;
30-
import java.util.concurrent.ConcurrentHashMap;
3131

3232
/**
3333
* Factory for creating sessions. Used by the communications code (acceptors,
3434
* initiators) for creating sessions.
3535
*/
3636
public class DefaultSessionFactory implements SessionFactory {
37-
private static final Map<String, DataDictionary> dictionaryCache = new ConcurrentHashMap<>();
37+
private static final SimpleCache<String, DataDictionary> dictionaryCache = new SimpleCache<>(path -> {
38+
try {
39+
return new DataDictionary(path);
40+
} catch (ConfigError e) {
41+
throw new QFJException(e.getMessage(), e);
42+
}
43+
});
44+
3845
private final Application application;
3946
private final MessageStoreFactory messageStoreFactory;
4047
private final LogFactory logFactory;
@@ -320,14 +327,7 @@ private String toDictionaryPath(String beginString) {
320327
}
321328

322329
private DataDictionary getDataDictionary(String path) throws ConfigError {
323-
synchronized (dictionaryCache) {
324-
DataDictionary dataDictionary = dictionaryCache.get(path);
325-
if (dataDictionary == null) {
326-
dataDictionary = new DataDictionary(path);
327-
dictionaryCache.put(path, dataDictionary);
328-
}
329-
return dataDictionary;
330-
}
330+
return dictionaryCache.computeIfAbsent(path);
331331
}
332332

333333
private int[] getLogonIntervalsInSeconds(SessionSettings settings, SessionID sessionID) throws ConfigError {

quickfixj-core/src/main/java/quickfix/FileStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public class FileStore implements MessageStore, Closeable {
7676
this.syncWrites = syncWrites;
7777
this.maxCachedMsgs = maxCachedMsgs;
7878

79-
messageIndex = maxCachedMsgs > 0 ? new TreeMap<Long, long[]>() : null;
79+
messageIndex = maxCachedMsgs > 0 ? new TreeMap<>() : null;
8080

8181
final String fullPath = new File(path == null ? "." : path).getAbsolutePath();
8282
final String sessionName = FileUtil.sessionIdFileName(sessionID);

quickfixj-core/src/main/java/quickfix/JdbcUtil.java

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
import java.sql.SQLException;
3232
import java.util.Map;
3333
import java.util.concurrent.ConcurrentHashMap;
34+
import java.util.concurrent.atomic.AtomicInteger;
35+
import java.util.function.Function;
3436

3537
class JdbcUtil {
3638

3739
static final String CONNECTION_POOL_ALIAS = "quickfixj";
3840

3941
private static final Map<String, ProxoolDataSource> dataSources = new ConcurrentHashMap<>();
40-
private static int dataSourceCounter = 1;
42+
private static AtomicInteger dataSourceCounter = new AtomicInteger();
4143

4244
static DataSource getDataSource(SessionSettings settings, SessionID sessionID)
4345
throws ConfigError, FieldConvertError {
@@ -81,35 +83,35 @@ static DataSource getDataSource(String jdbcDriver, String connectionURL, String
8183
}
8284

8385
/**
84-
* This is typically called from a single thread, but just in case we are synchronizing modification
85-
* of the cache. The cache itself is thread safe.
86+
* This is typically called from a single thread, but just in case we are using an atomic loading function
87+
* to avoid the creation of two data sources simultaneously. The cache itself is thread safe.
8688
*/
87-
static synchronized DataSource getDataSource(String jdbcDriver, String connectionURL, String user, String password,
89+
static DataSource getDataSource(String jdbcDriver, String connectionURL, String user, String password,
8890
boolean cache, int maxConnCount, int simultaneousBuildThrottle,
8991
long maxActiveTime, int maxConnLifetime) {
9092
String key = jdbcDriver + "#" + connectionURL + "#" + user + "#" + password;
9193
ProxoolDataSource ds = cache ? dataSources.get(key) : null;
9294

9395
if (ds == null) {
94-
ds = new ProxoolDataSource(JdbcUtil.CONNECTION_POOL_ALIAS + "-" + dataSourceCounter++);
95-
96-
ds.setDriver(jdbcDriver);
97-
ds.setDriverUrl(connectionURL);
98-
99-
// Bug in Proxool 0.9RC2. Must set both delegate properties and individual setters. :-(
100-
ds.setDelegateProperties("user=" + user + ","
101-
+ (password != null && !"".equals(password) ? "password=" + password : ""));
102-
ds.setUser(user);
103-
ds.setPassword(password);
104-
105-
ds.setMaximumActiveTime(maxActiveTime);
106-
ds.setMaximumConnectionLifetime(maxConnLifetime);
107-
ds.setMaximumConnectionCount(maxConnCount);
108-
ds.setSimultaneousBuildThrottle(simultaneousBuildThrottle);
109-
110-
if (cache) {
111-
dataSources.put(key, ds);
112-
}
96+
final Function<String, ProxoolDataSource> loadingFunction = dataSourceKey -> {
97+
final ProxoolDataSource dataSource = new ProxoolDataSource(CONNECTION_POOL_ALIAS + "-" + dataSourceCounter.incrementAndGet());
98+
99+
dataSource.setDriver(jdbcDriver);
100+
dataSource.setDriverUrl(connectionURL);
101+
102+
// Bug in Proxool 0.9RC2. Must set both delegate properties and individual setters. :-(
103+
dataSource.setDelegateProperties("user=" + user + ","
104+
+ (password != null && !"".equals(password) ? "password=" + password : ""));
105+
dataSource.setUser(user);
106+
dataSource.setPassword(password);
107+
108+
dataSource.setMaximumActiveTime(maxActiveTime);
109+
dataSource.setMaximumConnectionLifetime(maxConnLifetime);
110+
dataSource.setMaximumConnectionCount(maxConnCount);
111+
dataSource.setSimultaneousBuildThrottle(simultaneousBuildThrottle);
112+
return dataSource;
113+
};
114+
ds = cache ? dataSources.computeIfAbsent(key, loadingFunction) : loadingFunction.apply(key);
113115
}
114116
return ds;
115117
}

quickfixj-core/src/main/java/quickfix/SystemTime.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,7 @@
2929
public class SystemTime {
3030
public static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
3131

32-
private static final SystemTimeSource DEFAULT_TIME_SOURCE = new SystemTimeSource() {
33-
public long getTime() {
34-
return System.currentTimeMillis();
35-
}
36-
};
32+
private static final SystemTimeSource DEFAULT_TIME_SOURCE = System::currentTimeMillis;
3733

3834
private static volatile SystemTimeSource systemTimeSource = DEFAULT_TIME_SOURCE;
3935

quickfixj-core/src/main/java/quickfix/field/converter/UtcTimestampConverter.java

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,33 @@
1919

2020
package quickfix.field.converter;
2121

22+
import org.quickfixj.SimpleCache;
2223
import quickfix.FieldConvertError;
2324
import quickfix.SystemTime;
2425

2526
import java.text.DateFormat;
2627
import java.util.Calendar;
2728
import java.util.Date;
2829
import java.util.GregorianCalendar;
29-
import java.util.concurrent.ConcurrentHashMap;
3030

3131
/**
3232
* Convert between a timestamp and a String. A timestamp includes both a date
3333
* and a time.
3434
*/
3535
public class UtcTimestampConverter extends AbstractDateTimeConverter {
3636
private static final ThreadLocal<UtcTimestampConverter> utcTimestampConverter = new ThreadLocal<>();
37+
private final static SimpleCache<String, Long> dateCache = new SimpleCache<>(dateString -> {
38+
final Calendar c = new GregorianCalendar(1970, 0, 1, 0, 0, 0);
39+
c.setTimeZone(SystemTime.UTC_TIMEZONE);
40+
final int year = Integer.parseInt(dateString.substring(0, 4));
41+
final int month = Integer.parseInt(dateString.substring(4, 6));
42+
final int day = Integer.parseInt(dateString.substring(6, 8));
43+
c.set(year, month - 1, day);
44+
return c.getTimeInMillis();
45+
});
46+
3747
private final DateFormat utcTimestampFormat = createDateFormat("yyyyMMdd-HH:mm:ss");
3848
private final DateFormat utcTimestampFormatMillis = createDateFormat("yyyyMMdd-HH:mm:ss.SSS");
39-
private final static ConcurrentHashMap<String, Long> dateCache = new ConcurrentHashMap<>();
4049

4150
/**
4251
* Convert a timestamp (represented as a Date) to a String.
@@ -83,19 +92,7 @@ public static Date convert(String value) throws FieldConvertError {
8392
}
8493

8594
private static Long getMillisForDay(String value) {
86-
String dateString = value.substring(0, 8);
87-
Long millis = dateCache.get(dateString);
88-
if (millis == null) {
89-
Calendar c = new GregorianCalendar(1970, 0, 1, 0, 0, 0);
90-
c.setTimeZone(SystemTime.UTC_TIMEZONE);
91-
int year = Integer.parseInt(value.substring(0, 4));
92-
int month = Integer.parseInt(value.substring(4, 6));
93-
int day = Integer.parseInt(value.substring(6, 8));
94-
c.set(year, month - 1, day);
95-
millis = c.getTimeInMillis();
96-
dateCache.put(dateString, c.getTimeInMillis());
97-
}
98-
return millis;
95+
return dateCache.computeIfAbsent(value.substring(0, 8));
9996
}
10097

10198
private static void verifyFormat(String value) throws FieldConvertError {

quickfixj-core/src/main/java/quickfix/mina/SingleThreadedEventHandlingStrategy.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,10 @@ public void blockInThread() {
121121
}
122122

123123
startHandlingMessages();
124-
messageProcessingThread = new Thread(new Runnable() {
125-
@Override
126-
public void run() {
127-
sessionConnector.log.info("Started " + MESSAGE_PROCESSOR_THREAD_NAME);
128-
block();
129-
sessionConnector.log.info("Stopped " + MESSAGE_PROCESSOR_THREAD_NAME);
130-
}
124+
messageProcessingThread = new Thread(() -> {
125+
sessionConnector.log.info("Started " + MESSAGE_PROCESSOR_THREAD_NAME);
126+
block();
127+
sessionConnector.log.info("Stopped " + MESSAGE_PROCESSOR_THREAD_NAME);
131128
}, MESSAGE_PROCESSOR_THREAD_NAME);
132129
messageProcessingThread.setDaemon(true);
133130
messageProcessingThread.start();

0 commit comments

Comments
 (0)