getting on the needle
This commit is contained in:
parent
260c7a7786
commit
e1aa86a640
|
@ -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'
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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 == 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 == 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 == 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 == null ? 0 : 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 == null ? {@link #getObjectKey(Object) getObjectKey}(value) == null : 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), 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 == {@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) == {@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 == {@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) == {@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 == {@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<Key, Value> set = IndexedSet.{@link IndexedSet#create(IndexerFunction) create}((IndexerFunction.IdentityKey<Key, Value>)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 == {@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 == 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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, 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));
|
||||
}
|
||||
}
|
|
@ -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, 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));
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue