getting on the needle

This commit is contained in:
rusefi 2020-07-24 11:30:43 -04:00
parent 64ec3d2f4d
commit ed0decea00
19 changed files with 5054 additions and 0 deletions

View File

@ -1,3 +1,11 @@
plugins {
id 'java'
}
dependencies {
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'
//implementation group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2'
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
}

View File

@ -0,0 +1,150 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.logging;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
/**
* Logging implementation that uses {@link java.util.logging} logging facilities.
*/
class DefaultLogging {
Map<String, Exception> configure() {
// Heuristically check if there was an attempt to manually configure logging
if (getProperty("java.util.logging.config.class", null) != null ||
getProperty("java.util.logging.config.file", null) != null ||
!hasDefaultHandlers(Logger.getLogger("")))
{
return Collections.emptyMap(); // logging was already manually configured
}
return configureLogFile(getProperty(Logging.LOG_FILE_PROPERTY, null));
}
private boolean hasDefaultHandlers(Logger root) {
// Default configuration is 1 ConsoleHandler with SimpleFormatter and INFO level
Handler[] handlers = root.getHandlers();
if (handlers.length != 1)
return false;
Handler handler = handlers[0];
return handler.getClass() == ConsoleHandler.class &&
handler.getFormatter().getClass() == SimpleFormatter.class &&
handler.getLevel() == Level.INFO;
}
Map<String, Exception> configureLogFile(String log_file) {
Logger root = Logger.getLogger("");
Map<String, Exception> errors = new LinkedHashMap<String, Exception>();
try {
// Don't reset configuration. Retain all manually configured loggers, but
// reconfigure the root logger, which (as we checked) has a default configuration with
// 1 ConsoleHandler with SimpleFormatter and INFO level
for (Handler handler : root.getHandlers())
root.removeHandler(handler);
// configure "log" file or console
Handler handler = null;
if (log_file != null) {
try {
handler = new FileHandler(log_file, getLimit(Logging.LOG_MAX_FILE_SIZE_PROPERTY, errors), 2, true);
} catch (IOException e) {
errors.put(log_file, e);
}
}
if (handler == null)
handler = new ConsoleHandler();
handler.setFormatter(new LogFormatter());
handler.setLevel(Level.ALL);
root.addHandler(handler);
// configure "err" file
String err_file = getProperty(Logging.ERR_FILE_PROPERTY, null);
if (err_file != null) {
try {
handler = new FileHandler(err_file, getLimit(Logging.ERR_MAX_FILE_SIZE_PROPERTY, errors), 2, true);
handler.setFormatter(new LogFormatter());
handler.setLevel(Level.WARNING);
root.addHandler(handler);
} catch (IOException e) {
errors.put(err_file, e);
}
}
} catch (SecurityException e) {
// ignore -- does not have persmission to change configuration
}
return errors;
}
Object getPeer(String name) {
return Logger.getLogger(name);
}
String getName(Object peer) {
return ((Logger)peer).getName();
}
boolean debugEnabled(Object peer) {
return ((Logger)peer).isLoggable(Level.FINE);
}
void setDebugEnabled(Object peer, boolean debug_enabled) {
((Logger)peer).setLevel(debug_enabled ? Level.ALL : Level.INFO);
}
void log(Object peer, Level level, String msg, Throwable t) {
((Logger)peer).log(level, msg, t);
}
// ========== Utility methods ==========
/**
* Safely, from security point of view, gets system property.
*/
static String getProperty(String key, String def) {
// For applets we need to be ready for security exception in getProperty() call
try {
return System.getProperty(key, def);
} catch (SecurityException e) {
return def;
}
}
static int getLimit(String key, Map<String, Exception> errors) {
String value = getProperty(key, Logging.DEFAULT_MAX_FILE_SIZE).trim();
int multiplier = 1;
if (value.endsWith("K") || value.endsWith("k")) {
multiplier = 1024;
value = value.substring(0, value.length() - 1);
} else if (value.endsWith("M") || value.endsWith("m")) {
multiplier = 1024 * 1024;
value = value.substring(0, value.length() - 1);
} else if (value.endsWith("G") || value.endsWith("g")) {
multiplier = 1024 * 1024 * 1024;
value = value.substring(0, value.length() - 1);
}
try {
return Integer.valueOf(value) * multiplier;
} catch (NumberFormatException e) {
errors.put(key, e);
return 0;
}
}
}

View File

@ -0,0 +1,114 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.logging;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.layout.ByteBufferDestination;
import org.apache.logging.log4j.core.layout.Encoder;
import org.apache.logging.log4j.core.pattern.MessagePatternConverter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* Custom pattern layout for log4j2. Message formatting is delegated to {@link LogFormatter}.
*/
@SuppressWarnings("unused") //used by Log4j2
@Plugin(name = "dxFeedPatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public class DxFeedPatternLayout extends AbstractStringLayout {
private static final String LINE_SEP = DefaultLogging.getProperty("line.separator", "\n");
private final BiConsumer<Object, StringBuilder> msgConsumer;
private final LogFormatter logFormatter;
private DxFeedPatternLayout(Configuration configuration) {
super(configuration, Charset.defaultCharset(), null, null);
MessagePatternConverter messagePatternConverter = MessagePatternConverter.newInstance(configuration, null);
msgConsumer = (o, sb) -> {
// Format message
messagePatternConverter.format(o, sb);
if (o instanceof LogEvent) {
// Format exception
Throwable throwable = ((LogEvent)o).getThrown();
if (throwable != null) {
sb.append(LINE_SEP);
StringWriter w = new StringWriter();
throwable.printStackTrace(new PrintWriter(w));
sb.append(w.getBuffer());
// Remove extra line separator
sb.setLength(sb.length() - LINE_SEP.length());
}
}
};
logFormatter = new LogFormatter();
}
@Override
public String toSerializable(LogEvent event) {
StringBuilder text = getStringBuilder();
String s = format(event, text).toString();
trimToMaxSize(text);
return s;
}
@Override
public void encode(LogEvent event, ByteBufferDestination destination) {
StringBuilder text = getStringBuilder();
format(event, text);
Encoder<StringBuilder> encoder = getStringBuilderEncoder();
encoder.encode(text, destination);
trimToMaxSize(text);
}
private StringBuilder format(LogEvent event, StringBuilder text) {
char level = event.getLevel().name().charAt(0);
logFormatter.format(level, event.getTimeMillis(), event.getThreadName(), event.getLoggerName(), msgConsumer,
event, text);
return text;
}
@Override
public Map<String, String> getContentFormat() {
return Collections.emptyMap();
}
@Override
public String toString() {
return getContentFormat().toString();
}
public static DxFeedPatternLayout createDefaultLayout() {
return createDefaultLayout(null);
}
/**
* Creates a DxFeedPatternLayout using the default options and the given configuration. Options include using UTF-8.
*/
@PluginFactory
public static DxFeedPatternLayout createDefaultLayout(@PluginConfiguration Configuration configuration) {
if (configuration == null)
configuration = new DefaultConfiguration();
return new DxFeedPatternLayout(configuration);
}
}

View File

@ -0,0 +1,187 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.logging;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.NullConfiguration;
import org.apache.logging.log4j.core.filter.ThresholdFilter;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.status.StatusLogger;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import static org.apache.logging.log4j.Level.DEBUG;
import static org.apache.logging.log4j.Level.ERROR;
import static org.apache.logging.log4j.Level.INFO;
import static org.apache.logging.log4j.Level.OFF;
import static org.apache.logging.log4j.Level.WARN;
import static org.apache.logging.log4j.core.Filter.Result.ACCEPT;
import static org.apache.logging.log4j.core.Filter.Result.DENY;
import static org.apache.logging.log4j.core.config.ConfigurationSource.NULL_SOURCE;
/**
* Logging implementation that uses log4j2 logging facilities.
*/
class Log4j2Logging extends DefaultLogging {
private static final String FQCN = Logging.class.getName() + ".";
static {
StatusLogger.getLogger().setLevel(OFF);
}
@Override
Map<String, Exception> configure() {
LoggerContext ctx = (LoggerContext)LogManager.getContext(false);
if (ctx.getConfiguration().getConfigurationSource() != NULL_SOURCE)
return Collections.emptyMap(); // do nothing since log4j2 was already configured
return configureLogFile(getProperty(Logging.LOG_FILE_PROPERTY, null));
}
private static Map<String, Exception> reconfigure(String logFile) {
LoggerContext ctx = (LoggerContext)LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
Map<String, Exception> errors = new LinkedHashMap<>();
config.getRootLogger().setLevel(DEBUG);
String errFile = getProperty(Logging.ERR_FILE_PROPERTY, null);
for (Map.Entry<String, Appender> entry : config.getRootLogger().getAppenders().entrySet()) {
entry.getValue().stop();
// Safe to delete here since config.getRootLogger().getAppenders() returns new map
config.getRootLogger().removeAppender(entry.getKey());
}
Appender appender = null;
if (logFile != null) {
try {
appender = createFileAppender("common", logFile, Logging.LOG_MAX_FILE_SIZE_PROPERTY, errors);
} catch (Exception e) {
errors.put(logFile, e);
}
}
if (appender == null)
appender = ConsoleAppender.newBuilder()
.withName("common")
.withLayout(getDetailedLayout())
.setTarget(ConsoleAppender.Target.SYSTEM_OUT)
.build();
config.getRootLogger().addAppender(appender, DEBUG,
errFile == null ? null : ThresholdFilter.createFilter(WARN, DENY, ACCEPT));
if (errFile != null) {
try {
Appender errAppender = createFileAppender("error", errFile, Logging.ERR_MAX_FILE_SIZE_PROPERTY, errors);
config.getRootLogger().addAppender(errAppender, WARN, ThresholdFilter.createFilter(WARN, ACCEPT, DENY));
} catch (Exception e) {
errors.put(errFile, e);
}
}
ctx.updateLoggers();
return errors;
}
private static AbstractStringLayout getDetailedLayout() {
return DxFeedPatternLayout.createDefaultLayout(null);
}
private static RollingFileAppender createFileAppender(String name, String logFile, String maxSizeKey,
Map<String, Exception> errors)
{
RollingFileAppender.Builder builder = RollingFileAppender.newBuilder();
builder.setConfiguration(new NullConfiguration());
builder.withName(name);
builder.withLayout(getDetailedLayout());
builder.withFileName(logFile);
builder.withFilePattern(logFile);
builder.withAppend(true);
builder.withImmediateFlush(true);
int limit = getLimit(maxSizeKey, errors);
if (limit == 0)
limit = 900 * 1024 * 1024; // Default in Logging.DEFAULT_MAX_FILE_SIZE
builder.withPolicy(SizeBasedTriggeringPolicy.createPolicy(Integer.toString(limit)));
return builder.build();
}
@Override
Map<String, Exception> configureLogFile(String logFile) {
return reconfigure(logFile);
}
@Override
Object getPeer(String name) {
return LogManager.getLogger(name);
}
@Override
String getName(Object peer) {
return ((Logger)peer).getName();
}
@Override
boolean debugEnabled(Object peer) {
return ((Logger)peer).isDebugEnabled();
}
@Override
void setDebugEnabled(Object peer, boolean debugEnabled) {
((Logger)peer).setLevel(debugEnabled ? DEBUG : INFO);
}
@Override
void log(Object peer, Level level, String msg, Throwable t) {
org.apache.logging.log4j.Level priority;
if (level.intValue() <= Level.FINE.intValue())
priority = DEBUG;
else if (level.intValue() <= Level.INFO.intValue())
priority = INFO;
else if (level.intValue() <= Level.WARNING.intValue())
priority = WARN;
else
priority = ERROR;
if (!((Logger)peer).isEnabled(priority))
return;
// Before calling log4j logger we must clear "interrupted" flag from current thread.
// If this flag is "true", log4j will log error in 1 appender only (and probably clear the flag).
// We will re-establish "interrupted" flag later.
boolean interrupted = Thread.interrupted();
try {
((Logger)peer).logMessage(FQCN, priority, null, new SimpleMessage(msg == null ? "" : msg), t);
} catch (Exception e) {
System.err.println(new LogFormatter().format('E', System.currentTimeMillis(),
Thread.currentThread().getName(), "Log4j", e + " during logging of " + msg));
if (!(e instanceof IllegalStateException) || e.getMessage() == null ||
!e.getMessage().equals("Current state = FLUSHED, new state = CODING"))
{
e.printStackTrace(System.err);
}
} finally {
if (interrupted)
Thread.currentThread().interrupt();
}
}
}

View File

@ -0,0 +1,185 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.logging;
import com.devexperts.util.TimeUtil;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.function.BiConsumer;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import javax.annotation.concurrent.ThreadSafe;
/**
* Thread-safe formatter for log messages.
* It is used for formatting log4j, log4j2 and {@link java.util.logging} log messages.
* Performs conversion of thread names according to patterns specified in configuration file.
* <p>
* If the system property {@code logformatter.properties} is specified, then it should contain
* an URL to the configuration file. Otherwise, configuration is loaded from classpath, using
* <i>/META-INF/logformatter.properties</i> file.
* <p>
* The format of the file is:
* <ul>
* <li>pattern=replacement
* <li>"Pattern" uses regular expression syntax.
* You can escape "=" in pattern with "\=" syntax.
* <li>"Replacement" string can refer to capturing groups defined in pattern using usual
* regular expression syntax "$n", where "n" stands for the number of the group.
* <li>ISO 8859-1 encoding is used.
* <li>Empty lines and lines starting with # or ! are ignored.
* Lines containing wrong patterns are ignored.
* </ul>
* Configuration file is loaded during class loading.
* Any errors which occur in this class are printed in {@code System.err}.
* <p>
* Sample configuration file can be found in <i>etc/logformatter.properties</i>.
* <p>
* This class is not intended to be used standalone.
* It is a part of implementation of {@link com.devexperts.logging} package.
*
* @see DetailedLogLayout
* @see DxFeedPatternLayout
*/
@ThreadSafe
public class LogFormatter extends Formatter {
public static final String CONFIG_FILE_PROPERTY = "logformatter.properties";
public static final String DEFAULT_CONFIG_FILE = "/META-INF/logformatter.properties";
private static final String LINE_SEP = DefaultLogging.getProperty("line.separator", "\n");
private static final BiConsumer<Object, StringBuilder> STRING_FORMAT_CONSUMER = (s, sb) -> sb.append(s);
// ============== Instance ================
private final ThreadLocal<LocalFormatter> formatter;
public LogFormatter() {
this(TimeZone.getDefault());
}
public LogFormatter(TimeZone zone) {
formatter = ThreadLocal.withInitial(() -> new LocalFormatter(zone));
}
/**
* Used by {@link java.util.logging} logging.
* Formats messages with the same format as for log4j.
*/
@Override
public String format(LogRecord record) {
String s = format(getLevelChar(record.getLevel()),
record.getMillis(), Thread.currentThread().getName(),
record.getLoggerName(), formatMessage(record));
if (record.getThrown() != null) {
StringWriter sw = new StringWriter();
sw.write(s);
record.getThrown().printStackTrace(new PrintWriter(sw));
s = sw.toString();
}
return s;
}
/**
* Formats log message.
*
* @return Formatted message.
* @throws NullPointerException when threadName, loggerName, or msg are {@code null}.
*/
public String format(char levelChar, long time, String threadName, String loggerName, String msg) {
StringBuilder out = formatter.get().appendTo;
out.setLength(0);
try {
format(levelChar, time, threadName, loggerName, STRING_FORMAT_CONSUMER, msg, out);
return out.toString();
} finally {
boolean trim = out.length() > 1000;
out.setLength(0);
if (trim)
out.trimToSize();
}
}
void format(char levelChar, long time, String threadName, String loggerName,
BiConsumer<Object, StringBuilder> msgConsumer, Object msg, StringBuilder out)
{
out.append(levelChar).append(" ");
formatter.get().appendTime(time, out);
out.append(" ");
int threadPosition = out.length();
out.append("[");
out.append(ThreadNameFormatter.formatThreadName(time, threadName));
out.append("] ");
out.append(loggerName, loggerName.lastIndexOf('.') + 1, loggerName.length());
out.append(" - ");
int messagePosition = out.length();
msgConsumer.accept(msg, out);
out.append(LINE_SEP);
if (out.length() > messagePosition && out.charAt(messagePosition) == '\b')
out.delete(threadPosition, messagePosition + 1);
}
static char getLevelChar(Level level) {
int levelInt = level.intValue();
if (levelInt <= Level.FINEST.intValue())
return 'T';
if (levelInt <= Level.FINE.intValue())
return 'D';
if (levelInt <= Level.INFO.intValue())
return 'I';
if (levelInt <= Level.WARNING.intValue())
return 'W';
return 'E';
}
private static class LocalFormatter {
private final Calendar calendar;
private final char[] timeBuffer = new char[17]; // fixed-size buffer for time data "yyMMdd HHmmss.SSS"
private final StringBuilder appendTo = new StringBuilder();
private long translatedMinute;
private LocalFormatter(TimeZone zone) {
calendar = Calendar.getInstance(zone);
Arrays.fill(timeBuffer, 0, 17, ' ');
timeBuffer[13] = '.';
}
private void appendTime(long time, StringBuilder out) {
if (time < translatedMinute || time >= translatedMinute + TimeUtil.MINUTE) {
// set year, month, day, hour and minute
calendar.setTimeInMillis(time);
translatedMinute = calendar.getTime().getTime() - calendar.get(Calendar.SECOND) * 1000 - calendar.get(Calendar.MILLISECOND);
print2(0, calendar.get(Calendar.YEAR));
print2(2, calendar.get(Calendar.MONTH) + 1);
print2(4, calendar.get(Calendar.DAY_OF_MONTH));
print2(7, calendar.get(Calendar.HOUR_OF_DAY));
print2(9, calendar.get(Calendar.MINUTE));
}
// set seconds and milliseconds
int millis = (int)(time - translatedMinute);
print2(11, millis / 1000);
print2(14, millis / 10);
timeBuffer[16] = (char)('0' + millis % 10);
out.append(timeBuffer);
}
private void print2(int offset, int value) {
timeBuffer[offset] = (char)('0' + (value / 10) % 10);
timeBuffer[offset + 1] = (char)('0' + value % 10);
}
}
}

View File

@ -0,0 +1,235 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.logging;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
/**
* Main logging class.
* It supports use of both log4j and {@link java.util.logging} logging facilities.
* <p>First it tries to use log4j logging. If this attempt fails, it uses {@link java.util.logging} logging,
* so you'll always have some logging running.
* <p>Usage pattern:
* <br><code>public class SomeClass {
* <br>private static final Logging log = Logging.getLogging(SomeClass.class);
* <br>}
* </code>
*
* @see Log4jLogging
* @see DefaultLogging
* @see LogFormatter
*/
public class Logging {
private static final boolean TRACE_LOGGING = Logging.class.desiredAssertionStatus();
private static final int FINEST_INT = Level.FINEST.intValue();
private static final int FINE_INT = Level.FINE.intValue();
public static final String LOG_CLASS_NAME = "log.className";
public static final String LOG_FILE_PROPERTY = "log.file";
public static final String ERR_FILE_PROPERTY = "err.file";
public static final String LOG_MAX_FILE_SIZE_PROPERTY = "log.maxFileSize";
public static final String ERR_MAX_FILE_SIZE_PROPERTY = "err.maxFileSize";
public static final String DEFAULT_MAX_FILE_SIZE = "900M";
private static final ConcurrentMap<String, Logging> INSTANCES = new ConcurrentHashMap<>();
private static final DefaultLogging IMPL = configure(DefaultLogging.getProperty(LOG_CLASS_NAME, ""));
public static Logging getLogging(Class<?> clazz) {
return getLogging(clazz.getName());
}
public static Logging getLogging(String name) {
Logging logging = INSTANCES.get(name);
if (logging != null)
return logging;
INSTANCES.putIfAbsent(name, new Logging(name));
return INSTANCES.get(name);
}
/**
* Programmatically reconfigures logging to a specified file. This method
* overrides the value of {@link #LOG_FILE_PROPERTY} system property.
*/
public static void configureLogFile(String log_file) {
reportErrors(IMPL, IMPL.configureLogFile(log_file));
}
// ========== Instance =========
private final Object peer;
/**
* This constructor is designed for abstract framework classes like BeanBase or
* DAOBase that extend Logging to decorate messages by
* overriding {@link #decorateLogMessage(String)} method.
*/
protected Logging() {
peer = IMPL.getPeer(getClass().getName());
}
protected Logging(String name) {
peer = IMPL.getPeer(name);
}
/**
* Returns category name of this logging.
*/
public final String getName() {
return IMPL.getName(peer);
}
/**
* Changes default {@link #debugEnabled()} behaviour for this logging instance.
* Use this method to turn off debugging information for classes that do not
* need to print their debugging information in production environment.
*/
public final void configureDebugEnabled(boolean defaultDebugEnabled) {
IMPL.setDebugEnabled(peer, Boolean.valueOf(DefaultLogging.getProperty(getName() + ".debug",
String.valueOf(defaultDebugEnabled))));
}
public final boolean debugEnabled() {
return IMPL.debugEnabled(peer);
}
public final void trace(String message) {
log(Level.FINEST, message, null);
}
public final void debug(String message) {
log(Level.FINE, message, null);
}
public final void debug(String message, Throwable t) {
log(Level.FINE, message, t);
}
public final void info(String message) {
log(Level.INFO, message, null);
}
public final void info(String message, Throwable t) {
log(Level.INFO, message, t);
}
public final void warn(String message) {
log(Level.WARNING, message, null);
}
public final void warn(String message, Throwable t) {
log(Level.WARNING, message, t);
}
public final void error(String message) {
log(Level.SEVERE, message, null);
}
public final void error(String message, Throwable t) {
log(Level.SEVERE, message, t);
}
public final RuntimeException log(RuntimeException e) {
log(Level.SEVERE, e.getMessage(), e);
return e;
}
/**
* Decorates log message (reformatting, auditing, etc).
* This method is invoked one time for each logging event.
*/
protected String decorateLogMessage(String msg) {
return msg;
}
// ========== Internal ==========
private void log(Level level, String msg, Throwable t) {
if (TRACE_LOGGING)
TraceLogging.log(getName(), level, decorateLogMessage(msg), t);
int levelInt = level.intValue();
if (levelInt <= FINEST_INT)
return; // trace never goes to regular log
if (levelInt <= FINE_INT && !IMPL.debugEnabled(peer))
return;
try {
msg = decorateLogMessage(msg == null ? "" : msg);
} catch (Throwable tt) {
IMPL.log(peer, Level.SEVERE, "Failed to decorate log message", tt);
}
IMPL.log(peer, level, msg, t);
}
/**
* At first tries to use logging from passed class name. If this attempt fails, tries to use log4j logging.
* If this attempt fails, it uses log4j2 logging. If this attempt fails, it uses {@link java.util.logging} logging.
*
* @return Logging implementation
*/
private static DefaultLogging configure(String className) {
DefaultLogging impl = null;
Map<String, Exception> errors = new LinkedHashMap<>();
if (!className.isEmpty()) {
try {
impl = (DefaultLogging)Class.forName(className).newInstance();
errors.putAll(impl.configure());
} catch (Throwable t) {
// failed to configure with passed class name
impl = null;
if (!(t instanceof LinkageError) && !(t.getCause() instanceof LinkageError)) {
errors.put(className + " link", new IllegalStateException(t));
}
}
}
if (impl == null) {
try {
impl = (DefaultLogging)Class.forName("com.devexperts.logging.Log4jLogging").newInstance();
errors.putAll(impl.configure());
} catch (Throwable t) {
// failed to configure log4j
impl = null;
// LinkageError means that log4j is not found at all, otherwise it was found but our config is wrong
if (!(t instanceof LinkageError) && !(t.getCause() instanceof LinkageError)) {
errors.put("log4j link", new IllegalStateException(t));
}
}
}
if (impl == null) {
try {
impl = (DefaultLogging)Class.forName("com.devexperts.logging.Log4j2Logging").newInstance();
errors.putAll(impl.configure());
} catch (Throwable t) {
// failed to configure log4j2
impl = null;
if (!(t instanceof LinkageError) && !(t.getCause() instanceof LinkageError)) {
errors.put("log4j2 link", new IllegalStateException(t));
}
}
}
if (impl == null) {
impl = new DefaultLogging();
errors.putAll(impl.configure());
}
reportErrors(impl, errors);
return impl;
}
private static void reportErrors(DefaultLogging impl, Map<String, Exception> errors) {
for (Map.Entry<String, Exception> entry : errors.entrySet())
impl.log(impl.getPeer("config"), Level.SEVERE, entry.getKey(), entry.getValue());
}
}

View File

@ -0,0 +1,179 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.logging;
import com.devexperts.util.IndexedSet;
import com.devexperts.util.QuickSort;
import com.devexperts.util.SynchronizedIndexedSet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
class ThreadNameFormatter implements Comparable<ThreadNameFormatter> {
/**
* Configuration as a set of pairs (pattern, replacement).
*/
private static final Map<Pattern, String> PATTERNS = new LinkedHashMap<>();
private static final int MAX_NAME_CONVERSIONS_CACHE_SIZE = 1000;
/**
* Thread name replacement cache: (thread name, replacement string).
*/
private static final IndexedSet<String, ThreadNameFormatter> NAME_CONVERSIONS = SynchronizedIndexedSet.create(ThreadNameFormatter::getThreadName);
static {
loadPatterns();
}
private static void loadPatterns() {
InputStream config_input_stream = null;
try {
String config_path = DefaultLogging.getProperty(LogFormatter.CONFIG_FILE_PROPERTY, null);
if (config_path != null)
throw new UnsupportedOperationException("loadPatterns");
/*
try {
config_input_stream = new URLInputStream(config_path);
} catch (IOException e) {
System.err.println("Cannot find log formatter configuration file: '" + config_path + "'");
System.err.println("No thread name conversion will be performed.");
return;
}
*/ else
config_input_stream = LogFormatter.class.getResourceAsStream(LogFormatter.DEFAULT_CONFIG_FILE);
if (config_input_stream == null)
return;
BufferedReader reader = new BufferedReader(
new InputStreamReader(config_input_stream, Charset.forName("ISO-8859-1")));
Pattern config_line_pattern = Pattern.compile("((?:[^=]|(?:\\\\=))*[^\\\\=])=(.*)");
Pattern whitespace_line_pattern = Pattern.compile("\\s*");
Pattern comment_line_pattern = Pattern.compile("#.*|!.*");
String line;
Set<String> patterns_set = new HashSet<String>();
while ((line = reader.readLine()) != null) {
Matcher config_line_matcher = config_line_pattern.matcher(line);
// If it is whitespace or comment line
if (whitespace_line_pattern.matcher(line).matches() || comment_line_pattern.matcher(line).matches())
continue;
if (!config_line_matcher.matches()) {
System.err.println("The following line cannot be parsed in log formatter configuration file: '" + line + "'");
continue;
}
String config_pattern = config_line_matcher.group(1);
String config_replacement = config_line_matcher.group(2);
if (!patterns_set.add(config_pattern)) {
System.err.println("Duplicate pattern found in log formatter configuration file: '" + config_pattern + "'");
continue;
}
try {
PATTERNS.put(Pattern.compile(config_pattern), config_replacement);
} catch (PatternSyntaxException e) {
System.err.println("Cannot parse config pattern in log formatter configuration file: '" + config_pattern + "'");
}
}
} catch (IOException e) {
// Do not wish to log using logger until initialization has completed.
System.err.println("Cannot read log formatter configuration file");
e.printStackTrace(System.err);
} finally {
try {
if (config_input_stream != null) {
config_input_stream.close();
}
} catch (IOException e) {
// Do not wish to log using logger until initialization has completed.
System.err.println("Cannot close log formatter configuration file");
e.printStackTrace(System.err);
}
}
}
/**
* Formats thread name according to thread name conversion rules.
*
* @return Formatted thread name
*/
static String formatThreadName(long time, String thread_name) {
ThreadNameFormatter entry = NAME_CONVERSIONS.getByKey(thread_name);
if (entry == null) {
cleanupNameConversionsIfNeeded();
entry = new ThreadNameFormatter(thread_name, calculateThreadNameReplacement(thread_name));
NAME_CONVERSIONS.put(entry);
}
entry.last_time = time;
return entry.replacement_name;
}
private static void cleanupNameConversionsIfNeeded() {
if (NAME_CONVERSIONS.size() <= MAX_NAME_CONVERSIONS_CACHE_SIZE)
return; // everything is Ok
synchronized (NAME_CONVERSIONS) {
if (NAME_CONVERSIONS.size() <= MAX_NAME_CONVERSIONS_CACHE_SIZE)
return; // everything is Ok
ThreadNameFormatter[] entries = NAME_CONVERSIONS.toArray(new ThreadNameFormatter[NAME_CONVERSIONS.size()]);
QuickSort.sort(entries);
for (int i = 0; i < entries.length - MAX_NAME_CONVERSIONS_CACHE_SIZE / 2; i++)
NAME_CONVERSIONS.removeKey(entries[i].thread_name);
}
}
private static String calculateThreadNameReplacement(String thread_name) {
for (Map.Entry<Pattern, String> entry : PATTERNS.entrySet()) {
Matcher matcher = entry.getKey().matcher(thread_name);
if (matcher.matches()) {
String config_replacement = entry.getValue();
try {
return matcher.replaceAll(config_replacement);
} catch (IndexOutOfBoundsException e) {
// The replacement string refers to a capturing group that does not exist in the pattern.
// To prevent cycling log it as is.
// Incorrect replacement. Just use thread name.
System.err.println("Cannot parse replacement string in log formatter configuration file: '" + config_replacement + "'");
}
}
}
return thread_name;
}
final String thread_name;
final String replacement_name;
long last_time; // Atomicity, visibility and consistency of this field are unimportant.
ThreadNameFormatter(String thread_name, String replacement_name) {
this.thread_name = thread_name;
this.replacement_name = replacement_name;
}
private String getThreadName() {
return thread_name;
}
public int compareTo(ThreadNameFormatter o) {
return last_time < o.last_time ? -1 : last_time > o.last_time ? 1 : 0;
}
}

View File

@ -0,0 +1,146 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.logging;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
/**
* This is a small in-memory cyclic buffer to keep the log for debugging concurrency problems in order to
* reconstruct what was going on immediately before tests crashes, without actually writing all debug logging
* to the log file or console normally. It is used by some tests when assertions are enabled,
* otherwise this class should not even be loaded.
*/
public class TraceLogging {
private static final int STOPPED_INDEX = -1;
private static final int SIZE = Integer.parseInt(System.getProperty("TraceLogging.size", "4096")); // must be power of 2
private static final int MASK = SIZE - 1;
private static final int THREAD_OFS = 0;
private static final int NAME_OFS = 1;
private static final int LEVEL_OFS = 2;
private static final int MSG_OFS = 3;
private static final int THROWABLE_OFS = 4;
private static final int DATA_CNT = 5;
private static final long[] timeQueue = new long[SIZE];
private static final Object[] dataQueue = new Object[SIZE * DATA_CNT];
private static final AtomicInteger index = new AtomicInteger(STOPPED_INDEX);
private static int lastIndex = STOPPED_INDEX;
static {
if ((SIZE & MASK) != 0)
throw new RuntimeException("Size must be a power of two");
}
/**
* Restarts trace logging from scratch (old log entries are cleared).
* Use it at the beginning of the test.
*/
public static synchronized void restart() {
Arrays.fill(dataQueue, null);
lastIndex = STOPPED_INDEX;
index.compareAndSet(STOPPED_INDEX, 0);
}
/**
* Stops trace logging.
*/
public static void stop() {
stopIndex(-1);
}
/**
* Adds log entry. It is invoked from {@link Logging#log(Level, String, Throwable)} method when
* assertions are enabled.
*/
public static void log(String loggerName, Level level, String msg, Throwable t) {
append(nextIndex(), loggerName, level, msg, t);
}
/**
* Adds last entry and stops trace logging.
*/
public static void logAndStop(Class<?> where, String msg) {
logAndStop(where, msg, null);
}
/**
* Adds last entry with exception and stops trace logging.
*/
public static void logAndStop(Class<?> where, String msg, Throwable t) {
append(stopIndex(0), where.getName(), Level.INFO, msg, t);
}
private static void append(int i, String loggerName, Level level, String msg, Throwable t) {
if (i < 0)
return;
timeQueue[i] = System.currentTimeMillis();
dataQueue[i * DATA_CNT + THREAD_OFS] = Thread.currentThread();
dataQueue[i * DATA_CNT + NAME_OFS] = loggerName;
dataQueue[i * DATA_CNT + LEVEL_OFS] = level;
dataQueue[i * DATA_CNT + MSG_OFS] = msg;
dataQueue[i * DATA_CNT + THROWABLE_OFS] = t;
}
/**
* Dumps last entries from this trace log.
* It should be called after {@link #stop()} or {@link #logAndStop(Class, String)}.
* It does nothing if called more than once after stop or before stop.
*/
public static synchronized void dump(PrintStream out, String title) {
int stop = lastIndex;
if (stop < 0)
return;
lastIndex = STOPPED_INDEX;
LogFormatter formatter = new LogFormatter();
out.println("********************** Dump trace log for " + title);
int i = stop;
do {
i = (i + 1) & MASK;
Thread thread = (Thread)dataQueue[i * DATA_CNT + THREAD_OFS];
if (thread == null)
continue;
String loggerName = (String)dataQueue[i * DATA_CNT + NAME_OFS];
Level level = (Level)dataQueue[i * DATA_CNT + LEVEL_OFS];
String msg = (String)dataQueue[i * DATA_CNT + MSG_OFS];
Throwable t = (Throwable)dataQueue[i * DATA_CNT + THROWABLE_OFS];
long time = timeQueue[i];
out.print("* ");
out.print(formatter.format(LogFormatter.getLevelChar(level), time, thread.getName(), loggerName, msg));
if (t != null)
t.printStackTrace(out);
} while (i != stop);
out.println("********************** Done trace log for " + title);
}
private static int nextIndex() {
int result;
do {
result = index.get();
} while (result >= 0 && !index.compareAndSet(result, (result + 1) & MASK));
return result;
}
private static synchronized int stopIndex(int lastOffset) {
int result;
do {
result = index.get();
} while (result >= 0 && !index.compareAndSet(result, STOPPED_INDEX));
if (result >= 0)
lastIndex = (result + lastOffset) & MASK;
return result;
}
}

View File

@ -0,0 +1,16 @@
<!--
!++
QDS - Quick Data Signalling Library
!-
Copyright (C) 2002 - 2020 Devexperts LLC
!-
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
!__
-->
<html>
<body>
Provides logging classes.
</body>
</html>

View File

@ -0,0 +1,221 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.util;
import java.lang.reflect.Array;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
/**
* Provides a skeletal implementation of the {@link Set Set} interface to minimize the effort
* required to implement this interface. Unlike {@link AbstractSet AbstractSet} skeletal implementation,
* this one is more forgiving to concurrent modifications of this set during implemented bulk operations.
*/
public abstract class AbstractConcurrentSet<E> implements Set<E> {
// ========== Construction and Clearing ==========
/**
* Sole constructor; for invocation by subclass constructors, typically implicit.
*/
protected AbstractConcurrentSet() {}
/**
* Removes all of the elements from this set.
* <p>
* This implementation iterates all elements of this set and removes them using {@link Iterator#remove()} method.
*/
public void clear() {
for (Iterator<E> it = iterator(); it.hasNext();) {
it.next();
it.remove();
}
}
// ========== Query Operations ==========
/**
* Tests if this set has no elements.
* <p>
* This implementation checks emptiness using {@link #size()} method.
*/
public boolean isEmpty() {
return size() == 0;
}
/**
* Returns an array containing all of the elements in this set.
* Obeys the general contract of the {@link Collection#toArray()} method.
* <p>
* This implementation iterates all elements of this set and adds them into the array.
*/
public Object[] toArray() {
return toArrayImpl(null);
}
/**
* Returns an array containing all of the elements in this set whose runtime type
* is that of the specified array.
* Obeys the general contract of the {@link Collection#toArray(Object[])} method.
* <p>
* This implementation iterates all elements of this set and adds them into the array.
*/
public <T> T[] toArray(T[] a) {
return toArrayImpl(a);
}
@SuppressWarnings("unchecked")
private <T> T[] toArrayImpl(T[] a) {
// If (a == null) then returned array shall be of exact length, otherwise it can be larger.
int size = size(); // Atomic read.
Object[] result = a == null ? new Object[size] : a.length >= size ? a :
(Object[])Array.newInstance(a.getClass().getComponentType(), size);
int n = 0;
for (E o : this) {
if (n >= result.length) {
// More elements were added concurrently. Enlarge result array.
// Grow twice in size, but do not fail when (n == 0).
Object[] tmp = (Object[])Array.newInstance(result.getClass().getComponentType(), n + n + 1);
System.arraycopy(result, 0, tmp, 0, n);
result = tmp;
}
result[n++] = o;
}
if (n < result.length && a == null) {
// Shrink allocated array to exact size.
Object[] tmp = new Object[n];
System.arraycopy(result, 0, tmp, 0, n);
result = tmp;
}
if (n < result.length)
result[n] = null;
return (T[])result;
}
// ========== Bulk Operations ==========
/**
* Tests if this set contains all of the elements in the specified collection.
* <p>
* This implementation iterates all elements of specified collection and tests
* them one-by-one using {@link #contains(Object) contains(element)} method.
*/
public boolean containsAll(Collection<?> c) {
for (Object o : c)
if (!contains(o))
return false;
return true;
}
/**
* Adds all of the elements in the specified collection into this set and
* returns <b>true</b> if this operation has increased the size of this set.
* <p>
* This implementation iterates all elements of specified collection and adds
* them one-by-one using {@link #add(Object) add(element)} method.
*/
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E o : c)
if (add(o))
modified = true;
return modified;
}
/**
* Removes all of the elements in the specified collection from this set and
* returns <b>true</b> if this operation has decreased the size of this set.
* <p>
* This implementation compares size of specified collection with the size of this set,
* then iterates smaller collection and removes elements that need to be removed.
*/
public boolean removeAll(Collection<?> c) {
boolean modified = false;
if (size() > c.size()) {
for (Object o : c)
if (remove(o))
modified = true;
} else {
for (Iterator<E> it = iterator(); it.hasNext();)
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
/**
* Retains only the elements in this set that are contained in the specified collection.
* <p>
* This implementation iterates all elements of this set, checks if they are contained in
* the specified collection, and removes them if needed using {@link Iterator#remove()} method.
*/
public boolean retainAll(Collection<?> c) {
boolean modified = false;
for (Iterator<E> it = iterator(); it.hasNext();)
if (!c.contains(it.next())) {
it.remove();
modified = true;
}
return modified;
}
// ========== Comparison and Hashing ==========
/**
* Compares the specified object with this set for equality.
* Obeys the general contract of the {@link Set#equals(Object)} method.
* <p>
* This implementation compares size of specified set with the size of this set and then
* checks element containment using {@link #containsAll(Collection) containsAll((Set)o)} method.
*/
public boolean equals(Object o) {
return o == this || o instanceof Set && size() == ((Set)o).size() && containsAll((Set)o);
}
/**
* Returns the hash code value for this set.
* Obeys the general contract of the {@link Set#hashCode()} method.
* <p>
* This implementation iterates all elements of this set and adds their hash codes.
*/
public int hashCode() {
int hash = 0;
for (E o : this)
if (o != null)
hash += o.hashCode();
return hash;
}
// ========== String Conversion ==========
/**
* Returns a string representation of this set.
* <p>
* This implementation iterates all elements of this set and concatenates their string representations.
*/
public String toString() {
StringBuilder sb = new StringBuilder(size() * 3 + 10);
sb.append("[");
String separator = "";
for (E o : this) {
sb.append(separator);
sb.append(o);
separator = ", ";
}
sb.append("]");
return sb.toString();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.util;
import java.io.Serializable;
import java.util.Locale;
/**
* Provides access to statistics of the {@link IndexedSet} static structure.
* Statistics are tracked only during modification operations.
* It has self-explanatory {@link #toString()} method that can be used to periodically dump
* information about important caches that are based on the {@code IndexedSet}.
*/
public class IndexedSetStats implements Serializable {
private static final long serialVersionUID = 0;
private final int payload_size;
private final int allocated_size;
private final long payload_distance;
private final long amortized_cost;
private final long mod_count;
IndexedSetStats(int payload_size, int allocated_size, long payload_distance, long amortized_cost, long mod_count) {
this.payload_size = payload_size;
this.allocated_size = allocated_size;
this.payload_distance = payload_distance;
this.amortized_cost = amortized_cost;
this.mod_count = mod_count;
}
public int getSize() {
return payload_size;
}
public int getAllocatedSize() {
return allocated_size;
}
public double getFillFactor() {
return (double)payload_size / allocated_size;
}
public double getAverageDistance() {
return payload_distance == 0 ? 0 : (double)payload_distance / payload_size;
}
public double getAmortizedCost() {
return amortized_cost == 0 ? 0 : (double)amortized_cost / payload_size;
}
public long getModCount() {
return mod_count;
}
@Override
public String toString() {
return String.format(Locale.US, "size %d, filled %.1f%%, avgdist %.3f, amortized %.3f, mods %d",
getSize(), getFillFactor() * 100, getAverageDistance(), getAmortizedCost(), getModCount());
}
}

View File

@ -0,0 +1,110 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.util;
import java.io.Serializable;
/**
* A strategy that distinguishes and identifies elements in an {@link IndexedSet} and {@link IndexedMap}.
*
* <p>The <b>Indexer</b> is {@link Serializable}, so that all concrete subclasses
* shall be <b>serializable</b> in order to support serialization of indexed set and map..
*
* @deprecated Use a functional interface {@link IndexerFunction} instead.
*/
@Deprecated
public abstract class Indexer<K, V> implements IndexerFunction<K, V> {
private static final long serialVersionUID = 0L;
/**
* Default strategy that treats values as their own keys <b>(key&nbsp;==&nbsp;value)</b> and delegates to
* {@link Object#hashCode() Object.hashCode()} and {@link Object#equals(Object) Object.equals(Object)}
* methods as appropriate. This strategy does not support primitive keys.
*
* <p>This strategy basically turns {@link IndexedSet} into plain hash set of objects and {@link IndexedMap}
* into a self-reference mapping.
*/
@SuppressWarnings("rawtypes")
public static final Indexer DEFAULT = new DefaultIndexer();
// ========== Standard Subclasses ==========
/**
* Default strategy that treats values as their own keys <b>(key&nbsp;==&nbsp;value)</b>.
*/
@SuppressWarnings("rawtypes")
static final class DefaultIndexer extends Indexer {
private static final long serialVersionUID = 0;
DefaultIndexer() {}
@Override
public Object getObjectKey(Object value) {
return value;
}
@SuppressWarnings("ReadResolveAndWriteReplaceProtected")
public Object readResolve() {
return Indexer.DEFAULT;
}
}
static class DelegateIndexer<K, V> extends Indexer<K, V> {
private static final long serialVersionUID = 0L;
private final IndexerFunction<K, V> indexer;
DelegateIndexer(IndexerFunction<K, V> indexer) {
this.indexer = indexer;
}
@Override
public K getObjectKey(V value) {
return indexer.getObjectKey(value);
}
@Override
public int hashCodeByKey(K key) {
return indexer.hashCodeByKey(key);
}
@Override
public boolean matchesByKey(K key, V value) {
return indexer.matchesByKey(key, value);
}
@Override
public int hashCodeByValue(V value) {
return indexer.hashCodeByValue(value);
}
@Override
public boolean matchesByValue(V newValue, V oldValue) {
return indexer.matchesByValue(newValue, oldValue);
}
@Override
public long getNumberKey(V value) {
return indexer.getNumberKey(value);
}
@Override
public int hashCodeByKey(long key) {
return indexer.hashCodeByKey(key);
}
@Override
public boolean matchesByKey(long key, V value) {
return indexer.matchesByKey(key, value);
}
}
}

View File

@ -0,0 +1,354 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.util;
import java.io.Serializable;
/**
* A strategy that distinguishes and identifies elements in an {@link IndexedSet} and {@link IndexedMap}.
* The <b>IndexerFunction</b> defines 3 equivalent ways to identify elements:
* <ul>
* <li> <b>by value</b> - mandatory and primary method to identify element by itself
* <li> <b>by object key</b> - optional method that identifies elements using object key
* <li> <b>by number key</b> - optional method that identifies elements using number key
* </ul>
*
* <p>The <b>IndexerFunction</b> is not restricted to use <b>explicit key</b> concept for identification.
* Identity of an element may be defined by a number of attributes, specified in a value itself,
* in a template object, in a formal key object, or encoded in a number key. The <b>IndexerFunction</b>
* may use all these ways interchangeable to distinguish and identify elements.
*
* <p>Being a strategy, the <b>IndexerFunction</b> is required to be stateless, concurrent and thread-safe.
*
* <p>The <b>IndexerFunction</b> is a functional interface with a sole abstract method that shall be implemented
* in order to use identification using explicit object key - no other methods are required to be
* overridden in such simple cases.
* There are two other functional interfaces {@link IndexerFunction.IntKey} and {@link IndexerFunction.LongKey}
* which are similarly designed with sole abstract methods to simplify identification using explicit number keys.
* There is also a functional interface {@link IndexerFunction.IdentityKey} which is similarly designed
* with sole abstract method for cases when explicit object keys must be compared by reference rather than
* using their {@link Object#equals(Object) equals} method.
*
* <p>The <b>IndexerFunction</b> is {@link Serializable}, so that all concrete subclasses
* shall be <b>serializable</b> in order to support serialization of indexed set and map.
*/
@FunctionalInterface
public interface IndexerFunction<K, V> extends Serializable {
/**
* Default strategy that treats values as their own keys <b>(key&nbsp;==&nbsp;value)</b> and delegates to
* {@link Object#hashCode() Object.hashCode()} and {@link Object#equals(Object) Object.equals(Object)}
* methods as appropriate. This strategy does not support primitive keys.
*
* <p>This strategy basically turns {@link IndexedSet} into plain hash set of objects and {@link IndexedMap}
* into a self-reference mapping.
*/
@SuppressWarnings("rawtypes")
public static final IndexerFunction DEFAULT = new DefaultIndexerFunction();
// ========== Object Key Operations ==========
/**
* Returns object key for specified value to be used for hashing and identification;
* called when explicit object key is needed or when other methods delegate operations as specified.
*/
public K getObjectKey(V value);
/**
* Returns hash code for specified object key; called when performing operations using object keys.
*
* <p>This implementation delegates to
* <code>(key&nbsp;==&nbsp;null&nbsp;?&nbsp;0&nbsp;:&nbsp;key.{@link Object#hashCode() hashCode}())</code> expression.
*/
public default int hashCodeByKey(K key) {
return key == null ? 0 : key.hashCode();
}
/**
* Determines if specified object key matches specified value; called when performing operations using object keys.
*
* <p>This implementation delegates to
* <code>(key&nbsp;==&nbsp;null&nbsp;?&nbsp;{@link #getObjectKey(Object) getObjectKey}(value)&nbsp;==&nbsp;null&nbsp;:&nbsp;key.{@link Object#equals(Object) equals}({@link #getObjectKey(Object) getObjectKey}(value)))</code> expression.
*/
public default boolean matchesByKey(K key, V value) {
return key == null ? getObjectKey(value) == null : key.equals(getObjectKey(value));
}
// ========== Value Operations ==========
/**
* Returns hash code for specified value; called when performing value-based operations, including <b>rehash</b>.
*
* <p>This implementation delegates to
* <code>{@link #hashCodeByKey(Object) hashCodeByKey}({@link #getObjectKey(Object) getObjectKey}(value))</code> expression.
*/
public default int hashCodeByValue(V value) {
return hashCodeByKey(getObjectKey(value));
}
/**
* Determines if specified new value matches specified old value; called when performing value-based operations.
*
* <p>This implementation delegates to
* <code>{@link #matchesByKey(Object, Object) matchesByKey}({@link #getObjectKey(Object) getObjectKey}(newValue),&nbsp;oldValue)</code> expression.
*/
public default boolean matchesByValue(V newValue, V oldValue) {
return matchesByKey(getObjectKey(newValue), oldValue);
}
// ========== Number Key Operations (Optional) ==========
/**
* Returns number key for specified value to be used for hashing and identification;
* called when explicit number key is needed or when other methods delegate operations as specified.
*
* <p>This implementation delegates to
* <code>{@link #hashCodeByKey(long) hashCodeByKey}(((Number){@link #getObjectKey(Object) getObjectKey}(value)).{@link Number#longValue() longValue}())</code> expression.
*/
public default long getNumberKey(V value) {
return hashCodeByKey(((Number)getObjectKey(value)).longValue());
}
/**
* Returns hash code for specified number key; called when performing operations using long keys.
*
* <p>This implementation delegates to
* <code>Long.{@link Long#hashCode(long) hashCode}(key)</code> expression.
*/
public default int hashCodeByKey(long key) {
return Long.hashCode(key);
}
/**
* Determines if specified number key matches specified value; called when performing operations using number keys.
*
* <p>This implementation delegates to
* <code>(key&nbsp;==&nbsp;{@link #getNumberKey(Object) getNumberKey}(value))</code> expression.
*/
public default boolean matchesByKey(long key, V value) {
return key == getNumberKey(value);
}
/**
* An {@link IndexerFunction} that distinguishes and identifies elements using <b>int</b> keys.
*
* <p>It assumes that elements are fully identifiable by plain numeric identifier and treats object keys as a mere wrappers.
* The hash function is computed by taking <b>int</b> key value.
*/
@FunctionalInterface
public interface IntKey<V> extends IndexerFunction<Integer, V> {
/**
* Returns number key for specified value to be used for hashing and identification.
*/
public int getIntKey(V value);
/**
* Returns number key for specified value to be used for hashing and identification;
* called when explicit number key is needed or when other methods delegate operations as specified.
*
* <p>This implementation delegates to
* <code>{@link #getIntKey(Object) getIntKey}(value)</code> expression.
*/
@Override
public default long getNumberKey(V value) {
return getIntKey(value);
}
/**
* Returns hash code for specified value; called when performing value-based operations, including <b>rehash</b>.
*
* <p>This implementation delegates to
* <code>{@link #hashCodeByKey(long) hashCodeByKey}({@link #getIntKey(Object) getIntKey}(value))</code> expression.
*/
@Override
public default int hashCodeByValue(V value) {
return hashCodeByKey(getIntKey(value));
}
/**
* Determines if specified new value matches specified old value; called when performing value-based operations.
*
* <p>This implementation delegates to
* <code>({@link #getIntKey(Object) getIntKey}(newValue)&nbsp;==&nbsp;{@link #getIntKey(Object) getIntKey}(oldValue))</code> expression.
*/
@Override
public default boolean matchesByValue(V newValue, V oldValue) {
return getIntKey(newValue) == getIntKey(oldValue);
}
/**
* Returns object key for specified value to be used for hashing and identification;
* called when explicit object key is needed or when other methods delegate operations as specified.
*
* <p>This implementation delegates to
* <code>{@link #getIntKey(Object) getIntKey}(value)</code> expression.
*/
@Override
public default Integer getObjectKey(V value) {
return getIntKey(value);
}
/**
* Returns hash code for specified object key; called when performing operations using object keys.
*
* <p>This implementation delegates to
* <code>{@link #hashCodeByKey(long) hashCodeByKey}(key.{@link Integer#intValue() intValue}())</code> expression.
*/
@Override
public default int hashCodeByKey(Integer key) {
return hashCodeByKey(key.intValue());
}
/**
* Determines if specified object key matches specified value; called when performing operations using object keys.
*
* <p>This implementation delegates to
* <code>(key&nbsp;==&nbsp;{@link #getIntKey(Object) getIntKey}(value))</code> expression.
*/
@Override
public default boolean matchesByKey(Integer key, V value) {
return key == getIntKey(value);
}
}
/**
* An {@link IndexerFunction} that distinguishes and identifies elements using <b>long</b> keys.
*
* <p>It assumes that elements are fully identifiable by plain numeric identifier and treats object keys as a mere wrappers.
* The hash function is computed using <code>{@link Long#hashCode(long) Long.hashCode}(key)</code> expression.
*/
@FunctionalInterface
public interface LongKey<V> extends IndexerFunction<Long, V> {
/**
* Returns number key for specified value to be used for hashing and identification;
* called when explicit number key is needed or when other methods delegate operations as specified.
*/
@Override
public long getNumberKey(V value);
/**
* Returns hash code for specified value; called when performing value-based operations, including <b>rehash</b>.
*
* <p>This implementation delegates to
* <code>{@link #hashCodeByKey(long) hashCodeByKey}({@link #getNumberKey(Object) getNumberKey}(value))</code> expression.
*/
@Override
public default int hashCodeByValue(V value) {
return hashCodeByKey(getNumberKey(value));
}
/**
* Determines if specified new value matches specified old value; called when performing value-based operations.
*
* <p>This implementation delegates to
* <code>({@link #getNumberKey(Object) getNumberKey}(newValue)&nbsp;==&nbsp;{@link #getNumberKey(Object) getNumberKey}(oldValue))</code> expression.
*/
@Override
public default boolean matchesByValue(V newValue, V oldValue) {
return getNumberKey(newValue) == getNumberKey(oldValue);
}
/**
* Returns object key for specified value to be used for hashing and identification;
* called when explicit object key is needed or when other methods delegate operations as specified.
*
* <p>This implementation delegates to
* <code>{@link #getNumberKey(Object) getNumberKey}(value)</code> expression.
*/
@Override
public default Long getObjectKey(V value) {
return getNumberKey(value);
}
/**
* Returns hash code for specified object key; called when performing operations using object keys.
*
* <p>This implementation delegates to
* <code>{@link #hashCodeByKey(long) hashCodeByKey}(key.{@link Long#longValue() longValue}())</code> expression.
*/
@Override
public default int hashCodeByKey(Long key) {
return hashCodeByKey(key.longValue());
}
/**
* Determines if specified object key matches specified value; called when performing operations using object keys.
*
* <p>This implementation delegates to
* <code>(key&nbsp;==&nbsp;{@link #getNumberKey(Object) getNumberKey}(value))</code> expression.
*/
@Override
public default boolean matchesByKey(Long key, V value) {
return key == getNumberKey(value);
}
}
/**
* A specialized {@link IndexerFunction} that distinguishes and identifies elements using identity comparison of object keys.
*
* <p>It uses {@link System#identityHashCode(Object) System.identityHashCode(Object)} method instead of
* {@link Object#hashCode() Object.hashCode()} method to compute hashcode and reference comparison
* instead of {@link Object#equals(Object) Object.equals(Object)} method to determine identity.
*
* <p>In order to use this functional interface, cast a lambda expression to it when creating {@link IndexedSet}. For example,<br>
* <code>IndexedSet&lt;Key, Value&gt; set = IndexedSet.{@link IndexedSet#create(IndexerFunction) create}((IndexerFunction.IdentityKey&lt;Key, Value&gt;)Value::getKey);</code>
*/
@FunctionalInterface
public interface IdentityKey<K, V> extends IndexerFunction<K, V> {
/**
* Returns hash code for specified object key; called when performing operations using object keys.
*
* <p>This implementation delegates to
* <code>System.{@link System#identityHashCode(Object) identityHashCode}(key)</code> expression.
*/
@Override
public default int hashCodeByKey(K key) {
return System.identityHashCode(key);
}
/**
* Determines if specified object key matches specified value; called when performing operations using object keys.
*
* <p>This implementation delegates to
* <code>(key&nbsp;==&nbsp;{@link #getObjectKey(Object) getObjectKey}(value))</code> expression.
*/
@Override
public default boolean matchesByKey(K key, V value) {
return key == getObjectKey(value);
}
}
/**
* Default strategy that treats values as their own keys <b>(key&nbsp;==&nbsp;value)</b>.
*/
@SuppressWarnings("rawtypes")
static final class DefaultIndexerFunction implements IndexerFunction {
private static final long serialVersionUID = 0;
DefaultIndexerFunction() {}
@Override
public Object getObjectKey(Object value) {
return value;
}
// Substitute DefaultIndexer implementation for backward compatibility
private Object writeReplace() {
return Indexer.DEFAULT;
}
private Object readResolve() {
return IndexerFunction.DEFAULT;
}
}
}

View File

@ -0,0 +1,233 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.util;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.IntFunction;
import java.util.function.IntToDoubleFunction;
import java.util.function.IntToLongFunction;
import java.util.function.IntUnaryOperator;
/**
* A comparison function, which imposes a <i>total ordering</i> on some collection of ints.
* Comparators can be passed to a sort method (such as {@link QuickSort#sort(int[], IntComparator) QuickSort.sort})
* to allow precise control over the sort order.
*
* <p>The purpose of this function is to allow non-trivial ordering of ints which depend on some external data.
* For example when ints are some identifiers (pseudo-references) of actual data.
*/
@SuppressWarnings("UnusedDeclaration")
public interface IntComparator {
/**
* Compares its two arguments for order. Returns a negative integer, zero, or a positive integer
* as the first argument is less than, equal to, or greater than the second.
*
* @param i1 the first int to be compared.
* @param i2 the second int to be compared.
* @return a negative integer, zero, or a positive integer as the first argument is
* less than, equal to, or greater than the second.
*/
public int compare(int i1, int i2);
/**
* Returns a comparator that imposes the reverse ordering of this comparator.
*
* @return a comparator that imposes the reverse ordering of this comparator.
*/
public default IntComparator reversed() {
return (IntComparator & Serializable) (i1, i2) -> compare(i2, i1);
}
/**
* Returns a lexicographic-order comparator with another comparator.
* If this comparator considers two elements equal, i.e. {@code compare(i1, i2) == 0},
* then other comparator is used to determine the order.
*
* <p>The returned comparator is serializable if the specified comparator is also serializable.
*
* @param other the other comparator to be used when this comparator compares two ints that are equal.
* @return a lexicographic-order comparator composed of this comparator and then the other comparator.
* @throws NullPointerException if the argument is null.
*/
public default IntComparator thenComparing(IntComparator other) {
Objects.requireNonNull(other);
return (IntComparator & Serializable) (i1, i2) -> {
int res = compare(i1, i2);
return res != 0 ? res : other.compare(i1, i2);
};
}
/**
* Returns a lexicographic-order comparator with a function that extracts
* a sort key to be compared with the given sort key comparator.
*
* <p>This default implementation delegates to
* <code>{@link #thenComparing(IntComparator) thenComparing}({@link #comparing(IntFunction, Comparator) comparing}(keyExtractor,&nbsp;keyComparator))</code> expression.
*
* @param <K> the type of the sort key.
* @param keyExtractor the function used to extract the sort key.
* @param keyComparator the comparator used to compare the sort key.
* @return a lexicographic-order comparator composed of this comparator
* and then comparing an extracted sort key using the specified sort key comparator.
* @throws NullPointerException if either argument is null.
*/
public default <K> IntComparator thenComparing(IntFunction<? extends K> keyExtractor, Comparator<? super K> keyComparator) {
return thenComparing(comparing(keyExtractor, keyComparator));
}
/**
* Returns a lexicographic-order comparator with a function that extracts a comparable sort key.
*
* <p>This default implementation delegates to
* <code>{@link #thenComparing(IntComparator) thenComparing}({@link #comparing(IntFunction) comparing}(keyExtractor))</code> expression.
*
* @param <K> the type of the comparable sort key.
* @param keyExtractor the function used to extract the comparable sort key.
* @return a lexicographic-order comparator composed of this comparator
* and then comparing an extracted comparable sort key.
* @throws NullPointerException if the argument is null.
*/
public default <K extends Comparable<? super K>> IntComparator thenComparing(IntFunction<? extends K> keyExtractor) {
return thenComparing(comparing(keyExtractor));
}
/**
* Returns a lexicographic-order comparator with a function that extracts an int sort key.
*
* <p>This default implementation delegates to
* <code>{@link #thenComparing(IntComparator) thenComparing}({@link #comparingInt(IntUnaryOperator) comparing}(keyExtractor))</code> expression.
*
* @param keyExtractor the function used to extract the int sort key.
* @return a lexicographic-order comparator composed of this comparator
* and then comparing an extracted int sort key.
* @throws NullPointerException if the argument is null.
*/
public default IntComparator thenComparingInt(IntUnaryOperator keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
/**
* Returns a lexicographic-order comparator with a function that extracts a long sort key.
*
* <p>This default implementation delegates to
* <code>{@link #thenComparing(IntComparator) thenComparing}({@link #comparingLong(IntToLongFunction) comparing}(keyExtractor))</code> expression.
*
* @param keyExtractor the function used to extract the long sort key.
* @return a lexicographic-order comparator composed of this comparator
* and then comparing an extracted long sort key.
* @throws NullPointerException if the argument is null.
*/
public default IntComparator thenComparingLong(IntToLongFunction keyExtractor) {
return thenComparing(comparingLong(keyExtractor));
}
/**
* Returns a lexicographic-order comparator with a function that extracts a double sort key.
*
* <p>This default implementation delegates to
* <code>{@link #thenComparing(IntComparator) thenComparing}({@link #comparingDouble(IntToDoubleFunction) comparing}(keyExtractor))</code> expression.
*
* @param keyExtractor the function used to extract the double sort key.
* @return a lexicographic-order comparator composed of this comparator
* and then comparing and extracted double sort key.
* @throws NullPointerException if the argument is null.
*/
public default IntComparator thenComparingDouble(IntToDoubleFunction keyExtractor) {
return thenComparing(comparingDouble(keyExtractor));
}
/**
* Accepts a function that extracts a sort key and a sort key comparator, and returns
* a comparator that compares by an extracted sort key using the specified sort key comparator.
*
* <p>The returned comparator is serializable if the specified function and comparator are both serializable.
*
* @param <K> the type of the sort key.
* @param keyExtractor the function used to extract the sort key.
* @param keyComparator the comparator used to compare the sort key.
* @return a comparator that compares by an extracted sort key using the specified sort key comparator.
* @throws NullPointerException if either argument is null.
*/
public static <K> IntComparator comparing(IntFunction<? extends K> keyExtractor, Comparator<? super K> keyComparator) {
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (IntComparator & Serializable)
(i1, i2) -> keyComparator.compare(keyExtractor.apply(i1), keyExtractor.apply(i2));
}
/**
* Accepts a function that extracts a comparable sort, and returns
* a comparator that compares by an extracted comparable sort key.
*
* <p>The returned comparator is serializable if the specified function is also serializable.
*
* @param <K> the type of the comparable sort key.
* @param keyExtractor the function used to extract the comparable sort key.
* @return a comparator that compares by an extracted comparable sort key.
* @throws NullPointerException if the argument is null.
*/
public static <K extends Comparable<? super K>> IntComparator comparing(IntFunction<? extends K> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (IntComparator & Serializable)
(i1, i2) -> keyExtractor.apply(i1).compareTo(keyExtractor.apply(i2));
}
/**
* Accepts a function that extracts an int sort key, and returns
* a comparator that compares by an extracted int sort key.
*
* <p>The returned comparator is serializable if the specified function is also serializable.
*
* @param keyExtractor the function used to extract the int sort key.
* @return a comparator that compares by an extracted int sort key.
* @throws NullPointerException if the argument is null.
*/
public static IntComparator comparingInt(IntUnaryOperator keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (IntComparator & Serializable)
(i1, i2) -> Integer.compare(keyExtractor.applyAsInt(i1), keyExtractor.applyAsInt(i2));
}
/**
* Accepts a function that extracts a long sort key, and returns
* a comparator that compares by an extracted long sort key.
*
* <p>The returned comparator is serializable if the specified function is also serializable.
*
* @param keyExtractor the function used to extract the long sort key.
* @return a comparator that compares by an extracted long sort key.
* @throws NullPointerException if the argument is null.
*/
public static IntComparator comparingLong(IntToLongFunction keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (IntComparator & Serializable)
(i1, i2) -> Long.compare(keyExtractor.applyAsLong(i1), keyExtractor.applyAsLong(i2));
}
/**
* Accepts a function that extracts a double sort key, and returns
* a comparator that compares by an extracted double sort key.
*
* <p>The returned comparator is serializable if the specified function is also serializable.
*
* @param keyExtractor the function used to extract the double sort key.
* @return a comparator that compares by an extracted double sort key.
* @throws NullPointerException if the argument is null.
*/
public static IntComparator comparingDouble(IntToDoubleFunction keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (IntComparator & Serializable)
(i1, i2) -> Double.compare(keyExtractor.applyAsDouble(i1), keyExtractor.applyAsDouble(i2));
}
}

View File

@ -0,0 +1,233 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.util;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.LongFunction;
import java.util.function.LongToDoubleFunction;
import java.util.function.LongToIntFunction;
import java.util.function.LongUnaryOperator;
/**
* A comparison function, which imposes a <i>total ordering</i> on some collection of longs.
* Comparators can be passed to a sort method (such as {@link QuickSort#sort(long[], LongComparator) QuickSort.sort})
* to allow precise control over the sort order.
*
* <p>The purpose of this function is to allow non-trivial ordering of longs which depend on some external data.
* For example when longs are some identifiers (pseudo-references) of actual data.
*/
@SuppressWarnings("UnusedDeclaration")
public interface LongComparator {
/**
* Compares its two arguments for order. Returns a negative integer, zero, or a positive integer
* as the first argument is less than, equal to, or greater than the second.
*
* @param i1 the first long to be compared.
* @param i2 the second long to be compared.
* @return a negative integer, zero, or a positive integer as the first argument is
* less than, equal to, or greater than the second.
*/
public int compare(long i1, long i2);
/**
* Returns a comparator that imposes the reverse ordering of this comparator.
*
* @return a comparator that imposes the reverse ordering of this comparator.
*/
public default LongComparator reversed() {
return (LongComparator & Serializable) (i1, i2) -> compare(i2, i1);
}
/**
* Returns a lexicographic-order comparator with another comparator.
* If this comparator considers two elements equal, i.e. {@code compare(i1, i2) == 0},
* then other comparator is used to determine the order.
*
* <p>The returned comparator is serializable if the specified comparator is also serializable.
*
* @param other the other comparator to be used when this comparator compares two longs that are equal.
* @return a lexicographic-order comparator composed of this comparator and then the other comparator.
* @throws NullPointerException if the argument is null.
*/
public default LongComparator thenComparing(LongComparator other) {
Objects.requireNonNull(other);
return (LongComparator & Serializable) (i1, i2) -> {
int res = compare(i1, i2);
return res != 0 ? res : other.compare(i1, i2);
};
}
/**
* Returns a lexicographic-order comparator with a function that extracts
* a sort key to be compared with the given sort key comparator.
*
* <p>This default implementation delegates to
* <code>{@link #thenComparing(LongComparator) thenComparing}({@link #comparing(LongFunction, Comparator) comparing}(keyExtractor,&nbsp;keyComparator))</code> expression.
*
* @param <K> the type of the sort key.
* @param keyExtractor the function used to extract the sort key.
* @param keyComparator the comparator used to compare the sort key.
* @return a lexicographic-order comparator composed of this comparator
* and then comparing an extracted sort key using the specified sort key comparator.
* @throws NullPointerException if either argument is null.
*/
public default <K> LongComparator thenComparing(LongFunction<? extends K> keyExtractor, Comparator<? super K> keyComparator) {
return thenComparing(comparing(keyExtractor, keyComparator));
}
/**
* Returns a lexicographic-order comparator with a function that extracts a comparable sort key.
*
* <p>This default implementation delegates to
* <code>{@link #thenComparing(LongComparator) thenComparing}({@link #comparing(LongFunction) comparing}(keyExtractor))</code> expression.
*
* @param <K> the type of the comparable sort key.
* @param keyExtractor the function used to extract the comparable sort key.
* @return a lexicographic-order comparator composed of this comparator
* and then comparing an extracted comparable sort key.
* @throws NullPointerException if the argument is null.
*/
public default <K extends Comparable<? super K>> LongComparator thenComparing(LongFunction<? extends K> keyExtractor) {
return thenComparing(comparing(keyExtractor));
}
/**
* Returns a lexicographic-order comparator with a function that extracts an int sort key.
*
* <p>This default implementation delegates to
* <code>{@link #thenComparing(LongComparator) thenComparing}({@link #comparingInt(LongToIntFunction) comparing}(keyExtractor))</code> expression.
*
* @param keyExtractor the function used to extract the int sort key.
* @return a lexicographic-order comparator composed of this comparator
* and then comparing an extracted int sort key.
* @throws NullPointerException if the argument is null.
*/
public default LongComparator thenComparingInt(LongToIntFunction keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
/**
* Returns a lexicographic-order comparator with a function that extracts a long sort key.
*
* <p>This default implementation delegates to
* <code>{@link #thenComparing(LongComparator) thenComparing}({@link #comparingLong(LongUnaryOperator) comparing}(keyExtractor))</code> expression.
*
* @param keyExtractor the function used to extract the long sort key.
* @return a lexicographic-order comparator composed of this comparator
* and then comparing an extracted long sort key.
* @throws NullPointerException if the argument is null.
*/
public default LongComparator thenComparingLong(LongUnaryOperator keyExtractor) {
return thenComparing(comparingLong(keyExtractor));
}
/**
* Returns a lexicographic-order comparator with a function that extracts a double sort key.
*
* <p>This default implementation delegates to
* <code>{@link #thenComparing(LongComparator) thenComparing}({@link #comparingDouble(LongToDoubleFunction) comparing}(keyExtractor))</code> expression.
*
* @param keyExtractor the function used to extract the double sort key.
* @return a lexicographic-order comparator composed of this comparator
* and then comparing and extracted double sort key.
* @throws NullPointerException if the argument is null.
*/
public default LongComparator thenComparingDouble(LongToDoubleFunction keyExtractor) {
return thenComparing(comparingDouble(keyExtractor));
}
/**
* Accepts a function that extracts a sort key and a sort key comparator, and returns
* a comparator that compares by an extracted sort key using the specified sort key comparator.
*
* <p>The returned comparator is serializable if the specified function and comparator are both serializable.
*
* @param <K> the type of the sort key.
* @param keyExtractor the function used to extract the sort key.
* @param keyComparator the comparator used to compare the sort key.
* @return a comparator that compares by an extracted sort key using the specified sort key comparator.
* @throws NullPointerException if either argument is null.
*/
public static <K> LongComparator comparing(LongFunction<? extends K> keyExtractor, Comparator<? super K> keyComparator) {
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (LongComparator & Serializable)
(i1, i2) -> keyComparator.compare(keyExtractor.apply(i1), keyExtractor.apply(i2));
}
/**
* Accepts a function that extracts a comparable sort, and returns
* a comparator that compares by an extracted comparable sort key.
*
* <p>The returned comparator is serializable if the specified function is also serializable.
*
* @param <K> the type of the comparable sort key.
* @param keyExtractor the function used to extract the comparable sort key.
* @return a comparator that compares by an extracted comparable sort key.
* @throws NullPointerException if the argument is null.
*/
public static <K extends Comparable<? super K>> LongComparator comparing(LongFunction<? extends K> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (LongComparator & Serializable)
(i1, i2) -> keyExtractor.apply(i1).compareTo(keyExtractor.apply(i2));
}
/**
* Accepts a function that extracts an int sort key, and returns
* a comparator that compares by an extracted int sort key.
*
* <p>The returned comparator is serializable if the specified function is also serializable.
*
* @param keyExtractor the function used to extract the int sort key.
* @return a comparator that compares by an extracted int sort key.
* @throws NullPointerException if the argument is null.
*/
public static LongComparator comparingInt(LongToIntFunction keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (LongComparator & Serializable)
(i1, i2) -> Integer.compare(keyExtractor.applyAsInt(i1), keyExtractor.applyAsInt(i2));
}
/**
* Accepts a function that extracts a long sort key, and returns
* a comparator that compares by an extracted long sort key.
*
* <p>The returned comparator is serializable if the specified function is also serializable.
*
* @param keyExtractor the function used to extract the long sort key.
* @return a comparator that compares by an extracted long sort key.
* @throws NullPointerException if the argument is null.
*/
public static LongComparator comparingLong(LongUnaryOperator keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (LongComparator & Serializable)
(i1, i2) -> Long.compare(keyExtractor.applyAsLong(i1), keyExtractor.applyAsLong(i2));
}
/**
* Accepts a function that extracts a double sort key, and returns
* a comparator that compares by an extracted double sort key.
*
* <p>The returned comparator is serializable if the specified function is also serializable.
*
* @param keyExtractor the function used to extract the double sort key.
* @return a comparator that compares by an extracted double sort key.
* @throws NullPointerException if the argument is null.
*/
public static LongComparator comparingDouble(LongToDoubleFunction keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (LongComparator & Serializable)
(i1, i2) -> Double.compare(keyExtractor.applyAsDouble(i1), keyExtractor.applyAsDouble(i2));
}
}

View File

@ -0,0 +1,690 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.util;
import java.util.Comparator;
import java.util.List;
/**
* This class implements modified version of <b>Quick Sort</b> algorithm.
* This implementation offers <b>O(n*log(n))</b> performance on many data sets
* that cause other quick sort algorithms to degrade to quadratic performance.
*
* <p>The notable differences of this <b>Quick Sort</b> from other sorting algorithms are:
* <ul>
* <li> it is <b>unstable</b> - it can re-arrange equal elements in any order;
* <li> it is <b>robust</b> - it can withstand <b>unstable ordering</b> of elements,
* for example if ordering changes during sorting;
* <li> it is <b>garbage-free</b> - it does not allocate any temporary objects.
* </ul>
*
* <p>In the case of <b>unstable ordering</b> the result of this algorithm is not necessarily fully sorted,
* but it is guaranteed to complete in a finite amount of time and without exceptions.
* The result in this case would be partially sorted to the best of algorithm's ability.
*/
public class QuickSort {
/**
* Sorts the specified list into ascending order according
* to the {@linkplain Comparable natural ordering} of its elements.
* All elements in the list must implement the {@link Comparable} interface.
* Furthermore, all elements in the list must be <i>mutually comparable</i>.
*
* @param <T> the class of the objects in the list.
* @param list the list to be sorted.
* @throws ClassCastException if the list contains elements that are not <i>mutually comparable</i>.
*/
public static <T extends Comparable<? super T>> void sort(List<T> list) {
quickSort(list, 0, list.size() - 1, null);
}
/**
* Sorts the specified range of the specified list into ascending order according to the <i>natural ordering</i> of its elements.
* The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive.
* All elements in this range must implement the {@link Comparable} interface.
* Furthermore, all elements in this range must be <i>mutually comparable</i>.
*
* @param <T> the class of the objects in the list.
* @param list the list to be sorted.
* @param fromIndex the index of the first element (inclusive) to be sorted.
* @param toIndex the index of the last element (exclusive) to be sorted.
* @throws IllegalArgumentException if {@code fromIndex > toIndex}.
* @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}.
* @throws ClassCastException if the list contains elements that are not <i>mutually comparable</i>.
*/
public static <T extends Comparable<? super T>> void sort(List<T> list, int fromIndex, int toIndex) {
rangeCheck(list.size(), fromIndex, toIndex);
quickSort(list, fromIndex, toIndex - 1, null);
}
/**
* Sorts the specified list according to the order induced by the specified comparator.
* All elements in the list must be <i>mutually comparable</i> using the specified comparator.
*
* @param <T> the class of the objects in the list.
* @param list the list to be sorted.
* @param c the comparator to determine the order of the list. A {@code null} value indicates
* that the elements' <i>natural ordering</i> should be used.
* @throws ClassCastException if the list contains elements that are not <i>mutually comparable</i>
* using the specified comparator.
*/
public static <T> void sort(List<T> list, Comparator<? super T> c) {
quickSort(list, 0, list.size() - 1, c);
}
/**
* Sorts the specified range of the specified list according to the order induced by the specified comparator.
* The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive.
* All elements in the range must be <i>mutually comparable</i> by the specified comparator.
*
* @param <T> the class of the objects in the list.
* @param list the list to be sorted.
* @param fromIndex the index of the first element (inclusive) to be sorted.
* @param toIndex the index of the last element (exclusive) to be sorted.
* @param c the comparator to determine the order of the list. A {@code null} value indicates
* that the elements' <i>natural ordering</i> should be used.
* @throws IllegalArgumentException if {@code fromIndex > toIndex}.
* @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}.
* @throws ClassCastException if the list contains elements that are not <i>mutually comparable</i>
* using the specified comparator.
*/
public static <T> void sort(List<T> list, int fromIndex, int toIndex, Comparator<? super T> c) {
rangeCheck(list.size(), fromIndex, toIndex);
quickSort(list, fromIndex, toIndex - 1, c);
}
/**
* Sorts the specified array of objects into ascending order according
* to the {@linkplain Comparable natural ordering} of its elements.
* All elements in the array must implement the {@link Comparable} interface.
* Furthermore, all elements in the array must be <i>mutually comparable</i>.
*
* @param a the array to be sorted.
* @throws ClassCastException if the array contains elements that are not <i>mutually comparable</i>.
*/
public static void sort(Object[] a) {
quickSort(a, 0, a.length - 1, null);
}
/**
* Sorts the specified range of the specified array of objects into ascending order
* according to the {@linkplain Comparable natural ordering} of its elements.
* The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive.
* All elements in this range must implement the {@link Comparable} interface.
* Furthermore, all elements in this range must be <i>mutually comparable</i>.
*
* @param a the array to be sorted.
* @param fromIndex the index of the first element (inclusive) to be sorted.
* @param toIndex the index of the last element (exclusive) to be sorted.
* @throws IllegalArgumentException if {@code fromIndex > toIndex}.
* @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}.
* @throws ClassCastException if the array contains elements that are not <i>mutually comparable</i>.
*/
public static void sort(Object[] a, int fromIndex, int toIndex) {
rangeCheck(a.length, fromIndex, toIndex);
quickSort(a, fromIndex, toIndex - 1, null);
}
/**
* Sorts the specified array of objects according to the order induced by the specified comparator.
* All elements in the array must be <i>mutually comparable</i> by the specified comparator.
*
* @param <T> the class of the objects to be sorted.
* @param a the array to be sorted.
* @param c the comparator to determine the order of the array. A {@code null} value indicates
* that the elements' {@linkplain Comparable natural ordering} should be used.
* @throws ClassCastException if the array contains elements that are not <i>mutually comparable</i>
* using the specified comparator.
*/
public static <T> void sort(T[] a, Comparator<? super T> c) {
quickSort(a, 0, a.length - 1, c);
}
/**
* Sorts the specified range of the specified array of objects according to the order induced by the specified comparator.
* The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive.
* All elements in the range must be <i>mutually comparable</i> by the specified comparator.
*
* @param <T> the class of the objects to be sorted.
* @param a the array to be sorted.
* @param fromIndex the index of the first element (inclusive) to be sorted.
* @param toIndex the index of the last element (exclusive) to be sorted.
* @param c the comparator to determine the order of the array. A {@code null} value indicates
* that the elements' {@linkplain Comparable natural ordering} should be used.
* @throws IllegalArgumentException if {@code fromIndex > toIndex}.
* @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}.
* @throws ClassCastException if the array contains elements that are not <i>mutually comparable</i>
* using the specified comparator.
*/
public static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c) {
rangeCheck(a.length, fromIndex, toIndex);
quickSort(a, fromIndex, toIndex - 1, c);
}
/**
* Sorts the specified array of ints according to the order induced by the specified comparator.
* All elements in the array must be <i>mutually comparable</i> by the specified comparator.
*
* @param a the array to be sorted.
* @param c the comparator to determine the order of the array.
*/
public static void sort(int[] a, IntComparator c) {
quickSort(a, 0, a.length - 1, c);
}
/**
* Sorts the specified range of the specified array of ints according to the order induced by the specified comparator.
* The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive.
* All elements in the range must be <i>mutually comparable</i> by the specified comparator.
*
* @param a the array to be sorted.
* @param fromIndex the index of the first element (inclusive) to be sorted.
* @param toIndex the index of the last element (exclusive) to be sorted.
* @param c the comparator to determine the order of the array.
* @throws IllegalArgumentException if {@code fromIndex > toIndex}.
* @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}.
*/
public static void sort(int[] a, int fromIndex, int toIndex, IntComparator c) {
rangeCheck(a.length, fromIndex, toIndex);
quickSort(a, fromIndex, toIndex - 1, c);
}
/**
* Sorts the specified array of longs according to the order induced by the specified comparator.
* All elements in the array must be <i>mutually comparable</i> by the specified comparator.
*
* @param a the array to be sorted.
* @param c the comparator to determine the order of the array.
*/
public static void sort(long[] a, LongComparator c) {
quickSort(a, 0, a.length - 1, c);
}
/**
* Sorts the specified range of the specified array of longs according to the order induced by the specified comparator.
* The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive.
* All elements in the range must be <i>mutually comparable</i> by the specified comparator.
*
* @param a the array to be sorted.
* @param fromIndex the index of the first element (inclusive) to be sorted.
* @param toIndex the index of the last element (exclusive) to be sorted.
* @param c the comparator to determine the order of the array.
* @throws IllegalArgumentException if {@code fromIndex > toIndex}.
* @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}.
*/
public static void sort(long[] a, int fromIndex, int toIndex, LongComparator c) {
rangeCheck(a.length, fromIndex, toIndex);
quickSort(a, fromIndex, toIndex - 1, c);
}
// ========== Quick Sort of x[lo..hi] (inclusive) ==========
private static final int BINARY_INSERT_LIST = 20;
private static final int BINARY_INSERT_ARRAY = 40;
private static final int MOM_START = 400;
private static final int MOM_BASE = 15;
static {
//noinspection ConstantConditions,PointlessBooleanExpression
if (BINARY_INSERT_LIST < 4 || BINARY_INSERT_ARRAY < 4 || MOM_START < 25 || MOM_BASE < 5)
throw new AssertionError("invalid sort constants");
}
private static <T> void quickSort(List<T> x, int lo, int hi, Comparator<? super T> comparator) {
// Quick sort large ranges in a loop to retain stack depth at log(n).
while (hi - lo > BINARY_INSERT_LIST) {
T pivot;
int loScan;
int hiScan;
if (hi - lo > MOM_START) {
// Range is large - perform median-of-medians search of good pivot.
pivot = x.get(medianOfMedians(comparator, x, momStep(lo, hi), lo, hi));
loScan = lo;
hiScan = hi;
} else {
// Range is small - perform median-of-five search of good pivot.
pivot = x.get(medianOfFive(comparator, x, lo, lo + 1, (lo + hi) >>> 1, hi - 1, hi));
// Median-of-five rearranges elements around pivot - skip comparisons of 4 outer elements.
loScan = lo + 2;
hiScan = hi - 2;
}
// Excessive checks (loScan <= hiScan) protect from IndexOutOfBoundsException due to unstable ordering.
while (loScan <= hiScan) {
while (loScan <= hiScan && compare(x.get(loScan), pivot, comparator) < 0)
loScan++;
while (loScan <= hiScan && compare(x.get(hiScan), pivot, comparator) > 0)
hiScan--;
if (loScan > hiScan)
break;
T tmp = x.get(loScan);
x.set(loScan, x.get(hiScan));
x.set(hiScan, tmp);
loScan++;
hiScan--;
}
// Do recursion into smaller range, then do larger range ourselves.
if (hiScan - lo < hi - loScan) {
quickSort(x, lo, hiScan, comparator);
// Protect from degenerate partition when (loScan == lo) due to unstable ordering.
lo = Math.max(loScan, lo + 1);
} else {
quickSort(x, loScan, hi, comparator);
// Protect from degenerate partition when (hiScan == hi) due to unstable ordering.
hi = Math.min(hiScan, hi - 1);
}
}
// Binary insertion sort the remaining small range.
binaryInsertionSort(x, lo, hi, comparator);
}
private static <T> void quickSort(T[] x, int lo, int hi, Comparator<? super T> comparator) {
while (hi - lo > BINARY_INSERT_ARRAY) {
T pivot;
int loScan;
int hiScan;
if (hi - lo > MOM_START) {
pivot = x[medianOfMedians(comparator, x, momStep(lo, hi), lo, hi)];
loScan = lo;
hiScan = hi;
} else {
pivot = x[medianOfFive(comparator, x, lo, lo + 1, (lo + hi) >>> 1, hi - 1, hi)];
loScan = lo + 2;
hiScan = hi - 2;
}
while (loScan <= hiScan) {
while (loScan <= hiScan && compare(x[loScan], pivot, comparator) < 0)
loScan++;
while (loScan <= hiScan && compare(x[hiScan], pivot, comparator) > 0)
hiScan--;
if (loScan > hiScan)
break;
T tmp = x[loScan];
x[loScan] = x[hiScan];
x[hiScan] = tmp;
loScan++;
hiScan--;
}
if (hiScan - lo < hi - loScan) {
quickSort(x, lo, hiScan, comparator);
lo = Math.max(loScan, lo + 1);
} else {
quickSort(x, loScan, hi, comparator);
hi = Math.min(hiScan, hi - 1);
}
}
binaryInsertionSort(x, lo, hi, comparator);
}
private static void quickSort(int[] x, int lo, int hi, IntComparator comparator) {
while (hi - lo > BINARY_INSERT_ARRAY) {
int pivot;
int loScan;
int hiScan;
if (hi - lo > MOM_START) {
pivot = x[medianOfMedians(comparator, x, momStep(lo, hi), lo, hi)];
loScan = lo;
hiScan = hi;
} else {
pivot = x[medianOfFive(comparator, x, lo, lo + 1, (lo + hi) >>> 1, hi - 1, hi)];
loScan = lo + 2;
hiScan = hi - 2;
}
while (loScan <= hiScan) {
while (loScan <= hiScan && compare(x[loScan], pivot, comparator) < 0)
loScan++;
while (loScan <= hiScan && compare(x[hiScan], pivot, comparator) > 0)
hiScan--;
if (loScan > hiScan)
break;
int tmp = x[loScan];
x[loScan] = x[hiScan];
x[hiScan] = tmp;
loScan++;
hiScan--;
}
if (hiScan - lo < hi - loScan) {
quickSort(x, lo, hiScan, comparator);
lo = Math.max(loScan, lo + 1);
} else {
quickSort(x, loScan, hi, comparator);
hi = Math.min(hiScan, hi - 1);
}
}
binaryInsertionSort(x, lo, hi, comparator);
}
private static void quickSort(long[] x, int lo, int hi, LongComparator comparator) {
while (hi - lo > BINARY_INSERT_ARRAY) {
long pivot;
int loScan;
int hiScan;
if (hi - lo > MOM_START) {
pivot = x[medianOfMedians(comparator, x, momStep(lo, hi), lo, hi)];
loScan = lo;
hiScan = hi;
} else {
pivot = x[medianOfFive(comparator, x, lo, lo + 1, (lo + hi) >>> 1, hi - 1, hi)];
loScan = lo + 2;
hiScan = hi - 2;
}
while (loScan <= hiScan) {
while (loScan <= hiScan && compare(x[loScan], pivot, comparator) < 0)
loScan++;
while (loScan <= hiScan && compare(x[hiScan], pivot, comparator) > 0)
hiScan--;
if (loScan > hiScan)
break;
long tmp = x[loScan];
x[loScan] = x[hiScan];
x[hiScan] = tmp;
loScan++;
hiScan--;
}
if (hiScan - lo < hi - loScan) {
quickSort(x, lo, hiScan, comparator);
lo = Math.max(loScan, lo + 1);
} else {
quickSort(x, loScan, hi, comparator);
hi = Math.min(hiScan, hi - 1);
}
}
binaryInsertionSort(x, lo, hi, comparator);
}
// ========== Binary Insertion Sort of x[lo..hi] (inclusive) ==========
private static <T> void binaryInsertionSort(List<T> x, int lo, int hi, Comparator<? super T> comparator) {
for (int i = lo; ++i <= hi;) {
T pivot = x.get(i);
int left = lo;
for (int right = i; left < right;) {
int mid = (left + right) >>> 1;
if (compare(pivot, x.get(mid), comparator) < 0)
right = mid;
else
left = mid + 1;
}
if (left < i) {
for (int k = i; k > left; k--)
x.set(k, x.get(k - 1));
x.set(left, pivot);
}
}
}
private static <T> void binaryInsertionSort(T[] x, int lo, int hi, Comparator<? super T> comparator) {
for (int i = lo; ++i <= hi;) {
T pivot = x[i];
int left = lo;
for (int right = i; left < right;) {
int mid = (left + right) >>> 1;
if (compare(pivot, x[mid], comparator) < 0)
right = mid;
else
left = mid + 1;
}
if (left < i) {
System.arraycopy(x, left, x, left + 1, i - left);
x[left] = pivot;
}
}
}
private static void binaryInsertionSort(int[] x, int lo, int hi, IntComparator comparator) {
for (int i = lo; ++i <= hi;) {
int pivot = x[i];
int left = lo;
for (int right = i; left < right;) {
int mid = (left + right) >>> 1;
if (compare(pivot, x[mid], comparator) < 0)
right = mid;
else
left = mid + 1;
}
if (left < i) {
System.arraycopy(x, left, x, left + 1, i - left);
x[left] = pivot;
}
}
}
private static void binaryInsertionSort(long[] x, int lo, int hi, LongComparator comparator) {
for (int i = lo; ++i <= hi;) {
long pivot = x[i];
int left = lo;
for (int right = i; left < right;) {
int mid = (left + right) >>> 1;
if (compare(pivot, x[mid], comparator) < 0)
right = mid;
else
left = mid + 1;
}
if (left < i) {
System.arraycopy(x, left, x, left + 1, i - left);
x[left] = pivot;
}
}
}
// ========== Median Of Medians ==========
// Finds median of medians using quinary tree and median of five in each node.
// Expected number of used elements is pow(5, 1 + ceil(log(1 + size / MOM_START, MOM_BASE))).
// All used elements are spaced evenly (as much as possible) using "step" step.
private static int momStep(int lo, int hi) {
int mult = 5;
for (int k = (int)((hi - lo + 1L) / MOM_START); k > 0; k /= MOM_BASE)
mult *= 5;
while (hi - lo < mult - 1 && mult > 5)
mult /= 5;
return (hi - lo) / (mult - 1);
}
private static <T> int medianOfMedians(Comparator<? super T> comparator, List<T> x, int step, int lo, int hi) {
int ns = (hi - lo - step * 4) / 5;
if (ns < step * 4)
return medianOfFive(comparator, x, lo, lo + step, (lo + hi) >>> 1, hi - step, hi);
int bs = ns + step;
return medianOfFive(comparator, x,
medianOfMedians(comparator, x, step, lo, lo + ns),
medianOfMedians(comparator, x, step, lo + bs, lo + bs + ns),
medianOfMedians(comparator, x, step, lo + bs + bs, hi - bs - bs),
medianOfMedians(comparator, x, step, hi - bs - ns, hi - bs),
medianOfMedians(comparator, x, step, hi - ns, hi)
);
}
private static <T> int medianOfMedians(Comparator<? super T> comparator, T[] x, int step, int lo, int hi) {
int ns = (hi - lo - step * 4) / 5;
if (ns < step * 4)
return medianOfFive(comparator, x, lo, lo + step, (lo + hi) >>> 1, hi - step, hi);
int bs = ns + step;
return medianOfFive(comparator, x,
medianOfMedians(comparator, x, step, lo, lo + ns),
medianOfMedians(comparator, x, step, lo + bs, lo + bs + ns),
medianOfMedians(comparator, x, step, lo + bs + bs, hi - bs - bs),
medianOfMedians(comparator, x, step, hi - bs - ns, hi - bs),
medianOfMedians(comparator, x, step, hi - ns, hi)
);
}
private static int medianOfMedians(IntComparator comparator, int[] x, int step, int lo, int hi) {
int ns = (hi - lo - step * 4) / 5;
if (ns < step * 4)
return medianOfFive(comparator, x, lo, lo + step, (lo + hi) >>> 1, hi - step, hi);
int bs = ns + step;
return medianOfFive(comparator, x,
medianOfMedians(comparator, x, step, lo, lo + ns),
medianOfMedians(comparator, x, step, lo + bs, lo + bs + ns),
medianOfMedians(comparator, x, step, lo + bs + bs, hi - bs - bs),
medianOfMedians(comparator, x, step, hi - bs - ns, hi - bs),
medianOfMedians(comparator, x, step, hi - ns, hi)
);
}
private static int medianOfMedians(LongComparator comparator, long[] x, int step, int lo, int hi) {
int ns = (hi - lo - step * 4) / 5;
if (ns < step * 4)
return medianOfFive(comparator, x, lo, lo + step, (lo + hi) >>> 1, hi - step, hi);
int bs = ns + step;
return medianOfFive(comparator, x,
medianOfMedians(comparator, x, step, lo, lo + ns),
medianOfMedians(comparator, x, step, lo + bs, lo + bs + ns),
medianOfMedians(comparator, x, step, lo + bs + bs, hi - bs - bs),
medianOfMedians(comparator, x, step, hi - bs - ns, hi - bs),
medianOfMedians(comparator, x, step, hi - ns, hi)
);
}
// ========== Median Of Five ==========
// Finds median of 5 elements using 6 comparisons. See first method for algorithm explanation.
// All methods do reorder their input around median, thus performing partial sorting.
// This side effect is used by quick sort algorithms to skip comparisons of 4 outer elements.
// This side effect is useless for median of medians algorithms, but by using same methods we save on bytecode.
private static <T> int medianOfFive(Comparator<? super T> comparator, List<T> x, int ai, int bi, int ci, int di, int ei) {
T a = x.get(ai);
T b = x.get(bi);
T c = x.get(ci);
T d = x.get(di);
T e = x.get(ei);
T t;
// (a, b, c, d, e) - sort (a, b)
if (compare(a, b, comparator) > 0) { t = a; a = b; b = t; }
// (a < b, c, d, e) - sort (d, e)
if (compare(d, e, comparator) > 0) { t = d; d = e; e = t; }
// (a < b, c, d < e) - sort pairs (a < b, d < e) by lowest of (a, d)
if (compare(a, d, comparator) > 0) { t = a; a = d; d = t; t = b; b = e; e = t; }
// (a < b, c, a < d < e) - now [a] < [b, d, e], put it aside
// [a] < [b, d, e] (b, c, d < e) - sort (b, c)
if (compare(b, c, comparator) > 0) { t = b; b = c; c = t; }
// [a] < [c, d, e] (b < c, d < e) - sort pairs (b < c, d < e) by lowest of (b, d)
if (compare(b, d, comparator) > 0) { t = b; b = d; d = t; t = c; c = e; e = t; }
// [a] < [c, d, e] (b < c, b < d < e) - now [b] < [c, d, e], put it aside
// [a, b] < [c, d, e] (c, d < e) - sort (c, d)
if (compare(c, d, comparator) > 0) { t = c; c = d; d = t; }
// [a, b] < [c, d, e] (c < d, c < e) - now [c] < [d, e], rewrite
// [a, b] < [c] < [d, e] - [c] is a median
x.set(ai, a);
x.set(bi, b);
x.set(ci, c);
x.set(di, d);
x.set(ei, e);
return ci;
}
private static <T> int medianOfFive(Comparator<? super T> comparator, T[] x, int ai, int bi, int ci, int di, int ei) {
T a = x[ai];
T b = x[bi];
T c = x[ci];
T d = x[di];
T e = x[ei];
T t;
if (compare(a, b, comparator) > 0) { t = a; a = b; b = t; }
if (compare(d, e, comparator) > 0) { t = d; d = e; e = t; }
if (compare(a, d, comparator) > 0) { t = a; a = d; d = t; t = b; b = e; e = t; }
if (compare(b, c, comparator) > 0) { t = b; b = c; c = t; }
if (compare(b, d, comparator) > 0) { t = b; b = d; d = t; t = c; c = e; e = t; }
if (compare(c, d, comparator) > 0) { t = c; c = d; d = t; }
x[ai] = a;
x[bi] = b;
x[ci] = c;
x[di] = d;
x[ei] = e;
return ci;
}
private static int medianOfFive(IntComparator comparator, int[] x, int ai, int bi, int ci, int di, int ei) {
int a = x[ai];
int b = x[bi];
int c = x[ci];
int d = x[di];
int e = x[ei];
int t;
if (compare(a, b, comparator) > 0) { t = a; a = b; b = t; }
if (compare(d, e, comparator) > 0) { t = d; d = e; e = t; }
if (compare(a, d, comparator) > 0) { t = a; a = d; d = t; t = b; b = e; e = t; }
if (compare(b, c, comparator) > 0) { t = b; b = c; c = t; }
if (compare(b, d, comparator) > 0) { t = b; b = d; d = t; t = c; c = e; e = t; }
if (compare(c, d, comparator) > 0) { t = c; c = d; d = t; }
x[ai] = a;
x[bi] = b;
x[ci] = c;
x[di] = d;
x[ei] = e;
return ci;
}
private static int medianOfFive(LongComparator comparator, long[] x, int ai, int bi, int ci, int di, int ei) {
long a = x[ai];
long b = x[bi];
long c = x[ci];
long d = x[di];
long e = x[ei];
long t;
if (compare(a, b, comparator) > 0) { t = a; a = b; b = t; }
if (compare(d, e, comparator) > 0) { t = d; d = e; e = t; }
if (compare(a, d, comparator) > 0) { t = a; a = d; d = t; t = b; b = e; e = t; }
if (compare(b, c, comparator) > 0) { t = b; b = c; c = t; }
if (compare(b, d, comparator) > 0) { t = b; b = d; d = t; t = c; c = e; e = t; }
if (compare(c, d, comparator) > 0) { t = c; c = d; d = t; }
x[ai] = a;
x[bi] = b;
x[ci] = c;
x[di] = d;
x[ei] = e;
return ci;
}
// ========== Utility Code ==========
/**
* Compares specified objects using either specified comparator or their natural ordering.
*/
@SuppressWarnings("unchecked")
private static int compare(Object o1, Object o2, Comparator c) {
// Boost performance and protect from degenerate partition due to unstable ordering.
if (o1 == o2)
return 0;
return c != null ? c.compare(o1, o2) : ((Comparable)o1).compareTo(o2);
}
private static int compare(int i1, int i2, IntComparator c) {
// Boost performance and protect from degenerate partition due to unstable ordering.
if (i1 == i2)
return 0;
return c.compare(i1, i2);
}
private static int compare(long i1, long i2, LongComparator c) {
// Boost performance and protect from degenerate partition due to unstable ordering.
if (i1 == i2)
return 0;
return c.compare(i1, i2);
}
/**
* Checks that fromIndex and toIndex are in range and throws appropriate exception if they aren't.
*/
private static void rangeCheck(int length, int fromIndex, int toIndex) {
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex " + fromIndex + " > toIndex " + toIndex);
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex " + fromIndex + " < 0");
if (toIndex > length)
throw new IndexOutOfBoundsException("toIndex " + toIndex + " > length " + length);
}
/**
* Private constructor to prevent instantiation.
*/
private QuickSort() {}
}

View File

@ -0,0 +1,452 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.util;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collector;
/**
* A synchronized thread-safe version of {@link IndexedSet} class.
* It provides following benefits over standard {@link IndexedSet}:
*
* <ul>
* <li> concurrent asynchronous read access
* <li> synchronized thread-safe write access
* <li> all iterators are concurrent
* </ul>
*
* <p>Note that <b>SynchronizedIndexedSet</b> can be wrapped by {@link IndexedMap}
* to create what can be considered a <b>SynchronizedIndexedMap</b>.
*/
public class SynchronizedIndexedSet<K, V> extends IndexedSet<K, V> {
private static final long serialVersionUID = 0;
// ========== static factory methods ===========
/**
* Creates new empty set with default indexer {@link IndexerFunction#DEFAULT}.
*/
public static <V> SynchronizedIndexedSet<V, V> create() {
return new SynchronizedIndexedSet<>();
}
/**
* Creates new empty set with default identity indexer.
*/
public static <V> SynchronizedIndexedSet<V, V> createIdentity() {
return new SynchronizedIndexedSet<>((IndexerFunction.IdentityKey<V, V>)(v -> v));
}
/**
* Creates new empty set with specified indexer.
*/
public static <K, V> SynchronizedIndexedSet<K, V> create(IndexerFunction<K, ? super V> indexer) {
return new SynchronizedIndexedSet<>(indexer);
}
/**
* Creates new empty set with specified identity indexer.
*/
public static <K, V> SynchronizedIndexedSet<K, V> createIdentity(IndexerFunction.IdentityKey<K, ? super V> indexer) {
return new SynchronizedIndexedSet<>(indexer);
}
/**
* Creates new empty set with specified int indexer.
*/
public static <V> SynchronizedIndexedSet<Integer, V> createInt(IndexerFunction.IntKey<? super V> indexer) {
return new SynchronizedIndexedSet<>(indexer);
}
/**
* Creates new empty set with specified long indexer.
*/
public static <V> SynchronizedIndexedSet<Long, V> createLong(IndexerFunction.LongKey<? super V> indexer) {
return new SynchronizedIndexedSet<>(indexer);
}
/**
* Creates new empty set with specified indexer.
*
* @deprecated Use {@link #createInt(IndexerFunction.IntKey) createInt(indexer)}
*/
@Deprecated
public static <V> SynchronizedIndexedSet<Integer, V> create(IndexerFunction.IntKey<? super V> indexer) {
return new SynchronizedIndexedSet<>(indexer);
}
/**
* Creates new empty set with specified indexer.
*
* @deprecated Use {@link #createLong(IndexerFunction.LongKey) createLong(indexer)}
*/
@Deprecated
public static <V> SynchronizedIndexedSet<Long, V> create(IndexerFunction.LongKey<? super V> indexer) {
return new SynchronizedIndexedSet<>(indexer);
}
/**
* Creates new empty set with specified indexer and specified initial capacity.
*
* @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)}
*/
@Deprecated
public static <K, V> SynchronizedIndexedSet<K, V> create(IndexerFunction<K, ? super V> indexer, int initialCapacity) {
return new SynchronizedIndexedSet<>(indexer, initialCapacity);
}
/**
* Creates new empty set with specified indexer and specified initial capacity.
*
* @deprecated Use {@link #createInt(IndexerFunction.IntKey) createInt(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)}
*/
@Deprecated
public static <V> SynchronizedIndexedSet<Integer, V> create(IndexerFunction.IntKey<? super V> indexer, int initialCapacity) {
return new SynchronizedIndexedSet<>(indexer, initialCapacity);
}
/**
* Creates new empty set with specified indexer and specified initial capacity.
*
* @deprecated Use {@link #createLong(IndexerFunction.LongKey) createLong(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)}
*/
@Deprecated
public static <V> SynchronizedIndexedSet<Long, V> create(IndexerFunction.LongKey<? super V> indexer, int initialCapacity) {
return new SynchronizedIndexedSet<>(indexer, initialCapacity);
}
/**
* Creates a new set with specified indexer containing the elements in the specified collection.
*
* @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withElements(Collection) withElements(c)}
*/
@Deprecated
public static <K, V> SynchronizedIndexedSet<K, V> create(IndexerFunction<K, ? super V> indexer, Collection<? extends V> c) {
return new SynchronizedIndexedSet<>(indexer, c);
}
/**
* Creates a new set with specified indexer containing the elements in the specified collection.
*
* @deprecated Use {@link #createInt(IndexerFunction.IntKey) createInt(indexer)}.{@link #withElements(Collection) withElements(c)}
*/
@Deprecated
public static <V> SynchronizedIndexedSet<Integer, V> create(IndexerFunction.IntKey<? super V> indexer, Collection<? extends V> c) {
return new SynchronizedIndexedSet<>(indexer, c);
}
/**
* Creates a new set with default indexer containing specified elements.
*/
@SafeVarargs
public static <V> SynchronizedIndexedSet<V, V> of(V... objs) {
return new SynchronizedIndexedSet<>(Arrays.asList(objs));
}
/**
* Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with default indexer.
* This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector.
*/
@SuppressWarnings("unchecked")
public static <V> Collector<V, ?, ? extends SynchronizedIndexedSet<V, V>> collector() {
return collector((IndexerFunction<V, ? super V>)IndexerFunction.DEFAULT);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with default identity indexer.
* This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector.
*/
public static <V> Collector<V, ?, ? extends SynchronizedIndexedSet<V, V>> collectorIdentity() {
return collector((IndexerFunction.IdentityKey<V, V>)(v -> v));
}
/**
* Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified indexer.
* This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector.
*/
public static <K, V> Collector<V, ?, ? extends SynchronizedIndexedSet<K, V>> collector(IndexerFunction<K, ? super V> indexer) {
return Collector.of(() -> create(indexer), IndexedSet::add,
(left, right) -> { left.addAll(right); return left; },
Collector.Characteristics.CONCURRENT, Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified identity indexer.
* This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector.
*/
public static <K, V> Collector<V, ?, ? extends SynchronizedIndexedSet<K, V>> collectorIdentity(IndexerFunction.IdentityKey<K, ? super V> indexer) {
return collector((IndexerFunction<K, ? super V>)indexer);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified int indexer.
* This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector.
*/
public static <V> Collector<V, ?, ? extends SynchronizedIndexedSet<Integer, V>> collectorInt(IndexerFunction.IntKey<? super V> indexer) {
return collector((IndexerFunction<Integer, ? super V>)indexer);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified long indexer.
* This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector.
*/
public static <V> Collector<V, ?, ? extends SynchronizedIndexedSet<Long, V>> collectorLong(IndexerFunction.LongKey<? super V> indexer) {
return collector((IndexerFunction<Long, ? super V>)indexer);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified indexer.
* This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector.
*
* @deprecated Use {@link #collectorInt(IndexerFunction.IntKey) collectorInt(indexer)}
*/
@Deprecated
public static <V> Collector<V, ?, ? extends SynchronizedIndexedSet<Integer, V>> collector(IndexerFunction.IntKey<? super V> indexer) {
return collector((IndexerFunction<Integer, ? super V>)indexer);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified indexer.
* This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector.
*
* @deprecated Use {@link #collectorLong(IndexerFunction.LongKey) collectorLong(indexer)}
*/
@Deprecated
public static <V> Collector<V, ?, ? extends SynchronizedIndexedSet<Long, V>> collector(IndexerFunction.LongKey<? super V> indexer) {
return collector((IndexerFunction<Long, ? super V>)indexer);
}
// ========== Construction and Sizing Operations ==========
/**
* Creates new empty set with default indexer {@link IndexerFunction#DEFAULT}.
*/
public SynchronizedIndexedSet() {
super();
}
/**
* Creates new empty set with default indexer {@link IndexerFunction#DEFAULT} and specified initial capacity.
*/
public SynchronizedIndexedSet(int initialCapacity) {
super(initialCapacity);
}
/**
* Creates new empty set with specified indexer.
*/
protected SynchronizedIndexedSet(IndexerFunction<K, ? super V> indexer) {
super(indexer);
}
/**
* Creates new empty set with specified indexer.
*
* @deprecated Use {@link #create(IndexerFunction) create(indexer)}
*/
@Deprecated
public SynchronizedIndexedSet(Indexer<K, ? super V> indexer) {
super(indexer);
}
/**
* Creates new empty set with specified indexer and specified initial capacity.
*/
protected SynchronizedIndexedSet(IndexerFunction<K, ? super V> indexer, int initialCapacity) {
super(indexer, initialCapacity);
}
/**
* Creates new empty set with specified indexer and specified initial capacity.
*
* @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)}
*/
@Deprecated
public SynchronizedIndexedSet(Indexer<K, ? super V> indexer, int initialCapacity) {
super(indexer, initialCapacity);
}
/**
* Creates a new set containing the elements in the specified collection.
* If specified collection is an {@link IndexedSet}, then new indexed set uses same indexer,
* otherwise it uses default indexer {@link IndexerFunction#DEFAULT}.
*/
public SynchronizedIndexedSet(Collection<V> c) {
super(c);
}
/**
* Creates a new set with specified indexer containing the elements in the specified collection.
*/
protected SynchronizedIndexedSet(IndexerFunction<K, ? super V> indexer, Collection<? extends V> c) {
super(indexer, c);
}
/**
* Creates a new set with specified indexer containing the elements in the specified collection.
*
* @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withElements(Collection) withElements(c)}
*/
@Deprecated
public SynchronizedIndexedSet(Indexer<K, ? super V> indexer, Collection<? extends V> c) {
super(indexer, c);
}
/**
* Returns a shallow copy of this set - the values themselves are not cloned.
*/
@Override
public synchronized SynchronizedIndexedSet<K, V> clone() {
return (SynchronizedIndexedSet<K, V>)super.clone();
}
/**
* Increases the capacity of this set instance, if necessary, to ensure that it
* can hold at least the number of elements specified by the capacity argument.
* <p>
* Returns <b>this</b> set instance for convenience.
*/
@Override
public synchronized SynchronizedIndexedSet<K, V> withCapacity(int capacity) {
return (SynchronizedIndexedSet<K, V>)super.withCapacity(capacity);
}
/**
* Adds all of the elements in the specified collection into this set.
* <p>
* Returns <b>this</b> set instance for convenience.
*/
@Override
public synchronized SynchronizedIndexedSet<K, V> withElements(Collection<? extends V> c) {
return (SynchronizedIndexedSet<K, V>)super.withElements(c);
}
/**
* Increases the capacity of this set instance, if necessary, to ensure that it
* can hold at least the number of elements specified by the capacity argument.
*/
@Override
public synchronized void ensureCapacity(int capacity) {
super.ensureCapacity(capacity);
}
/**
* Trims the capacity of this set instance to be the set's current size.
* An application can use this operation to minimize the storage of this set instance.
*/
@Override
public synchronized void trimToSize() {
super.trimToSize();
}
/**
* Removes all elements from this set.
*/
@Override
public synchronized void clear() {
super.clear();
}
// ========== Query Operations ==========
/**
* Returns static structure statistics of this set.
*/
@Override
public synchronized IndexedSetStats getStats() {
// This method is synchronized to provide consistent view of several cross-linked variables.
// It should not pose any contention risk anyway.
return super.getStats();
}
// ========== Modification Operations ==========
/**
* Puts specified element into this set and returns previous element that matches specified one.
*/
@Override
public synchronized V put(V value) {
return super.put(value);
}
/**
* Puts specified element into this set if it is absent and
* returns current element in the set that matches specified one.
* This is equivalent to
* <pre>
* if (set.containsValue(value)) {
* return set.getByValue(value);
* } else {
* set.put(value);
* return value;
* }
* </pre>
* except that the action is performed atomically if it is properly synchronized.
* <p>
* Note, that unlike {@link ConcurrentMap#putIfAbsent},
* this method returns specified value (not <b>null</b>) if the value was absent.
*/
@Override
public synchronized V putIfAbsentAndGet(V value) {
return super.putIfAbsentAndGet(value);
}
/**
* Removes the element from this set which matches specified value if it is present
* and returns removed element or <b>null</b> if none were found.
*/
@Override
public synchronized V removeValue(V value) {
return super.removeValue(value);
}
/**
* Removes the element from this set which matches specified key if it is present
* and returns removed element or <b>null</b> if none were found.
*/
@Override
public synchronized V removeKey(K key) {
return super.removeKey(key);
}
/**
* Removes the element from this set which matches specified key if it is present
* and returns removed element or <b>null</b> if none were found.
*/
@Override
public synchronized V removeKey(long key) {
return super.removeKey(key);
}
// ========== Internal Implementation - Helper Instance Methods ==========
@Override
void checkModification(Object checkCore, long checkModCount) {
// Do nothing - all iterators are concurrent.
}
@Override
synchronized void removeIterated(Object checkCore, long checkModCount, boolean concurrent, V lastValue, int lastIndex) {
super.removeIterated(checkCore, checkModCount, true, lastValue, lastIndex);
}
@Override
synchronized void writeCore(ObjectOutputStream out) throws IOException {
// This method is synchronized to provide consistent serialization.
// It should not pose any contention risk anyway.
super.writeCore(out);
}
}

View File

@ -0,0 +1,65 @@
/*
* !++
* QDS - Quick Data Signalling Library
* !-
* Copyright (C) 2002 - 2020 Devexperts LLC
* !-
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
* !__
*/
package com.devexperts.util;
/**
* A collection of static utility methods for manipulation of Java long time.
* @see System#currentTimeMillis()
*/
public class TimeUtil {
private TimeUtil() {} // do not create this class
/**
* Number of milliseconds in a second.
*/
public static final long SECOND = 1000;
/**
* Number of milliseconds in a minute.
*/
public static final long MINUTE = 60 * SECOND;
/**
* Number of milliseconds in an hour.
*/
public static final long HOUR = 60 * MINUTE;
/**
* Number of milliseconds in an day.
*/
public static final long DAY = 24 * HOUR;
/**
* Returns correct number of seconds with proper handling negative values and overflows.
* Idea is that number of milliseconds shall be within [0..999] interval
* so that the following equation always holds
* {@code getSecondsFromTime(timeMillis) * 1000L + getMillisFromTime(timeMillis) == timeMillis}
* as as long the time in seconds fits into <b>int</b>.
* @see #getMillisFromTime(long)
*/
public static int getSecondsFromTime(long timeMillis) {
return timeMillis >= 0 ? (int)Math.min(timeMillis / SECOND, Integer.MAX_VALUE) :
(int)Math.max((timeMillis + 1) / SECOND - 1, Integer.MIN_VALUE);
}
/**
* Returns correct number of milliseconds with proper handling negative values.
* Idea is that number of milliseconds shall be within [0..999] interval
* so that the following equation always holds
* {@code getSecondsFromTime(timeMillis) * 1000L + getMillisFromTime(timeMillis) == timeMillis}
* as as long the time in seconds fits into <b>int</b>.
* @see #getSecondsFromTime(long)
*/
public static int getMillisFromTime(long timeMillis) {
return (int)Math.floorMod(timeMillis, SECOND);
}
}