From 2d4f50912057b377963e08eb687a79b9dc528992 Mon Sep 17 00:00:00 2001 From: Alex Karpovich Date: Thu, 24 Jul 2025 17:17:37 +0300 Subject: [PATCH 1/4] [*] merge recent changes --- .../com/epam/deltix/util/lang/MathUtil.java | 18 +- .../epam/deltix/util/lang/StringUtils.java | 32 +++ .../java/com/epam/deltix/util/lang/Util.java | 180 ++++++++++++---- .../deltix/util/memory/DataExchangeUtils.java | 197 ++++++------------ .../deltix/util/memory/MemoryDataInput.java | 31 +-- .../deltix/util/memory/MemoryDataOutput.java | 115 ++++++++-- .../epam/deltix/data/level2fix/TinyFIX.java | 2 +- .../qsrv/hf/spi/conn/ReconnectableImpl.java | 167 ++++++++------- .../concurrent/QuickExecutorWithQueue.java | 1 - .../com/epam/deltix/util/io/BasicIOUtil.java | 10 +- .../deltix/util/io/BufferedInputStreamEx.java | 14 +- .../com/epam/deltix/util/io/CSVWriter.java | 163 +++++++++------ .../deltix/util/io/FlushableOutputStream.java | 7 +- .../java/com/epam/deltix/util/io/IOUtil.java | 7 - .../util/io/LittleEndianDataInputStream.java | 10 +- .../deltix/util/io/TokenReplacingReader.java | 113 +++++----- .../deltix/util/lang/JavaCompilerHelper.java | 26 ++- .../epam/deltix/util/lang/JavaVerifier.java | 5 +- .../deltix/util/net/SSLContextProvider.java | 45 ++-- .../com/epam/deltix/util/os/NamedPipe.java | 3 - .../util/parsers/SyntaxErrorException.java | 4 + .../util/repository/AbstractRepository.java | 7 +- .../deltix/util/text/DateFormatDetector.java | 40 +++- .../deltix/util/time/BasicTimeSource.java | 10 +- .../deltix/util/time/KeeperTimeSource.java | 4 + .../com/epam/deltix/util/time/TimeKeeper.java | 15 +- .../util/io/Test_TokenReplacingReader.java | 6 +- 27 files changed, 739 insertions(+), 493 deletions(-) diff --git a/lang/src/main/java/com/epam/deltix/util/lang/MathUtil.java b/lang/src/main/java/com/epam/deltix/util/lang/MathUtil.java index a90f2022..ed518f39 100644 --- a/lang/src/main/java/com/epam/deltix/util/lang/MathUtil.java +++ b/lang/src/main/java/com/epam/deltix/util/lang/MathUtil.java @@ -50,7 +50,7 @@ public static int sign (float x) { * greater, less than, or equal to 0 respectively. */ public static int sign (int x) { - return (x > 0 ? 1 : x < 0 ? -1 : 0); + return (Integer.compare(x, 0)); } /** @@ -65,7 +65,7 @@ public static int sign (long x) { * Returns 1, -1, or 0 depending on the difference between arguments. */ public static int compare (int x, int y) { - return (x > y ? 1 : x < y ? -1 : 0); + return (Integer.compare(x, y)); } /** @@ -86,7 +86,7 @@ public static int compare (double x, double y) { * Returns 1, -1, or 0 depending on the difference between arguments. */ public static int compare (long x, long y) { - return (x > y ? 1 : x < y ? -1 : 0); + return Long.compare(x, y); } /** @@ -224,22 +224,22 @@ public static double cumulativeStdNormalDistribution (double X) { public static Number negate (Number n) { if (n instanceof Integer) - return (new Integer (-n.intValue ())); + return (-n.intValue()); if (n instanceof Long) - return (new Long (-n.longValue ())); + return (-n.longValue()); if (n instanceof Double) - return (new Double (-n.doubleValue ())); + return (-n.doubleValue()); if (n instanceof Float) - return (new Float (-n.floatValue ())); + return (-n.floatValue()); if (n instanceof Byte) - return (new Byte ((byte) -n.byteValue ())); + return ((byte) -n.byteValue()); if (n instanceof Short) - return (new Short ((short) -n.shortValue ())); + return ((short) -n.shortValue()); if (n instanceof BigDecimal) return (((BigDecimal) n).negate ()); diff --git a/lang/src/main/java/com/epam/deltix/util/lang/StringUtils.java b/lang/src/main/java/com/epam/deltix/util/lang/StringUtils.java index 00786a6b..b92838c2 100644 --- a/lang/src/main/java/com/epam/deltix/util/lang/StringUtils.java +++ b/lang/src/main/java/com/epam/deltix/util/lang/StringUtils.java @@ -728,6 +728,11 @@ public static boolean isEmpty (CharSequence value) { return true; } + /** @return true if trimmed input is not blank string and is not null */ + public static boolean isNotEmpty (CharSequence value) { + return !isEmpty(value); + } + /** @return true if input is null or empty string contains only whitespaces */ public static boolean isWhitespace (CharSequence value) { if (value == null) @@ -1276,4 +1281,31 @@ private static boolean isWildcardQuoted(CharSequence expression, int pos) { ("_%".indexOf(expression.charAt(pos + 1)) != -1); } + public static boolean equals(CharSequence s1, CharSequence s2) { + if (s1 == null) { + return s2 == null; + } + + if (s2 == null) { + return false; + } + + final int len1 = s1.length(); + final int len2 = s2.length(); + + final int diff = len1 - len2; + + if (diff != 0) { + return false; + } + + for (int i = 0; i < len1; i++) { + if (s1.charAt(i) != s2.charAt(i)) { + return false; + } + } + + return true; + } + } \ No newline at end of file diff --git a/lang/src/main/java/com/epam/deltix/util/lang/Util.java b/lang/src/main/java/com/epam/deltix/util/lang/Util.java index eb71153b..51f2eb67 100644 --- a/lang/src/main/java/com/epam/deltix/util/lang/Util.java +++ b/lang/src/main/java/com/epam/deltix/util/lang/Util.java @@ -21,13 +21,14 @@ import java.io.*; import java.lang.management.*; -import java.net.*; -import java.security.*; +import java.lang.reflect.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; import java.util.*; import java.util.prefs.Preferences; -import java.lang.reflect.*; - /** Set of useful methods */ public class Util { public static final boolean IS64BIT = "64".equals(System.getProperty("sun.arch.data.model")); @@ -40,6 +41,10 @@ public class Util { public static final String[] EMPTY_STRING_ARRAY = {}; public static final boolean QUIET = Boolean.getBoolean("quiet"); + // https://stackoverflow.com/questions/3038392/do-java-arrays-have-a-maximum-size + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + private static final int HALF_OF_MAX_INTEGER = Integer.MAX_VALUE / 2; + public static void collectLocalFiles(String path, Collection files) { File file = new File(path); if (file.isDirectory()) { @@ -74,16 +79,52 @@ public static void collectLocalFiles(String path, Collection files, Filt } } + /** + * WARN: Consider using {@link #growArraySize(int, int)} instead, if you do not need the produced value + * to be double of the original value and if the produced value will be used to create an array. + */ public static int doubleUntilAtLeast (int a, int limit) { if (a == 0) return limit; - while (a < limit) + while (a < limit) { + if (a > HALF_OF_MAX_INTEGER) { + throw new IllegalArgumentException("Cannot double " + a + " to at least " + limit + ", it will overflow"); + } + // Double value a = a << 1; - + } + return (a); } + /** + * Returns a new size of the array that is at least minSize. + * Similar to {@link #doubleUntilAtLeast(int, int)}, but allows to specify + * + * @param currentSize Current size of the array + * @param minSize required minimum value + */ + public static int growArraySize(int currentSize, int minSize) { + if (currentSize == 0) { + return minSize; + } + + while (currentSize < minSize) { + if (currentSize > HALF_OF_MAX_INTEGER) { + if (minSize > MAX_ARRAY_SIZE) { + throw new IllegalArgumentException("Cannot grow " + currentSize + " to at least " + minSize + ", the limit is higher than MAX_ARRAY_SIZE"); + } else { + return MAX_ARRAY_SIZE; + } + } + + currentSize = currentSize << 1; + } + + return currentSize; + } + /** * Returns the sign of a - b */ @@ -136,7 +177,7 @@ public static int compare (CharSequence s1, CharSequence s2, boolean f * Compare two CharSequences. A null argument is always less than a non-null argument * and is equal to another null argument. * - * @param maxLength Only compare the first maxLength characters. + * @param maxLength Only compare the first maxLength characters. * Send 0 to unlimit. * @param fast When true, use a fast algorithm, which makes a * char sequence greater than another if it is longer. @@ -208,6 +249,56 @@ public static int fastCompare (CharSequence s1, CharSequence s2) { return (diff); } + /** + * Searches target CharSequence in the source. + * The source is the character sequence being searched, and the target + * is the character sequence being searched for. + * + * @param source the characters being searched. + * @param sourceOffset offset of the source string. + * @param sourceCount count of the source string. + * @param target the characters being searched for. + * @param targetOffset offset of the target string. + * @param targetCount count of the target string. + * @param fromIndex the index to begin searching from. + */ + public static int indexOf(CharSequence source, int sourceOffset, int sourceCount, + CharSequence target, int targetOffset, int targetCount, + int fromIndex) { + if (fromIndex >= sourceCount) { + return (targetCount == 0 ? sourceCount : -1); + } + if (fromIndex < 0) { + fromIndex = 0; + } + if (targetCount == 0) { + return fromIndex; + } + + char first = target.charAt(targetOffset); + int max = sourceOffset + (sourceCount - targetCount); + + for (int i = sourceOffset + fromIndex; i <= max; i++) { + /* Look for first character. */ + if (source.charAt(i) != first) { + while (++i <= max && source.charAt(i) != first); + } + + /* Found first character, now look at the rest of v2 */ + if (i <= max) { + int j = i + 1; + int end = j + targetCount - 1; + for (int k = targetOffset + 1; j < end && source.charAt(j) == target.charAt(k); j++, k++); + + if (j == end) { + /* Found whole string. */ + return i - sourceOffset; + } + } + } + return -1; + } + public static > T max (T a, T b) { if (a == null) return (b); @@ -293,7 +384,6 @@ public static Object callNonStaticMethod ( Object ... args ) throws - ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException @@ -844,53 +934,57 @@ public static boolean xequals(Object obj1, Object obj2) } /** - * Given a Class object, attempts to find its .class location - * Returns null if no such definition can be found. Use for testing/debugging only. - * @param clazz class to lookup + * Given a Class object, attempts to find its .class location [returns null + * if no such definition can be found]. Use for testing/debugging only. + * @param cls class to lookup * @return URL that points to the class definition [null if not found]. + * (From http://www.javaworld.com/javaqa/2003-07/01-qa-0711-classsrc_p.html) */ - public static URL getClassLocation(final Class clazz) + public static URL getClassLocation(final Class cls) { - URL url = null; - - if (clazz == null) - throw new IllegalArgumentException ("Class in undefined"); - - ProtectionDomain pd = clazz.getProtectionDomain (); + if (cls == null) + throw new IllegalArgumentException ("null input: cls"); - String resource = clazz.getName ().replace ('.', '/').concat (".class"); + URL result = null; + final String clsAsResource = cls.getName ().replace ('.', '/').concat (".class"); - // java.lang.Class contract does not specify if 'pd' can ever be null, but anyway + final ProtectionDomain pd = cls.getProtectionDomain (); + // java.lang.Class contract does not specify if 'pd' can ever be null; + // it is not the case for Sun's implementations, but guard against null + // just in case: if (pd != null) { - if (pd.getCodeSource () != null) - url = pd.getCodeSource ().getLocation (); + final CodeSource cs = pd.getCodeSource (); + // 'cs' can be null depending on the classloader behavior: + if (cs != null) + result = cs.getLocation (); - if (url != null) { + if (result != null) { // Convert a code source location into a full class file location - if ("file".equals (url.getProtocol ())) { + // for some common cases: + if ("file".equals (result.getProtocol ())) { try { - String form = url.toExternalForm(); - - if (form.endsWith (".jar") || form.endsWith (".zip")) - url = new URL ("jar:" + form + "!/" + resource); - else if (new File (url.getFile ()).isDirectory ()) - url = new URL (url, resource); - - } catch (MalformedURLException ignore) { - // ignore - } + if (result.toExternalForm ().endsWith (".jar") || + result.toExternalForm ().endsWith (".zip")) + result = new URL ("jar:".concat (result.toExternalForm ()) + .concat ("!/").concat (clsAsResource)); + else if (new File (result.getFile ()).isDirectory ()) + result = new URL (result, clsAsResource); + } catch (MalformedURLException ignore) {} } } } - if (url == null) { - // Try to find 'clazz' definition as a resource; this is not + if (result == null) { + // Try to find 'cls' definition as a resource; this is not // documented to be legal, but Sun's implementations seem to allow this: - ClassLoader clsLoader = clazz.getClassLoader (); - url = clsLoader != null ? clsLoader.getResource (resource) : ClassLoader.getSystemResource (resource); + final ClassLoader clsLoader = cls.getClassLoader (); + + result = clsLoader != null ? + clsLoader.getResource (clsAsResource) : + ClassLoader.getSystemResource (clsAsResource); } - return url; + return result; } /** @@ -1028,7 +1122,7 @@ public static String getThreadStackTrace(Thread thread, ThreadInfo info) { sb.append(" on ").append(info.getLockName()); if (info.getLockOwnerName() != null) - sb.append(" owned by \"" + info.getLockOwnerName() + "\" id=" + info.getLockOwnerId()); + sb.append(" owned by \"").append(info.getLockOwnerName()).append("\" id=").append(info.getLockOwnerId()); // if (isSuspended()) { // sb.append(" (suspended)"); @@ -1068,7 +1162,7 @@ public static String getThreadStackTrace(Thread thread, ThreadInfo info) { for (MonitorInfo mi : info.getLockedMonitors()) { if (mi.getLockedStackDepth() == i) { - sb.append("\t- locked " + mi); + sb.append("\t- locked ").append(mi); sb.append('\n'); } } @@ -1113,6 +1207,7 @@ public static T findCause(Throwable error, Class causeC } /** @return Array of all interfaces implemented by given class (calls cls.getInterfaces() recursively), never null */ + @SuppressWarnings("rawtypes") public static Class [] getClassInterfaces (Class cls) { List result = new ArrayList (); @@ -1134,6 +1229,7 @@ public static T findCause(Throwable error, Class causeC } /** @return true if given cls is instanceof interface specified by className */ + @SuppressWarnings("rawtypes") public static boolean isntanceOf (Class cls, String className) { Class c = cls; @@ -1267,7 +1363,7 @@ public static int arraylen (Object [] array) { * @exception ArrayIndexOutOfBoundsException * If atIdx is out of bounds. */ - @SuppressWarnings ("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) public static T [] arraydel (T [] array, int atIdx) { int oldDim = array.length; diff --git a/lang/src/main/java/com/epam/deltix/util/memory/DataExchangeUtils.java b/lang/src/main/java/com/epam/deltix/util/memory/DataExchangeUtils.java index a5c698cf..267c35cd 100644 --- a/lang/src/main/java/com/epam/deltix/util/memory/DataExchangeUtils.java +++ b/lang/src/main/java/com/epam/deltix/util/memory/DataExchangeUtils.java @@ -18,8 +18,11 @@ import com.epam.deltix.dfp.Decimal64; -import com.epam.deltix.hdtime.HdDateTime; -import com.epam.deltix.hdtime.HdTimeSpan; +import com.epam.deltix.hdtime.*; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; /** * Reads/writes primitive values from/to an array of bytes, @@ -29,12 +32,24 @@ public class DataExchangeUtils { public static final long MAX_LONG48 = 0x00007FFFFFFFFFFFL; public static final long MIN_LONG48 = 0xFFFF800000000000L; + // Big-endian array access + private static final VarHandle BE_LONG_HANDLE = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + private static final VarHandle BE_INT_HANDLE = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + private static final VarHandle BE_SHORT_HANDLE = MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.BIG_ENDIAN); + private static final VarHandle BE_CHAR_HANDLE = MethodHandles.byteArrayViewVarHandle(char[].class, ByteOrder.BIG_ENDIAN); + // Little-endian array access + private static final VarHandle LE_LONG_HANDLE = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle LE_INT_HANDLE = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle LE_SHORT_HANDLE = MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle LE_CHAR_HANDLE = MethodHandles.byteArrayViewVarHandle(char[].class, ByteOrder.LITTLE_ENDIAN); + + public static int readByte (byte [] bytes, int offset) { return (((int) bytes [offset]) & 0xFF); } public static void writeByte (byte [] bytes, int offset, int byt) { - bytes [offset] = (byte) (byt & 0xFF); + bytes [offset] = (byte) byt; } public static void writeByte (byte [] bytes, int offset, byte byt) { @@ -50,7 +65,7 @@ public static void writeByte7 (byte [] bytes, int offset, int byt) { } private static void b (byte [] bytes, int offset, long byt) { - bytes [offset] = (byte) (byt & 0xFF); + bytes [offset] = (byte) byt; } private static long lb (byte [] bytes, int offset) { @@ -62,17 +77,11 @@ public static byte readByte7 (byte [] bytes, int offset) { } public static short readShort (byte [] bytes, int offset) { - return ((short) - (readByte (bytes, offset) << 8 | - readByte (bytes, offset + 1)) - ); + return (short) BE_SHORT_HANDLE.get(bytes, offset); } public static short readShortInvertBytes (byte [] bytes, int offset) { - return ((short) - (readByte (bytes, offset) | - readByte (bytes, offset + 1) << 8) - ); + return (short) LE_SHORT_HANDLE.get(bytes, offset); } public static short readShort15 (byte [] bytes, int offset) { @@ -80,20 +89,16 @@ public static short readShort15 (byte [] bytes, int offset) { } public static int readUnsignedShort (byte [] bytes, int offset) { - return ( - (readByte (bytes, offset) << 8 | - readByte (bytes, offset + 1)) - ); + short value = (short) BE_SHORT_HANDLE.get(bytes, offset); + return value & 0xFFFF; } public static void writeShort (byte [] bytes, int offset, short s) { - writeByte (bytes, offset, s >>> 8); - writeByte (bytes, offset + 1, s); + BE_SHORT_HANDLE.set(bytes, offset, s); } public static void writeShortInvertBytes (byte [] bytes, int offset, short s) { - writeByte (bytes, offset, s); - writeByte (bytes, offset + 1, s >>> 8); + LE_SHORT_HANDLE.set(bytes, offset, s); } public static void writeShort15 (byte [] bytes, int offset, short s) { @@ -101,34 +106,23 @@ public static void writeShort15 (byte [] bytes, int offset, short s) { } public static void writeUnsignedShort (byte [] bytes, int offset, int s) { - writeByte (bytes, offset, s >>> 8); - writeByte (bytes, offset + 1, s); + BE_SHORT_HANDLE.set(bytes, offset, (short) s); } public static char readChar (byte [] bytes, int offset) { - return ((char) - (readByte (bytes, offset) << 8 | - readByte (bytes, offset + 1)) - ); + return (char) BE_CHAR_HANDLE.get(bytes, offset); } public static void writeChar (byte [] bytes, int offset, char s) { - writeByte (bytes, offset, s >>> 8); - writeByte (bytes, offset + 1, s); + BE_CHAR_HANDLE.set(bytes, offset, s); } public static void writeCharInvertBytes (byte [] bytes, int offset, char s) { - writeByte (bytes, offset, s); - writeByte (bytes, offset + 1, s >>> 8); + LE_CHAR_HANDLE.set(bytes, offset, s); } public static int readInt (byte [] bytes, int offset) { - return ( - ((int) bytes [offset]) << 24 | - readByte (bytes, offset + 1) << 16 | - readByte (bytes, offset + 2) << 8 | - readByte (bytes, offset + 3) - ); + return (int) BE_INT_HANDLE.get(bytes, offset); } public static int readInt31 (byte [] bytes, int offset) { @@ -136,19 +130,11 @@ public static int readInt31 (byte [] bytes, int offset) { } public static int readIntInvertBytes (byte [] bytes, int offset) { - return ( - readByte (bytes, offset) | - readByte (bytes, offset + 1) << 8 | - readByte (bytes, offset + 2) << 16 | - readByte (bytes, offset + 3) << 24 - ); + return (int) LE_INT_HANDLE.get(bytes, offset); } public static void writeInt (byte [] bytes, int offset, int i) { - writeByte (bytes, offset, i >>> 24); - writeByte (bytes, offset + 1, i >>> 16); - writeByte (bytes, offset + 2, i >>> 8); - writeByte (bytes, offset + 3, i); + BE_INT_HANDLE.set(bytes, offset, i); } public static void writeInt31 (byte [] bytes, int offset, int i) { @@ -156,17 +142,11 @@ public static void writeInt31 (byte [] bytes, int offset, int i) { } public static void writeUnsignedInt (byte [] bytes, int offset, long i) { - b (bytes, offset, i >>> 24); - b (bytes, offset + 1, i >>> 16); - b (bytes, offset + 2, i >>> 8); - b (bytes, offset + 3, i); + BE_INT_HANDLE.set(bytes, offset, (int) i); } public static void writeIntInvertBytes (byte [] bytes, int offset, int i) { - writeByte (bytes, offset, i); - writeByte (bytes, offset + 1, i >>> 8); - writeByte (bytes, offset + 2, i >>> 16); - writeByte (bytes, offset + 3, i >>> 24); + LE_INT_HANDLE.set(bytes, offset, i); } public static float readFloat (byte [] bytes, int offset) { @@ -181,6 +161,10 @@ public static void writeFloatInvertBytes (byte [] bytes, int offset, float f) writeIntInvertBytes (bytes, offset, Float.floatToIntBits (f)); } + /** + * @deprecated use {@link #readLong(byte[], int)} instead + */ + @Deprecated(forRemoval = true) public static long readLongOld (byte [] bytes, int offset) { return ( ((long) bytes [offset]) << 56 | @@ -195,28 +179,7 @@ public static long readLongOld (byte [] bytes, int offset) { } public static long readLong(byte[] b, int a) { - return makeLong( - b[a], - b[a + 1], - b[a + 2], - b[a + 3], - b[a + 4], - b[a + 5], - b[a + 6], - b[a + 7]); - } - - private static long makeLong(byte b7, byte b6, byte b5, byte b4, - byte b3, byte b2, byte b1, byte b0) - { - return ((((long)b7 ) << 56) | - (((long)b6 & 0xff) << 48) | - (((long)b5 & 0xff) << 40) | - (((long)b4 & 0xff) << 32) | - (((long)b3 & 0xff) << 24) | - (((long)b2 & 0xff) << 16) | - (((long)b1 & 0xff) << 8) | - (((long)b0 & 0xff) )); + return (long) BE_LONG_HANDLE.get(b, a); } /** @@ -224,20 +187,19 @@ private static long makeLong(byte b7, byte b6, byte b5, byte b4, * years -2000 .. 6000, which is usually enough. */ public static long readLong48 (byte [] bytes, int offset) { - return ( - ((long) bytes [offset]) << 40 | - lb (bytes, offset + 1) << 32 | - lb (bytes, offset + 2) << 24 | - readByte (bytes, offset + 3) << 16 | - readByte (bytes, offset + 4) << 8 | - readByte (bytes, offset + 5) - ); + // Note: using VarHandles for long48 is not effective as for other types + // because it requires two calls to VarHandle.get() but it's still a bit faster than + // reading 6 bytes separately. + + // This implementation reads 6 byte signed value as 4-byte signed int and 2-byte unsigned char. + int highBits = (int) BE_INT_HANDLE.get(bytes, offset); + // We use char instead of short to avoid needless sign extension + char lowBits = (char) BE_CHAR_HANDLE.get(bytes, offset + Integer.BYTES); + return (long) highBits << Short.SIZE | lowBits; } - /** - * - */ public static long readUnsigned40 (byte [] bytes, int offset) { + // TODO: Consider using VarHandle return ( lb (bytes, offset) << 32 | lb (bytes, offset + 1) << 24 | @@ -253,6 +215,7 @@ public static long readUnsigned40 (byte [] bytes, int offset) { * @see #readLittleEndianLong(byte[]) */ public static long readLittleEndianLong40(byte[] bytes) { + // TODO: Consider using VarHandle return (0xFF & bytes[0]) + ((0xFF & bytes[1]) << 8) + @@ -266,15 +229,7 @@ public static long readLittleEndianLong40(byte[] bytes) { * @see #readLong(byte[], int) */ public static long readLittleEndianLong (byte [] bytes) { - return - (0xFF & bytes[0]) + - ((0xFF & bytes[1]) << 8) + - ((0xFF & bytes[2]) << 16) + - ((0xFFL & bytes[3]) << 24) + - ((0xFFL & bytes[4]) << 32) + - ((0xFFL & bytes[5]) << 40) + - ((0xFFL & bytes[6]) << 48) + - ((0xFFL & bytes[7]) << 56); + return (long) LE_LONG_HANDLE.get(bytes, 0); } /** @@ -283,6 +238,7 @@ public static long readLittleEndianLong (byte [] bytes) { * @see #readLittleEndianLong(byte[]) */ public static long readLittleEndianLong40(byte[] bytes, int offset) { + // TODO: Consider using VarHandle return (0xFF & bytes[offset]) + ((0xFF & bytes[offset+1]) << 8) + @@ -296,15 +252,7 @@ public static long readLittleEndianLong40(byte[] bytes, int offset) { * @see #readLong(byte[], int) */ public static long readLittleEndianLong (byte [] bytes, int offset) { - return - (0xFF & bytes[offset]) + - ((0xFF & bytes[offset+1]) << 8) + - ((0xFF & bytes[offset+2]) << 16) + - ((0xFFL & bytes[offset+3]) << 24) + - ((0xFFL & bytes[offset+4]) << 32) + - ((0xFFL & bytes[offset+5]) << 40) + - ((0xFFL & bytes[offset+6]) << 48) + - ((0xFFL & bytes[offset+7]) << 56) ; + return (long) LE_LONG_HANDLE.get(bytes, offset); } public static long readLong63 (byte [] bytes, int offset) { @@ -312,34 +260,16 @@ public static long readLong63 (byte [] bytes, int offset) { } public static long readUnsignedInt (byte [] bytes, int offset) { - return ( - lb (bytes, offset + 4) << 24 | - lb (bytes, offset + 5) << 16 | - lb (bytes, offset + 6) << 8 | - lb (bytes, offset + 7) - ); + int value = (int) BE_INT_HANDLE.get(bytes, offset); + return value & 0xFFFFFFFFL; } public static void writeLong (byte [] bytes, int offset, long l) { - b (bytes, offset, l >>> 56); - b (bytes, offset + 1, l >>> 48); - b (bytes, offset + 2, l >>> 40); - b (bytes, offset + 3, l >>> 32); - b (bytes, offset + 4, l >>> 24); - b (bytes, offset + 5, l >>> 16); - b (bytes, offset + 6, l >>> 8); - b (bytes, offset + 7, l); + BE_LONG_HANDLE.set(bytes, offset, l); } public static void writeLongInvertBytes (byte [] bytes, int offset, long l) { - b (bytes, offset, l); - b (bytes, offset + 1, l >>> 8); - b (bytes, offset + 2, l >>> 16); - b (bytes, offset + 3, l >>> 24); - b (bytes, offset + 4, l >>> 32); - b (bytes, offset + 5, l >>> 40); - b (bytes, offset + 6, l >>> 48); - b (bytes, offset + 7, l >> 56); + LE_LONG_HANDLE.set(bytes, offset, l); } public static void writeLong63 (byte [] bytes, int offset, long l) { @@ -353,12 +283,13 @@ public static void writeLong63 (byte [] bytes, int offset, long l) { public static void writeLong48 (byte [] bytes, int offset, long l) { assert l <= MAX_LONG48 && l >= MIN_LONG48 : l; - b (bytes, offset, l >>> 40); - b (bytes, offset + 1, l >>> 32); - b (bytes, offset + 2, l >>> 24); - b (bytes, offset + 3, l >>> 16); - b (bytes, offset + 4, l >>> 8); - b (bytes, offset + 5, l); + // Note: using VarHandles for long48 is not effective as for other types + // because it requires two calls to VarHandle.set() but it's still a bit faster than + // writing 6 bytes separately. + + // This implementation writes 6 byte signed value as 4-byte signed int and 2-byte signed char + BE_INT_HANDLE.set(bytes, offset, (int) (l >>> Short.SIZE)); + BE_SHORT_HANDLE.set(bytes, offset + Integer.BYTES, (short) l); } /** diff --git a/lang/src/main/java/com/epam/deltix/util/memory/MemoryDataInput.java b/lang/src/main/java/com/epam/deltix/util/memory/MemoryDataInput.java index 05c29ebd..eb95ddd1 100644 --- a/lang/src/main/java/com/epam/deltix/util/memory/MemoryDataInput.java +++ b/lang/src/main/java/com/epam/deltix/util/memory/MemoryDataInput.java @@ -400,9 +400,8 @@ public void skipCharSequence () { } /** - * Returns false if the string value is null. + * Appends to the StringBuilder */ - @Deprecated // buggy public StringBuilder appendToStringBuilder (StringBuilder sb) { int utflen = readUnsignedShort (); @@ -413,14 +412,13 @@ public StringBuilder appendToStringBuilder (StringBuilder sb) { return (sb); int c; - int char2, char3; + int char2, char3, char4; int count = 0; for (;;) { - c = readByte (); - if (c < 0) //WAS: if (c > 127 || c < 0) - break; - + c = (int) readByte () & 0xff; // convert to unsigned + if (c > 127) break; // we have character takes more than 1 byte + count++; sb.append ((char) c); @@ -472,12 +470,19 @@ public StringBuilder appendToStringBuilder (StringBuilder sb) { ((char2 & 0x3F) << 6) | (char3 & 0x3F))); break; - - default: + + default: // support for .NET /* 10xx xxxx, 1111 xxxx */ - throw new UncheckedIOException ( - "malformed input around byte " + count - ); + count += 4; + char2 = readByte (); + char3 = readByte (); + char4 = readByte (); + if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80) || ((char4 & 0xC0) != 0x80)) + throw new UncheckedIOException( + "malformed input around byte " + (count-1)); + + sb.append ((char) (((c & 0xF7) << 18) | ((char2 & 0x3F) << 12) | ((char3 & 0x3F) << 6) | (char4 & 0x3F))); + break; } if (count >= utflen) @@ -489,7 +494,7 @@ public StringBuilder appendToStringBuilder (StringBuilder sb) { return (sb); } - private static double [] SCALES = new double [MemoryDataOutput.MAX_SCALE_EXP]; + private static final double [] SCALES = new double [MemoryDataOutput.MAX_SCALE_EXP]; static { long v = 1; diff --git a/lang/src/main/java/com/epam/deltix/util/memory/MemoryDataOutput.java b/lang/src/main/java/com/epam/deltix/util/memory/MemoryDataOutput.java index 5e9288e8..e688deca 100644 --- a/lang/src/main/java/com/epam/deltix/util/memory/MemoryDataOutput.java +++ b/lang/src/main/java/com/epam/deltix/util/memory/MemoryDataOutput.java @@ -20,12 +20,18 @@ import com.epam.deltix.dfp.Decimal64Utils; import com.epam.deltix.util.BitUtil; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; + /** * Equivalent of DataOutputStream wrapped around * ByteArrayOutputStream optimized for extreme performance. This class uses * no virtual method calls and presents a non-virtual public API. */ public final class MemoryDataOutput { + private static final VarHandle LE_INT_HANDLE = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN); + private byte [] mBuffer; private int mPos = 0; private int mSize = 0; @@ -42,6 +48,16 @@ public void makeRoom (int space) { ensureSize (mPos + space); } + /** + * Extends buffer capacity to accommodate the specified space after the current position. + * Unlike {@link #makeRoom(int)} this method does not change the logical size of the buffer. + */ + public void ensureSpace(int space) { + if (mPos + space > mBuffer.length) { + extendBuffer(mSize, mPos + space); + } + } + /** * Increases "logical" size up to specified value. */ @@ -62,6 +78,7 @@ public void ensureSize (int minSize) { /** * Increases "physical" size (size of "mBuffer") up to specified value. + * Does not change "logical" buffer size (mSize). */ private void ensureBufferSize(int minSize) { int currentSize = mBuffer.length; @@ -119,7 +136,15 @@ public void skip (int numBytes) { makeRoom (numBytes); mPos += numBytes; } - + + /** + * Advances current position without checking if there is enough space in the buffer. + *

Should be used only after {@link #makeRoom(int)} call to ensure that there is enough space. + */ + public void skipUnsafe(int numBytes) { + mPos += numBytes; + } + public byte [] getBuffer () { return (mBuffer); } @@ -150,12 +175,16 @@ public void writeStringNonNull (CharSequence str) { writeStringNonNull (str, 0, str.length ()); } - public void writeStringNonNull (CharSequence str, int start, int strlen) { + /** + * @deprecated Use {@link #writeStringNonNull(CharSequence, int, int)} instead. + */ + @Deprecated(forRemoval = true) + public void writeStringNonNullOld (CharSequence str, int start, int endIndex) { int utflen = 0; int c, count = 0; /* use charAt instead of copying String to char array */ - for (int i = start; i < strlen; i++) { + for (int i = start; i < endIndex; i++) { c = str.charAt(i); if ((c >= 0x0001) && (c <= 0x007F)) utflen++; @@ -173,7 +202,7 @@ else if (c > 0x07FF) int i=0; - for (i=start; i= 0x0001) && (c <= 0x007F))) @@ -181,8 +210,8 @@ else if (c > 0x07FF) mBuffer [mPos++] = (byte) c; } - - for (; i < strlen; i++) { + + for (; i < endIndex; i++) { c = str.charAt(i); if ((c >= 0x0001) && (c <= 0x007F)) @@ -199,6 +228,41 @@ else if (c > 0x07FF) { } } + public void writeStringNonNull(CharSequence str, int startIndex, int endIndex) { + // Ensure enough space for the worst case + ensureSpace(2 + (endIndex - startIndex) * 3); + int startPos = mPos; + + int pos = startPos + 2; // reserve space for length + + for (int i = startIndex; i < endIndex; i++) { + int c = str.charAt(i); + + if ((c >= 0x0001) && (c <= 0x007F)) { + mBuffer[pos] = (byte) c; + pos++; + } else if (c > 0x07FF) { + mBuffer[pos] = (byte) (0xE0 | ((c >> 12) & 0x0F)); + mBuffer[pos + 1] = (byte) (0x80 | ((c >> 6) & 0x3F)); + mBuffer[pos + 2] = (byte) (0x80 | (c & 0x3F)); + pos += 3; + } else { + mBuffer[pos] = (byte) (0xC0 | ((c >> 6) & 0x1F)); + mBuffer[pos + 1] = (byte) (0x80 | (c & 0x3F)); + pos += 2; + } + } + + int utfLen = pos - startPos - 2; + if (utfLen >= 0xFFFF) { + throw new RuntimeException("Encoded string too long: " + utfLen + " bytes"); + } + DataExchangeUtils.writeUnsignedShort(mBuffer, startPos, utfLen); + + mPos = pos; + mSize = Math.max(mSize, mPos); + } + public void write (byte[] b, int off, int len) { makeRoom (len); System.arraycopy (b, off, mBuffer, mPos, len); @@ -234,7 +298,10 @@ private void writeByteUnsafe (byte v) { /** * Writes byte to buffer without checking if there space for this byte available. * This call *must* be prepended with corresponding {@link #makeRoom(int)} call. + * + * @deprecated Use direct access to buffer via {@link #getBuffer()} and advance position using {@link #skipUnsafe} instead. */ + @Deprecated public void writeByteUnsafe (long v) { writeByteUnsafe((byte) v); } @@ -608,25 +675,37 @@ public void writeScaledDouble (double v) { // } /** - * Writes out a long in LSBF order, and stops when all + * Writes out a long in LSBF (Little-Endian) order, and stops when all * remaining bytes are 0. * * @param v The long to write. * @return The number of bytes written, between 0 .. 8 inclusively. */ public int writeLongBytes (long v) { - int addlPos = mPos; - - // Expand buffer enough to cover te worst cast - // Note: we might expand buffer a bit more than we actually need - ensureBufferSize(mPos + 8); - - while (v != 0) { - writeByteUnsafe (v); - v = v >>> 8; + // Get number of non-zero bytes + int numBytes = 8 - (Long.numberOfLeadingZeros(v) >>> 3); + + int pos = mPos; + int newPos = pos + numBytes; + // Expand buffer enough to cover the worst case + ensureBufferSize(newPos); + + // Note: while generally this implementation can benefit from using VarHandle to copy 8 bytes at once as long, + // in practice this method is called only with values that fit into 7 bytes. + // So it's not useful to have a special case for 8 bytes. + int i = 0; + if (numBytes >= Integer.BYTES) { + // Write first 4 bytes (lowest) + LE_INT_HANDLE.set(mBuffer, pos, (int) v); + i += Integer.BYTES; } - mSize = Math.max(mSize, mPos); - return (mPos - addlPos); + for (; i < numBytes; i++) { + mBuffer[pos + i] = (byte) (v >>> (i * Byte.SIZE)); + } + + mPos = newPos; + mSize = Math.max(mSize, newPos); + return numBytes; } public byte [] toByteArray () { diff --git a/util/src/main/java/com/epam/deltix/data/level2fix/TinyFIX.java b/util/src/main/java/com/epam/deltix/data/level2fix/TinyFIX.java index 483190ae..44418811 100644 --- a/util/src/main/java/com/epam/deltix/data/level2fix/TinyFIX.java +++ b/util/src/main/java/com/epam/deltix/data/level2fix/TinyFIX.java @@ -26,7 +26,7 @@ */ public abstract class TinyFIX { - private class CharArray implements CharSequence { + private static class CharArray implements CharSequence { private final char [] buf; diff --git a/util/src/main/java/com/epam/deltix/qsrv/hf/spi/conn/ReconnectableImpl.java b/util/src/main/java/com/epam/deltix/qsrv/hf/spi/conn/ReconnectableImpl.java index a89766ea..f75d19be 100644 --- a/util/src/main/java/com/epam/deltix/qsrv/hf/spi/conn/ReconnectableImpl.java +++ b/util/src/main/java/com/epam/deltix/qsrv/hf/spi/conn/ReconnectableImpl.java @@ -16,15 +16,11 @@ */ package com.epam.deltix.qsrv.hf.spi.conn; -import com.epam.deltix.gflog.api.Log; -import com.epam.deltix.gflog.api.LogFactory; -import com.epam.deltix.gflog.api.LogLevel; -import com.epam.deltix.util.lang.Util; -import com.epam.deltix.util.log.LazyLogger; import com.epam.deltix.util.time.GlobalTimer; import com.epam.deltix.util.time.TimerRunner; import net.jcip.annotations.GuardedBy; +import java.util.Objects; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; @@ -40,10 +36,10 @@ public class ReconnectableImpl extends DisconnectableEventHandler { public interface Reconnector { /** * Try and reconnect. If successful, this method must call - * {@link ReconnectableImpl#connected} on helper. After that, the return + * {@link ReconnectableImpl#connected} on helper. After that, the return * value is irrelevant. If unsucessful, this method can either throw - * an exception, or return true to reschedule the reconnect, - * or, in rare instances, return false to give up. + * an exception, or return true to reschedule the reconnect, + * or, in rare instances, return false to give up. * * @return Whether reconnection should be rescheduled. * @throws java.lang.Exception @@ -121,32 +117,46 @@ public long nextInterval ( private volatile Level logLevel = Level.FINE; private volatile String logprefix; - @GuardedBy ("this") + + private final Object lockObject; + + @GuardedBy("lockObject") private Reconnector reconnector = null; private volatile boolean isConnected = false; - @GuardedBy ("this") + @GuardedBy("lockObject") private long timeDisconnected; - @GuardedBy ("this") + @GuardedBy("lockObject") private int numReconnectAttempts; - @GuardedBy ("this") - private long currentReconnectInterval; + @GuardedBy("lockObject") + private long currentReconnectInterval; - @GuardedBy ("this") + @GuardedBy("lockObject") private TimerTask reconnectTask; - @GuardedBy ("this") + @GuardedBy("lockObject") private String lastExceptionAsString; public ReconnectableImpl() { this.logprefix = getDefaultPrefix(getClass()); + this.lockObject = this; } public ReconnectableImpl(String logprefix) { this.logprefix = logprefix; + this.lockObject = this; + } + + /** + * @param lockObject an object to use as lock for synchronization instead of "this" + */ + public ReconnectableImpl(String logprefix, Object lockObject) { + this.logprefix = logprefix; + // Use this as lock object if not provided. That's preserves old behavior. + this.lockObject = Objects.requireNonNull(lockObject, "lockObject may not be null"); } public ReconnectIntervalAdjuster getAdjuster () { @@ -178,9 +188,11 @@ public void setInitialReconnectInterval ( { this.initialReconnectInterval = initialReconnectInterval; } - - public synchronized int getNumReconnectAttempts () { - return numReconnectAttempts; + + public int getNumReconnectAttempts() { + synchronized (lockObject) { + return numReconnectAttempts; + } } public Level getLogLevel () { @@ -208,7 +220,7 @@ public void setLogPrefix(String logprefix) { } public void connected () { - synchronized (this) { + synchronized (lockObject) { if (reconnectTask != null) reconnectTask.cancel (); @@ -223,7 +235,7 @@ public void connected () { } public void disconnected () { - synchronized (this) { + synchronized (lockObject) { isConnected = false; timeDisconnected = System.currentTimeMillis (); } @@ -254,57 +266,63 @@ private void log(String msg, Throwable x) { lg.log (logLevel, msg, x); } - public synchronized boolean isConnected () { - return (isConnected); + @Override + public boolean isConnected() { + synchronized (lockObject) { + return isConnected; + } } - - private synchronized void tryReconnect () throws Exception { - reconnectTask = null; - - boolean reschedule = false; - - if (!isConnected && reconnector != null) { - try { - reschedule = - reconnector.tryReconnect ( - numReconnectAttempts, - System.currentTimeMillis () - timeDisconnected, - this - ); - } catch (Throwable x) { - String check = x.toString (); - if (check.equals (lastExceptionAsString)) { - //logger.log (logLevel, "[%s] Reconnect failed due to: %s").with(logprefix).with(lastExceptionAsString); - log ("[{0}] Reconnect failed due to: {1}", logprefix, lastExceptionAsString); - } else { - //logger.log (logLevel, "[%s] Reconnect failed: %s").with(logprefix).with(x); - log ("[" + logprefix + "] Reconnect failed", x); - lastExceptionAsString = check; + + private void tryReconnect() { + synchronized (lockObject) { + reconnectTask = null; + + boolean reschedule = false; + + if (!isConnected && reconnector != null) { + try { + reschedule = + reconnector.tryReconnect( + numReconnectAttempts, + System.currentTimeMillis() - timeDisconnected, + this + ); + } catch (Throwable x) { + String check = x.toString(); + if (check.equals(lastExceptionAsString)) { + //logger.log (logLevel, "[%s] Reconnect failed due to: %s").with(logprefix).with(lastExceptionAsString); + log("[{0}] Reconnect failed due to: {1}", logprefix, lastExceptionAsString); + } else { + //logger.log (logLevel, "[%s] Reconnect failed: %s").with(logprefix).with(x); + log("[" + logprefix + "] Reconnect failed", x); + lastExceptionAsString = check; + } + + reschedule = true; } - reschedule = true; + numReconnectAttempts++; } - numReconnectAttempts++; - } - - if (!isConnected && reschedule) { - ReconnectIntervalAdjuster adj = adjuster; + if (!isConnected && reschedule) { + ReconnectIntervalAdjuster adj = adjuster; - if (adj != null) - currentReconnectInterval = - adj.nextInterval ( - numReconnectAttempts, - timeDisconnected, - currentReconnectInterval - ); + if (adj != null) + currentReconnectInterval = + adj.nextInterval( + numReconnectAttempts, + timeDisconnected, + currentReconnectInterval + ); - scheduleTask (); + scheduleTask(); + } } } + @GuardedBy("lockObject") private void scheduleTask () { - assert Thread.holdsLock (this); + assert Thread.holdsLock(lockObject); reconnectTask = new TimerRunner() { @@ -325,21 +343,26 @@ public void runInternal () { log("[{0}] Next reconnect in {1}", logprefix, currentReconnectInterval); } - public synchronized void scheduleReconnect () { - if (reconnector == null) - throw new IllegalStateException ("[" + logprefix + "] Call setReconnector() first."); + public void scheduleReconnect() { + synchronized (lockObject) { + if (reconnector == null) + throw new IllegalStateException ("[" + logprefix + "] Call setReconnector() first."); - if (reconnectTask != null) - reconnectTask.cancel (); - - numReconnectAttempts = 0; - currentReconnectInterval = initialReconnectInterval; - scheduleTask (); + if (reconnectTask != null) + reconnectTask.cancel (); + + numReconnectAttempts = 0; + currentReconnectInterval = initialReconnectInterval; + scheduleTask (); + } } - public synchronized void cancelReconnect () { - if (reconnectTask != null) - reconnectTask.cancel (); + public void cancelReconnect() { + synchronized (lockObject) { + if (reconnectTask != null) { + reconnectTask.cancel(); + } + } } private static String getDefaultPrefix(Class current) { diff --git a/util/src/main/java/com/epam/deltix/util/concurrent/QuickExecutorWithQueue.java b/util/src/main/java/com/epam/deltix/util/concurrent/QuickExecutorWithQueue.java index 70e41c3b..608c1133 100644 --- a/util/src/main/java/com/epam/deltix/util/concurrent/QuickExecutorWithQueue.java +++ b/util/src/main/java/com/epam/deltix/util/concurrent/QuickExecutorWithQueue.java @@ -20,7 +20,6 @@ import com.epam.deltix.gflog.api.LogFactory; import com.epam.deltix.gflog.api.LogLevel; import com.epam.deltix.util.collections.QuickList; -import com.epam.deltix.util.lang.Util; import java.util.*; import java.util.concurrent.locks.LockSupport; import net.jcip.annotations.GuardedBy; diff --git a/util/src/main/java/com/epam/deltix/util/io/BasicIOUtil.java b/util/src/main/java/com/epam/deltix/util/io/BasicIOUtil.java index 632143bf..dd7ac1cf 100644 --- a/util/src/main/java/com/epam/deltix/util/io/BasicIOUtil.java +++ b/util/src/main/java/com/epam/deltix/util/io/BasicIOUtil.java @@ -49,7 +49,7 @@ * I/O Utilities which have no dependencies on Deltix CG code. */ public abstract class BasicIOUtil { - public static final String CR = System.getProperty ("line.separator"); + public static final String CR = System.lineSeparator(); public static final String TEMP_FILE_PREFIX = "~"; /** @@ -685,13 +685,7 @@ public static T readFromReaderWithProperties (Reader r, f r = new BufferedReader (r); if (props != null) { - r = new TokenReplacingReader(r, new TokenReplacingReader.TokenResolver() { - - @Override - public String resolveToken(String token) { - return props.getProperty(token); - } - }); + r = new TokenReplacingReader(r, props::getProperty); } CharBuffer tmpContent = CharBuffer.allocate(4096); diff --git a/util/src/main/java/com/epam/deltix/util/io/BufferedInputStreamEx.java b/util/src/main/java/com/epam/deltix/util/io/BufferedInputStreamEx.java index 877a5ed5..cdc7b87f 100644 --- a/util/src/main/java/com/epam/deltix/util/io/BufferedInputStreamEx.java +++ b/util/src/main/java/com/epam/deltix/util/io/BufferedInputStreamEx.java @@ -25,8 +25,20 @@ * {@link BufferedInputStream} populates it's buffer with initial data from provided buffer. */ public class BufferedInputStreamEx extends BufferedInputStream { + /** + * Use {@link #BufferedInputStreamEx(InputStream, byte[], int, int)} instead. + */ + @Deprecated public BufferedInputStreamEx(InputStream in, byte[] buf, int size) { - super(in, Math.max(Bits.nextPowerOfTwo(size), 8192)); + this(in, buf, size, 8192); + } + + /** + * @param size amount of data in the provided buffer, will be copied to the new buffer + * @param minBufferSize minimum size for a new buffer + */ + public BufferedInputStreamEx(InputStream in, byte[] buf, int size, int minBufferSize) { + super(in, Bits.nextPowerOfTwo(Math.max(size, minBufferSize))); assert size <= this.buf.length; diff --git a/util/src/main/java/com/epam/deltix/util/io/CSVWriter.java b/util/src/main/java/com/epam/deltix/util/io/CSVWriter.java index 5e4d5e26..807e2850 100644 --- a/util/src/main/java/com/epam/deltix/util/io/CSVWriter.java +++ b/util/src/main/java/com/epam/deltix/util/io/CSVWriter.java @@ -15,6 +15,7 @@ * the License. */ package com.epam.deltix.util.io; +import com.epam.deltix.util.collections.generated.CharacterHashSet; import java.io.*; @@ -24,51 +25,96 @@ public class CSVWriter extends FilterWriter { private static final char DEFAULT_SEPARATOR = ','; - + public static final char DEFAULT_QUOTE_CHARACTER = '"'; + public static final char[] DEFAULT_ESCAPE_CHARACTERS = new char[] {'\n', '\r'}; + private boolean closeDelegate = true; private boolean flushEveryLine = false; private final char separator; - - public CSVWriter (String f) throws IOException { - this (new File (f)); - } - - public CSVWriter (String f, char separator) throws IOException { - this (new File (f), separator); - } + private final char quoteCharacter; + private final CharacterHashSet escapeCharacters; - public CSVWriter (String f, boolean append) throws IOException { - this (new File (f), append); - } - - public CSVWriter (String f, boolean append, char separator ) throws IOException { - this (new File (f), append, separator); - } - - public CSVWriter (File f) throws IOException { - this (f, false); + /** + * Creates CSWWriter instance with default settings: + * separator char = ',' + * quote char = '"' + * escape chars = '\n' '\r' ',' '"' + * + * @param file output file + * @throws IOException when any IO + */ + public CSVWriter (File file) throws IOException { + this(file, false); } - - public CSVWriter (File f, char separator) throws IOException { - this (f, false, separator); + + /** + * Creates CSWWriter instance with given separator char and defaults: + * quote char = '"' + * escape chars = '\n' '\r' '"' separator + * + * @param file output file + * @param separator separator char + * @throws IOException when any IO + */ + public CSVWriter (File file, char separator) throws IOException { + this (file, false, separator); } - public CSVWriter (File f, boolean append) throws IOException { - this (new BufferedWriter (new FileWriter (f, append))); + /** + * Creates CSWWriter instance with default settings: + * separator char = ',' + * quote char = '"' + * escape chars = '\n' '\r' ',' '"' + * + * @param file output file + * @param append append mode + * @throws IOException when any IO + */ + public CSVWriter (File file, boolean append) throws IOException { + this (new BufferedWriter (new FileWriter (file, append))); } - + + /** + * Creates CSWWriter instance with given separator char and defaults: + * quote char = '"' + * escape chars = '\n' '\r' '"' separator + * + * @param f output file + * @param append append mode + * @throws IOException when any IO + */ public CSVWriter (File f, boolean append, char separator) throws IOException { this (new BufferedWriter (new FileWriter (f, append)), separator); } - + + /** + * @param file output file + * @param append append mode + * @param separator separator char + * @param quoteCharacter quote char to escape special characters + * @param escapeCharacters list of characters to escape (separator and quote char will be included) + * @throws IOException on any error + */ + public CSVWriter (File file, boolean append, char separator, char quoteCharacter, char... escapeCharacters) throws IOException { + this(new BufferedWriter(new FileWriter(file, append)), separator, quoteCharacter, escapeCharacters); + } + public CSVWriter (Writer out) { this (out, DEFAULT_SEPARATOR); } - + public CSVWriter (Writer out, char separator) { + this (out, separator, DEFAULT_QUOTE_CHARACTER, DEFAULT_ESCAPE_CHARACTERS); + } + + public CSVWriter (Writer out, char separator, char quoteCharacter, char... escapeCharacters) { super (out); this.separator = separator; + this.quoteCharacter = quoteCharacter; + this.escapeCharacters = new CharacterHashSet(escapeCharacters); + this.escapeCharacters.add(separator); + this.escapeCharacters.add(quoteCharacter); } public CSVWriter (OutputStream os) { @@ -106,7 +152,7 @@ public void setFlushEveryLine (boolean flushEveryLine) { public void writeCell (CharSequence unescapedText) throws IOException { if (unescapedText != null) synchronized (lock) { - printCell (unescapedText, out); + printCell (unescapedText); } } @@ -127,7 +173,7 @@ public void writeCells (boolean first, Object ... args) throws IOExc write (separator); if (arg != null) - printCell (arg.toString (), out); + printCell (arg.toString ()); } } } @@ -141,7 +187,7 @@ public void writeCells (boolean first, Object ... args) throws IOExc public void writeCell (Object cell) throws IOException { if (cell != null) synchronized (lock) { - printCell (cell.toString (), out); + printCell (cell.toString ()); } } @@ -183,45 +229,36 @@ public void close () throws IOException { else super.flush (); } - - /** - * Prints text to CSV cell, escaping it if necessary. - * - * @param unescapedText The text of a single cell to print. - * @param wr The CSV format writer - * @throws java.io.IOException If writer fails to write - */ - public static void printCell (CharSequence unescapedText, Appendable wr) throws IOException { - int len = unescapedText.length (); - + + private void printCell(CharSequence unescapedText) throws IOException { + int len = unescapedText.length(); + if (len == 0) return; - - boolean needEscape = false; - - search: for (int ii = 0; ii < len; ii++) { - switch (unescapedText.charAt (ii)) { - case '"': - case ',': - needEscape = true; - break search; + + boolean needEscape = false; + + for (int ii = 0; ii < len; ii++) { + if (escapeCharacters.contains(unescapedText.charAt(ii))) { + needEscape = true; + break; } } - - if (needEscape) - wr.append ('"'); - + + if (needEscape) + out.append(quoteCharacter); + for (int ii = 0; ii < len; ii++) { - char ch = unescapedText.charAt (ii); - - if (ch == '"') - wr.append ('"'); - - wr.append (ch); + char ch = unescapedText.charAt(ii); + + if (ch == quoteCharacter) + out.append(quoteCharacter); + + out.append(ch); } - - if (needEscape) - wr.append ('"'); + + if (needEscape) + out.append(quoteCharacter); } } \ No newline at end of file diff --git a/util/src/main/java/com/epam/deltix/util/io/FlushableOutputStream.java b/util/src/main/java/com/epam/deltix/util/io/FlushableOutputStream.java index 0168213f..93dace06 100644 --- a/util/src/main/java/com/epam/deltix/util/io/FlushableOutputStream.java +++ b/util/src/main/java/com/epam/deltix/util/io/FlushableOutputStream.java @@ -85,10 +85,11 @@ public synchronized void disableFlushing() { * @exception IOException if an I/O error occurs. */ public synchronized void write(int b) throws IOException { - if (cache.size() > limit && canFlush) + if (cache.size() > limit && canFlush) { flushInternal(); - else - cache.write(b); + } + + cache.write(b); } /** diff --git a/util/src/main/java/com/epam/deltix/util/io/IOUtil.java b/util/src/main/java/com/epam/deltix/util/io/IOUtil.java index 83eb124d..070b4d13 100644 --- a/util/src/main/java/com/epam/deltix/util/io/IOUtil.java +++ b/util/src/main/java/com/epam/deltix/util/io/IOUtil.java @@ -314,11 +314,4 @@ public static boolean waitForCopiedCompletely(File file, long timeoutInMillis) t return true; } - - public static void main(String[] args) throws Exception { - File file = new File("C:\\Quartus-web-14.1.0.186.iso"); - System.out.println(copiedCompletely(file)); - System.out.println(waitForCopiedCompletely(file)); - System.out.println("DONE"); - } } \ No newline at end of file diff --git a/util/src/main/java/com/epam/deltix/util/io/LittleEndianDataInputStream.java b/util/src/main/java/com/epam/deltix/util/io/LittleEndianDataInputStream.java index d2dc9849..d5c9d642 100644 --- a/util/src/main/java/com/epam/deltix/util/io/LittleEndianDataInputStream.java +++ b/util/src/main/java/com/epam/deltix/util/io/LittleEndianDataInputStream.java @@ -41,8 +41,8 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da /** * working arrays initialized on demand by readUTF */ - private byte bytearr[] = new byte[80]; - private char chararr[] = new char[80]; + private byte[] bytearr = new byte[80]; + private char[] chararr = new char[80]; /** @@ -77,7 +77,7 @@ public final void close() throws IOException { * @throws IOException if read fails. */ @Override - public final int read(byte ba[], int off, int len) throws IOException { + public final int read(byte[] ba, int off, int len) throws IOException { // For efficiency, we avoid one layer of wrapper return is.read(ba, off, len); } @@ -140,7 +140,7 @@ public final float readFloat() throws IOException { * * @see java.io.DataInput#readFully(byte[]) */ - public final void readFully(byte ba[]) throws IOException { + public final void readFully(byte[] ba) throws IOException { dis.readFully(ba, 0, ba.length); } @@ -150,7 +150,7 @@ public final void readFully(byte ba[]) throws IOException { * @throws IOException if read fails. * @see java.io.DataInput#readFully(byte[],int,int) */ - public final void readFully(byte ba[], + public final void readFully(byte[] ba, int off, int len) throws IOException { dis.readFully(ba, off, len); diff --git a/util/src/main/java/com/epam/deltix/util/io/TokenReplacingReader.java b/util/src/main/java/com/epam/deltix/util/io/TokenReplacingReader.java index adf43842..a7011110 100644 --- a/util/src/main/java/com/epam/deltix/util/io/TokenReplacingReader.java +++ b/util/src/main/java/com/epam/deltix/util/io/TokenReplacingReader.java @@ -23,89 +23,92 @@ public class TokenReplacingReader extends Reader { - private final StringBuilder nameBuffer = new StringBuilder(); + private PushbackReader pushbackReader = null; + private ITokenResolver tokenResolver = null; + private StringBuilder tokenNameBuffer = new StringBuilder(); + private String tokenValue = null; + private int tokenValueIndex = 0; - private final PushbackReader reader; - private final TokenResolver resolver; + public static interface ITokenResolver { - private String tokenValue = null; - private int tokenValueIndex = 0; + String resolveToken(String token); + } - public TokenReplacingReader(Reader in, TokenResolver resolver) { - this.reader = new PushbackReader(in, 2); - this.resolver = resolver; + public TokenReplacingReader(Reader source, ITokenResolver resolver) { + this.pushbackReader = new PushbackReader(source, 2); + this.tokenResolver = resolver; + } + + @Override + public int read(CharBuffer target) throws IOException { + + return read(target.array(), 0, target.limit()); } @Override public int read() throws IOException { - if (tokenValue != null) { - int length = tokenValue.length(); - - if (tokenValueIndex < length) - return tokenValue.charAt(tokenValueIndex++); - - if (tokenValueIndex == length) { - tokenValue = null; - tokenValueIndex = 0; + if (this.tokenValue != null) { + if (this.tokenValueIndex < this.tokenValue.length()) { + return this.tokenValue.charAt(this.tokenValueIndex++); + } + if (this.tokenValueIndex == this.tokenValue.length()) { + this.tokenValue = null; + this.tokenValueIndex = 0; + } } - } - int data = reader.read(); - if (data != '$') - return data; + int data = this.pushbackReader.read(); + if (data != '$') + return data; - data = reader.read(); - if (data != '{') { - reader.unread(data); - return '$'; - } - nameBuffer.delete(0, nameBuffer.length()); + data = this.pushbackReader.read(); + if (data != '{') { + this.pushbackReader.unread(data); + return '$'; + } + this.tokenNameBuffer.delete(0, this.tokenNameBuffer.length()); - data = reader.read(); - while (data != '}') { - nameBuffer.append((char) data); - data = reader.read(); - } + data = this.pushbackReader.read(); + while (data != '}') { + this.tokenNameBuffer.append((char) data); + data = this.pushbackReader.read(); + } - tokenValue = resolver.resolveToken(nameBuffer.toString()); + this.tokenValue = this.tokenResolver.resolveToken(this.tokenNameBuffer + .toString()); - if (tokenValue == null || tokenValue.length() == 0) - tokenValue = "${" + nameBuffer.toString() + "}"; + if (this.tokenValue == null || this.tokenValue.length() == 0) { + this.tokenValue = "${" + this.tokenNameBuffer.toString() + "}"; + } + return this.tokenValue.charAt(this.tokenValueIndex++); - return tokenValue.charAt(tokenValueIndex++); } @Override - public int read(CharBuffer target) throws IOException { - return read(target.array(), 0, target.limit()); + public int read(char cbuf[]) throws IOException { + return read(cbuf, 0, cbuf.length); } - @Override - public int read(char[] input) throws IOException { - return read(input, 0, input.length); - } @Override - public int read(char[] input, int off, int len) throws IOException { - int count = 0; - - while (count < len) { - int next = read(); - - if (next == -1) { // EOF? - if (count == 0) + public int read(char cbuf[], int off, int len) throws IOException { + int charsRead = 0; + while (charsRead < len) { + int nextChar = read(); + if (nextChar == -1) { // EOF? + if (charsRead == 0) { return -1; // none read + } break; } - - input[off + (count++)] = (char) next; + cbuf[off + (charsRead++)] = (char) nextChar; } - return count; + return charsRead; } @Override public void close() throws IOException { - reader.close(); + this.pushbackReader.close(); } @Override @@ -115,7 +118,7 @@ public long skip(long n) throws IOException { @Override public boolean ready() throws IOException { - return reader.ready(); + return this.pushbackReader.ready(); } @Override diff --git a/util/src/main/java/com/epam/deltix/util/lang/JavaCompilerHelper.java b/util/src/main/java/com/epam/deltix/util/lang/JavaCompilerHelper.java index c87150ce..87133764 100644 --- a/util/src/main/java/com/epam/deltix/util/lang/JavaCompilerHelper.java +++ b/util/src/main/java/com/epam/deltix/util/lang/JavaCompilerHelper.java @@ -24,6 +24,7 @@ import javax.tools.*; import java.io.*; +import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.*; @@ -61,8 +62,8 @@ private static JavaCompiler getJavaCompilerInstance() { try { Class c = Class.forName(toolsJarClassLoader, false, Thread.currentThread().getContextClassLoader()).asSubclass(JavaCompiler.class); - compiler = c.newInstance(); - } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + compiler = c.getDeclaredConstructor().newInstance(); + } catch (NoSuchMethodException | InvocationTargetException | ClassNotFoundException | IllegalAccessException | InstantiationException e) { compiler = ToolProvider.getSystemJavaCompiler(); } return compiler; @@ -87,14 +88,16 @@ public ClassLoader getClassLoader () { } public Class compileClass (String className, String code) throws ClassNotFoundException { - List compilationUnits = Arrays.asList(new MemorySource(className, code)); + List compilationUnits = List.of(new MemorySource(className, code)); Writer out = new PrintWriter(System.err); DiagnosticCollector dianosticListener = new DiagnosticCollector<>(); final String optionString = System.getProperty("javac.options"); final Iterable options = optionString == null ? null : Arrays.asList(optionString.split(" ")); - JavaCompiler.CompilationTask compile = JAVA_COMPILER_INSTANCE.getTask(out, fileManager, dianosticListener, options, null, compilationUnits); + JavaCompiler.CompilationTask compile; final boolean ok; synchronized (JAVA_COMPILER_INSTANCE) { + // "fileManager" is not thread-safe, so "getTask" should be synchronized + compile = JAVA_COMPILER_INSTANCE.getTask(out, fileManager, dianosticListener, options, null, compilationUnits); ok = compile.call(); } @@ -133,9 +136,11 @@ public Map> compileClasses(Map mapClassName2Cod DiagnosticCollector dianosticListener = new DiagnosticCollector<>(); final String optionString = System.getProperty("javac.options"); final Iterable options = optionString == null ? null : Arrays.asList(optionString.split(" ")); - JavaCompiler.CompilationTask compile = JAVA_COMPILER_INSTANCE.getTask(out, fileManager, dianosticListener, options, null, compilationUnits); + JavaCompiler.CompilationTask compile; final boolean ok; synchronized (JAVA_COMPILER_INSTANCE) { + // "fileManager" is not thread-safe, so "getTask" should be synchronized + compile = JAVA_COMPILER_INSTANCE.getTask(out, fileManager, dianosticListener, options, null, compilationUnits); ok = compile.call(); } @@ -176,7 +181,7 @@ public Map> compileClasses(Map mapClassName2Cod } private static class MemorySource extends SimpleJavaFileObject { - private String src; + private final String src; public MemorySource(String name, String src) { super(URI.create("string:///" + name.replace ('.', '/') + ".java"), Kind.SOURCE); @@ -201,7 +206,7 @@ public InputStream openInputStream() { private static class SpecialJavaFileManager extends ForwardingJavaFileManager { - private SpecialClassLoader xcl; + private final SpecialClassLoader xcl; public SpecialJavaFileManager(JavaFileManager sjfm, SpecialClassLoader xcl) { super(sjfm); @@ -209,7 +214,7 @@ public SpecialJavaFileManager(JavaFileManager sjfm, SpecialClassLoader xcl) { } @Override - public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) throws IOException { + public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) { MemoryByteCode mbc = new MemoryByteCode(name); xcl.addClass(name, mbc); return mbc; @@ -280,7 +285,7 @@ public static class SpecialClassLoader extends ClassLoader implements ClassDirectory { - private Map m = new HashMap<>(); + private final Map m = new HashMap<>(); public SpecialClassLoader(ClassLoader parent) { super(parent); @@ -314,10 +319,11 @@ public Class findClass(String name) return clazz == null ? defineClass(name, mbc.getBytes(), 0, mbc.getBytes().length) : clazz; } - public void addClass(String name, MemoryByteCode mbc) { + void addClass(String name, MemoryByteCode mbc) { m.put(name, mbc); } + @Override public Collection > listClassesForPackage (String packageName) { if (packageName != null && packageName.isEmpty ()) packageName = null; diff --git a/util/src/main/java/com/epam/deltix/util/lang/JavaVerifier.java b/util/src/main/java/com/epam/deltix/util/lang/JavaVerifier.java index 4ac854a8..91477db9 100644 --- a/util/src/main/java/com/epam/deltix/util/lang/JavaVerifier.java +++ b/util/src/main/java/com/epam/deltix/util/lang/JavaVerifier.java @@ -21,6 +21,7 @@ import javax.tools.JavaCompiler; import javax.tools.ToolProvider; +import java.lang.reflect.InvocationTargetException; /** * Verify that runtime included java compiler. @@ -37,8 +38,8 @@ private static JavaCompiler getJavaCompilerInstance() { try { Class c = Class.forName(toolsJarClassLoader, false, Thread.currentThread().getContextClassLoader()).asSubclass(JavaCompiler.class); - compiler = c.newInstance(); - } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + compiler = c.getDeclaredConstructor().newInstance(); + } catch (NoSuchMethodException | InvocationTargetException | ClassNotFoundException | IllegalAccessException | InstantiationException e) { compiler = ToolProvider.getSystemJavaCompiler(); } return compiler; diff --git a/util/src/main/java/com/epam/deltix/util/net/SSLContextProvider.java b/util/src/main/java/com/epam/deltix/util/net/SSLContextProvider.java index f86371dc..e60d1b3b 100644 --- a/util/src/main/java/com/epam/deltix/util/net/SSLContextProvider.java +++ b/util/src/main/java/com/epam/deltix/util/net/SSLContextProvider.java @@ -42,12 +42,29 @@ public static SSLContext createSSLContext(String keystore public static SSLContext createSSLContext(String protocol, String keystoreFile, String keystorePass, boolean trustALL) throws GeneralSecurityException, IOException { + KeyStore keystore = KeyStore.getInstance(KEYSTORE_FORMAT); + try (FileInputStream stream = new FileInputStream(keystoreFile)) { + keystore.load(stream, keystorePass != null ? keystorePass.toCharArray() : null); + } + SSLContext context = SSLContext.getInstance(protocol); - context.init(getKeyManagers(keystoreFile, keystorePass), getTrustManagers(trustALL, keystoreFile, keystorePass), null); + context.init(getKeyManagers(keystore, keystorePass), getTrustManagers(trustALL, keystore), null); //Util.LOGGER.info("SSLContext based on " + keystoreFile + " loaded successfully."); return context; } + public static SSLContext createSSLContext(String keystoreType, boolean trustALL) + throws GeneralSecurityException, IOException { + + KeyStore keyStore = KeyStore.getInstance(keystoreType); + keyStore.load(null, null); + + SSLContext context = SSLContext.getInstance(SAFE_TRANSPORT_PROTOCOL); + context.init(getKeyManagers(keyStore, null), getTrustManagers(trustALL, keyStore), null); + + return context; + } + // public static SSLContext getDefaultContext() { // SSLContext context; // try { @@ -73,37 +90,23 @@ private static SSLContext createSSLContext(KeyStore keySto return context; } - private static KeyManager[] getKeyManagers(String keystoreFile, String keystorePass) - throws GeneralSecurityException, IOException + private static KeyManager[] getKeyManagers(KeyStore keyStore, String keystorePass) + throws GeneralSecurityException { - KeyManager[] keyManagers = null; - - KeyStore keyStore = KeyStore.getInstance(KEYSTORE_FORMAT); - keyStore.load(new FileInputStream(keystoreFile), keystorePass != null ? keystorePass.toCharArray() : null); - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, keystorePass != null ? keystorePass.toCharArray() : null); - - keyManagers = keyManagerFactory.getKeyManagers(); - - - return keyManagers; + return keyManagerFactory.getKeyManagers(); } - private static TrustManager[] getTrustManagers(boolean trustAll, String keystoreFile, String keystorePass) - throws GeneralSecurityException, IOException + private static TrustManager[] getTrustManagers(boolean trustAll, KeyStore keyStore) + throws GeneralSecurityException { TrustManager[] trustManagers = null; if (!trustAll) { - KeyStore keystore = KeyStore.getInstance(KEYSTORE_FORMAT); - try (FileInputStream stream = new FileInputStream(keystoreFile)) { - keystore.load(stream, keystorePass != null ? keystorePass.toCharArray() : null); - } - //create and init trust manager factory TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(keystore); + trustManagerFactory.init(keyStore); //create trust manager trustManagers = trustManagerFactory.getTrustManagers(); diff --git a/util/src/main/java/com/epam/deltix/util/os/NamedPipe.java b/util/src/main/java/com/epam/deltix/util/os/NamedPipe.java index db53c91d..0c608af3 100644 --- a/util/src/main/java/com/epam/deltix/util/os/NamedPipe.java +++ b/util/src/main/java/com/epam/deltix/util/os/NamedPipe.java @@ -16,9 +16,6 @@ */ package com.epam.deltix.util.os; -import com.epam.deltix.util.lang.Disposable; -import com.epam.deltix.util.lang.Util; - import java.io.*; /** diff --git a/util/src/main/java/com/epam/deltix/util/parsers/SyntaxErrorException.java b/util/src/main/java/com/epam/deltix/util/parsers/SyntaxErrorException.java index 50c607b4..d1ea5c9b 100644 --- a/util/src/main/java/com/epam/deltix/util/parsers/SyntaxErrorException.java +++ b/util/src/main/java/com/epam/deltix/util/parsers/SyntaxErrorException.java @@ -24,6 +24,10 @@ public SyntaxErrorException (long location) { this ("Syntax error", location); } + public SyntaxErrorException(String msg) { + this(msg, Element.NO_LOCATION); + } + public SyntaxErrorException (String msg, long location) { super (msg, location); } diff --git a/util/src/main/java/com/epam/deltix/util/repository/AbstractRepository.java b/util/src/main/java/com/epam/deltix/util/repository/AbstractRepository.java index 08669185..734415f1 100644 --- a/util/src/main/java/com/epam/deltix/util/repository/AbstractRepository.java +++ b/util/src/main/java/com/epam/deltix/util/repository/AbstractRepository.java @@ -20,12 +20,7 @@ import com.epam.deltix.gflog.api.LogFactory; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public abstract class AbstractRepository implements Repository { diff --git a/util/src/main/java/com/epam/deltix/util/text/DateFormatDetector.java b/util/src/main/java/com/epam/deltix/util/text/DateFormatDetector.java index 56b63c71..3f05348d 100644 --- a/util/src/main/java/com/epam/deltix/util/text/DateFormatDetector.java +++ b/util/src/main/java/com/epam/deltix/util/text/DateFormatDetector.java @@ -46,8 +46,9 @@ private enum TPS { SECONDS, BEFORE_MILLIS, MILLIS, + NANOS, STOP - }; + } private static void appendToFormat (char c, StringBuilder out) { if (c == '\'') @@ -232,17 +233,31 @@ else if (StringUtils.endsWith (timeFormat, "ss")) case BEFORE_MILLIS: if (isdigit) state = TPS.MILLIS; - break; - - case MILLIS: - if (!isdigit) - if (StringUtils.endsWith (timeFormat, "SSS")) + break; + + case MILLIS: + if (!isdigit) { + if (StringUtils.endsWith(timeFormat, "SSS")) state = TPS.STOP; else return (false); // Prohibit partial millis to avoid false matches with year! - else if (StringUtils.endsWith (timeFormat, "SSS")) - state = TPS.STOP; - break; + + } else if (StringUtils.endsWith(timeFormat, "SSS")) { + state = TPS.NANOS; + } + break; + + case NANOS: + if (!isdigit) { + if (StringUtils.endsWith(timeFormat, "SSSSSSSSS")) + state = TPS.STOP; + else + return (false); // Prohibit partial nano and micro + + } else if (StringUtils.endsWith(timeFormat, "SSSSSSSSS")) { + state = TPS.STOP; + } + break; } if (c == 0) @@ -257,8 +272,11 @@ else if (StringUtils.endsWith (timeFormat, "SSS")) case MINUTES: timeFormat.append ('m'); break; case SECONDS: timeFormat.append ('s'); break; - case MILLIS: timeFormat.append ('S'); break; - case STOP: + case MILLIS: + case NANOS: + timeFormat.append ('S'); + break; + case STOP: return (false); } else diff --git a/util/src/main/java/com/epam/deltix/util/time/BasicTimeSource.java b/util/src/main/java/com/epam/deltix/util/time/BasicTimeSource.java index 023d8c96..a7e38fe1 100644 --- a/util/src/main/java/com/epam/deltix/util/time/BasicTimeSource.java +++ b/util/src/main/java/com/epam/deltix/util/time/BasicTimeSource.java @@ -38,15 +38,17 @@ public class BasicTimeSource implements TimeSource { private BasicTimeSource() { } + public static BasicTimeSource getInstance() { + return INSTANCE; + } + @Override public long currentTimeMillis() { long currentTime = System.currentTimeMillis(); while (true) { long prevVal = lastTime.get(); - if (prevVal == currentTime) { - return currentTime; - } - if (prevVal > currentTime) { + if (prevVal >= currentTime) { + // Shared value is already ahead (or same). So we can use it and do not need to update shared value. return prevVal; } // currentTime > prevVal diff --git a/util/src/main/java/com/epam/deltix/util/time/KeeperTimeSource.java b/util/src/main/java/com/epam/deltix/util/time/KeeperTimeSource.java index 8e73461c..51a852f3 100644 --- a/util/src/main/java/com/epam/deltix/util/time/KeeperTimeSource.java +++ b/util/src/main/java/com/epam/deltix/util/time/KeeperTimeSource.java @@ -33,6 +33,10 @@ public class KeeperTimeSource implements TimeSource { private KeeperTimeSource() { } + public static KeeperTimeSource getInstance() { + return INSTANCE; + } + @Override public long currentTimeMillis() { return TimeKeeper.currentTime; diff --git a/util/src/main/java/com/epam/deltix/util/time/TimeKeeper.java b/util/src/main/java/com/epam/deltix/util/time/TimeKeeper.java index fae4f49b..fa5162b2 100644 --- a/util/src/main/java/com/epam/deltix/util/time/TimeKeeper.java +++ b/util/src/main/java/com/epam/deltix/util/time/TimeKeeper.java @@ -400,9 +400,20 @@ public void run () { static { if (Util.IS_WINDOWS_OS) { + // Force the Windows system clock into high resolution mode. // - // Force the Windows system clock into fast mode - // Workaround per http://bugs.sun.com/view_bug.do?bug_id=6435126 + // In Windows, the default clock resolution is 15.625ms (1s divided by 64). + // For many of TimeBase use cases is a way too low resolution. + // The simplest way to get a higher timer resolution from Java is to have a thread that "sleeps" forever. + // Under the hood Java will use timeBeginPeriod(1)/timeEndPeriod(1) WinAPI calls to increase timer resolution. + // + // Workaround from https://bugs.openjdk.org/browse/JDK-6435126 + // + // Related articles: + // https://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/ + // https://hazelcast.com/blog/locksupport-parknanos-under-the-hood-and-the-curious-case-of-parking-part-ii-windows/ + // + // See also class TimerFreqTest // Thread magic = new Thread ("Windows System Clock Speeder-Upper") { diff --git a/util/src/test/java/com/epam/deltix/util/io/Test_TokenReplacingReader.java b/util/src/test/java/com/epam/deltix/util/io/Test_TokenReplacingReader.java index e01e1db9..1588b472 100644 --- a/util/src/test/java/com/epam/deltix/util/io/Test_TokenReplacingReader.java +++ b/util/src/test/java/com/epam/deltix/util/io/Test_TokenReplacingReader.java @@ -32,7 +32,7 @@ */ public class Test_TokenReplacingReader { - private final static TokenReplacingReader.TokenResolver NULL_RESOLVER = token -> null; + private final static TokenReplacingReader.ITokenResolver NULL_RESOLVER = token -> null; @Test public void testPartialRead () throws Exception { @@ -74,14 +74,14 @@ private static void assertReplacementEquals(String expectedResult, String text, assertEquals(expectedResult, actualResult); } - private static String replace (String text, TokenReplacingReader.TokenResolver resolver) throws IOException, InterruptedException { + private static String replace (String text, TokenReplacingReader.ITokenResolver resolver) throws IOException, InterruptedException { Reader reader = new TokenReplacingReader(new StringReader(text), resolver); return IOUtil.readFromReader(reader); } //${deltix_home}\\..\\QuantOffice\\Bin - static class MapBasedTokenReplacer implements TokenReplacingReader.TokenResolver { + static class MapBasedTokenReplacer implements TokenReplacingReader.ITokenResolver { private final Map map = new HashMap<>(); From c00f87146b0035956c7327ab35153bbe5af10870 Mon Sep 17 00:00:00 2001 From: Alex Karpovich Date: Thu, 24 Jul 2025 17:57:01 +0300 Subject: [PATCH 2/4] [*] formatter --- .../util/time/DurationFormatUtilEx.java | 480 ++++++++++++++++++ 1 file changed, 480 insertions(+) create mode 100644 util/src/main/java/com/epam/deltix/util/time/DurationFormatUtilEx.java diff --git a/util/src/main/java/com/epam/deltix/util/time/DurationFormatUtilEx.java b/util/src/main/java/com/epam/deltix/util/time/DurationFormatUtilEx.java new file mode 100644 index 00000000..357e57ff --- /dev/null +++ b/util/src/main/java/com/epam/deltix/util/time/DurationFormatUtilEx.java @@ -0,0 +1,480 @@ +package com.epam.deltix.util.time; + +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + + +public class DurationFormatUtilEx { + + public static final long MILLIS_PER_SECOND = 1000L; + public static final long MILLIS_PER_MINUTE = 60000L; + public static final long MILLIS_PER_HOUR = 3600000L; + public static final long MILLIS_PER_DAY = 86400000L; + + public static final int DAYS_IN_MONTH = 30; + public static final int DAYS_IN_YEAR = 365; + + public static final long MILLIS_PER_MONTH = MILLIS_PER_DAY * DAYS_IN_MONTH; + public static final long MILLIS_PER_YEAR = MILLIS_PER_DAY * DAYS_IN_YEAR; + + static final Object y = "y"; + static final Object M = "M"; + static final Object d = "d"; + static final Object H = "H"; + static final Object m = "m"; + static final Object s = "s"; + static final Object S = "S"; + + /** + * Parses a classic date format string into Tokens + * + * @param format + * to parse + * @return Token[] of tokens + */ + static Token[] lexx (final String format) { + final char[] array = format.toCharArray (); + final ArrayList list = new ArrayList (array.length); + + boolean inLiteral = false; + StringBuffer buffer = null; + Token previous = null; + final int sz = array.length; + for (int i = 0; i < sz; i++) { + final char ch = array[i]; + if (inLiteral && ch != '\'') { + buffer.append (ch); + continue; + } + Object value = null; + switch (ch) { + // TODO: Need to handle escaping of ' + case '\'': + if (inLiteral) { + buffer = null; + inLiteral = false; + } else { + buffer = new StringBuffer (); + list.add (new Token (buffer)); + inLiteral = true; + } + break; + case 'y': + value = y; + break; + case 'M': + value = M; + break; + case 'd': + value = d; + break; + case 'H': + value = H; + break; + case 'm': + value = m; + break; + case 's': + value = s; + break; + case 'S': + value = S; + break; + default: + if (buffer == null) { + buffer = new StringBuffer (); + list.add (new Token (buffer)); + } + buffer.append (ch); + } + + if (value != null) { + if (previous != null && previous.getValue () == value) { + previous.increment (); + } else { + final Token token = new Token (value); + list.add (token); + previous = token; + } + buffer = null; + } + } + return list.toArray (new Token[list.size ()]); + } + + /** + * Element that is parsed from the format pattern. + */ + static class Token { + + /** + * Helper method to determine if a set of tokens contain a value + * + * @param tokens + * set to look in + * @param value + * to look for + * @return boolean true if contained + */ + static boolean containsTokenWithValue (final Token[] tokens, + final Object value) { + final int sz = tokens.length; + for (int i = 0; i < sz; i++) { + if (tokens[i].getValue () == value) { + return true; + } + } + return false; + } + + private final Object value; + private int count; + + /** + * Wraps a token around a value. A value would be something like a 'Y'. + * + * @param value + * to wrap + */ + Token (final Object value) { + this.value = value; + this.count = 1; + } + + /** + * Wraps a token around a repeated number of a value, for example it would + * store 'yyyy' as a value for y and a count of 4. + * + * @param value + * to wrap + * @param count + * to wrap + */ + Token (final Object value, + final int count) { + this.value = value; + this.count = count; + } + + /** + * Adds another one of the value + */ + void increment () { + count++; + } + + /** + * Gets the current number of values represented + * + * @return int number of values represented + */ + int getCount () { + return count; + } + + /** + * Gets the particular value this token represents. + * + * @return Object value + */ + Object getValue () { + return value; + } + + /** + * Supports equality of this Token to another Token. + * + * @param obj2 + * Object to consider equality of + * @return boolean true if equal + */ + @Override + public boolean equals (final Object obj2) { + if (obj2 instanceof Token) { + final Token tok2 = (Token) obj2; + if (this.value.getClass () != tok2.value.getClass ()) { + return false; + } + if (this.count != tok2.count) { + return false; + } + if (this.value instanceof StringBuffer) { + return this.value.toString ().equals ( + tok2.value.toString ()); + } else if (this.value instanceof Number) { + return this.value.equals (tok2.value); + } else { + return this.value == tok2.value; + } + } + return false; + } + + /** + * Returns a hashcode for the token equal to the + * hashcode for the token's value. Thus 'TT' and 'TTTT' + * will have the same hashcode. + * + * @return The hashcode for the token + */ + @Override + public int hashCode () { + return this.value.hashCode (); + } + + /** + * Represents this token as a String. + * + * @return String representation of the token + */ + @Override + public String toString () { + return StringUtils.repeat (this.value.toString (), + this.count); + } + } + + public static String formatDuration (final long durationMillis, + final String format) { + return formatDuration (durationMillis, + format, + true); + } + + public static String formatDuration (long durationMillis, + final String format, + final boolean padWithZeros) { + final Token[] tokens = lexx (format); + + int years = 0; + int months = 0; + int days = 0; + int hours = 0; + int minutes = 0; + int seconds = 0; + int milliseconds = 0; + + if (Token.containsTokenWithValue (tokens, + y)) { + years = (int) (durationMillis / MILLIS_PER_YEAR); + durationMillis = durationMillis + - (years * MILLIS_PER_YEAR); + } + if (Token.containsTokenWithValue (tokens, + M)) { + months = (int) (durationMillis / MILLIS_PER_MONTH); + durationMillis = durationMillis + - (months * MILLIS_PER_MONTH); + } + if (Token.containsTokenWithValue (tokens, + d)) { + days = (int) (durationMillis / MILLIS_PER_DAY); + durationMillis = durationMillis + - (days * MILLIS_PER_DAY); + } + if (Token.containsTokenWithValue (tokens, + H)) { + hours = (int) (durationMillis / MILLIS_PER_HOUR); + durationMillis = durationMillis + - (hours * MILLIS_PER_HOUR); + } + if (Token.containsTokenWithValue (tokens, + m)) { + minutes = (int) (durationMillis / MILLIS_PER_MINUTE); + durationMillis = durationMillis + - (minutes * MILLIS_PER_MINUTE); + } + if (Token.containsTokenWithValue (tokens, + s)) { + seconds = (int) (durationMillis / MILLIS_PER_SECOND); + durationMillis = durationMillis + - (seconds * MILLIS_PER_SECOND); + } + if (Token.containsTokenWithValue (tokens, + S)) { + milliseconds = (int) durationMillis; + } + + + return format (tokens, + years, + months, + days, + hours, + minutes, + seconds, + milliseconds, + padWithZeros); + + } + + static String format (final Token[] tokens, + final int years, + final int months, + final int days, + final int hours, + final int minutes, + final int seconds, + int milliseconds, + final boolean padWithZeros) { + final StringBuffer buffer = new StringBuffer (); + boolean lastOutputSeconds = false; + final int sz = tokens.length; + for (int i = 0; i < sz; i++) { + final Token token = tokens[i]; + final Object value = token.getValue (); + final int count = token.getCount (); + if (value instanceof StringBuffer) { + buffer.append (value.toString ()); + } else { + if (value == y) { + buffer.append (padWithZeros ? StringUtils.leftPad (Integer.toString (years), + count, + '0') : Integer.toString (years)); + lastOutputSeconds = false; + } else if (value == M) { + buffer.append (padWithZeros ? StringUtils.leftPad (Integer.toString (months), + count, + '0') : Integer.toString (months)); + lastOutputSeconds = false; + } else if (value == d) { + buffer.append (padWithZeros ? StringUtils.leftPad (Integer.toString (days), + count, + '0') : Integer.toString (days)); + lastOutputSeconds = false; + } else if (value == H) { + buffer.append (padWithZeros ? StringUtils.leftPad (Integer.toString (hours), + count, + '0') : Integer.toString (hours)); + lastOutputSeconds = false; + } else if (value == m) { + buffer.append (padWithZeros ? StringUtils.leftPad (Integer.toString (minutes), + count, + '0') : Integer.toString (minutes)); + lastOutputSeconds = false; + } else if (value == s) { + buffer.append (padWithZeros ? StringUtils.leftPad (Integer.toString (seconds), + count, + '0') : Integer.toString (seconds)); + lastOutputSeconds = true; + } else if (value == S) { + if (lastOutputSeconds) { + milliseconds += 1000; + final String str = padWithZeros ? StringUtils.leftPad (Integer.toString (milliseconds), + count, + '0') : Integer.toString (milliseconds); + buffer.append (str.substring (1)); + } else { + buffer.append (padWithZeros ? StringUtils.leftPad (Integer.toString (milliseconds), + count, + '0') : Integer.toString (milliseconds)); + } + lastOutputSeconds = false; + } + } + } + return buffer.toString (); + } + + public static String formatDurationWords (final long durationMillis, + final boolean suppressLeadingZeroElements, + final boolean suppressTrailingZeroElements) { + String duration = formatDuration (durationMillis, + "y' years 'M' months 'd' days 'H' hours 'm' minutes 's' seconds'"); + if (suppressLeadingZeroElements) { + duration = " " + duration; + String tmp = StringUtils.replaceOnce (duration, + " 0 years", + ""); + if (tmp.length () != duration.length ()) { + duration = tmp; + tmp = StringUtils.replaceOnce (duration, + " 0 months", + ""); + if (tmp.length () != duration.length ()) { + duration = tmp; + tmp = StringUtils.replaceOnce (duration, + " 0 days", + ""); + if (tmp.length () != duration.length ()) { + duration = tmp; + tmp = StringUtils.replaceOnce (duration, + " 0 hours", + ""); + if (tmp.length () != duration.length ()) { + duration = tmp; + tmp = StringUtils.replaceOnce (duration, + " 0 minutes", + ""); + duration = tmp; + if (tmp.length () != duration.length ()) + duration = StringUtils.replaceOnce (tmp, + " 0 seconds", + ""); + } + } + } + } + + if (duration.length () != 0) + duration = duration.substring (1); + } + if (suppressTrailingZeroElements) { + String tmp = StringUtils.replaceOnce (duration, + " 0 seconds", + ""); + if (tmp.length () != duration.length ()) { + duration = tmp; + tmp = StringUtils.replaceOnce (duration, + " 0 minutes", + ""); + if (tmp.length () != duration.length ()) { + duration = tmp; + tmp = StringUtils.replaceOnce (duration, + " 0 hours", + ""); + if (tmp.length () != duration.length ()) { + duration = tmp; + tmp = StringUtils.replaceOnce (tmp, + " 0 days", + ""); + if (tmp.length () != duration.length ()) { + duration = tmp; + tmp = StringUtils.replaceOnce (tmp, + " 0 months", + ""); + if (tmp.length () != duration.length ()) { + duration = tmp; + tmp = StringUtils.replaceOnce (tmp, + " 0 years", + ""); + } + } + } + } + } + } + duration = " " + duration; + duration = StringUtils.replaceOnce (duration, + " 1 seconds", + " 1 second"); + duration = StringUtils.replaceOnce (duration, + " 1 minutes", + " 1 minute"); + duration = StringUtils.replaceOnce (duration, + " 1 hours", + " 1 hour"); + duration = StringUtils.replaceOnce (duration, + " 1 days", + " 1 day"); + duration = StringUtils.replaceOnce (duration, + " 1 months", + " 1 month"); + duration = StringUtils.replaceOnce (duration, + " 1 years", + " 1 year"); + return duration.trim (); + } + +} From ab42bb8a677e249bb4e9805dedf0abd545a8e6ee Mon Sep 17 00:00:00 2001 From: Alex Karpovich Date: Thu, 24 Jul 2025 20:46:49 +0300 Subject: [PATCH 3/4] [*] CI, publishing --- build.gradle | 21 +- buildSrc/build.gradle | 7 + ...typeCentralPortalUploadRepositoryTask.java | 210 ++++++++++++++++++ 3 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 buildSrc/build.gradle create mode 100644 buildSrc/src/main/java/com/epam/deltix/buildsrc/SonatypeCentralPortalUploadRepositoryTask.java diff --git a/build.gradle b/build.gradle index 869cc2db..fba51d1c 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,11 @@ def leafProjects = subprojects.findAll { subproject -> } ext.leafProjects = leafProjects // Publish as external variable +ext { + sonaUser = findProperty('SONATYPE_NEXUS_USERNAME') ?: System.getenv('SONATYPE_NEXUS_USERNAME') ?: "FakeUser" + sonaPass = findProperty('SONATYPE_NEXUS_PASSWORD') ?: System.getenv('SONATYPE_NEXUS_PASSWORD') ?: "FakePass" +} + // Default test categories ext.categories = 'Unit' @@ -288,14 +293,18 @@ configure(leafProjects) { required { isReleaseVersion } } +} -// //tasks.test { enabled = false } -// test { -// useJUnit { -// setIncludeCategories categories.split(',').collect { "deltix.util.CommonsJUnitCategories\$${it}".toString() } as Set -// } -// } +task publishAll(dependsOn: leafProjects.collect { it.path + ":publish" }) { + group 'publishing' + description 'Publish All Artifacts' +} +tasks.register('uploadArtifactsToCentralPortal', com.epam.deltix.buildsrc.SonatypeCentralPortalUploadRepositoryTask) { + portalUsername.set(sonaUser) + portalPassword.set(sonaPass) + groupId.set('com.epam.deltix') + snapshotRelease.set(false) } allprojects { diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 00000000..f4292113 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,7 @@ +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.json:json:20240303' +} diff --git a/buildSrc/src/main/java/com/epam/deltix/buildsrc/SonatypeCentralPortalUploadRepositoryTask.java b/buildSrc/src/main/java/com/epam/deltix/buildsrc/SonatypeCentralPortalUploadRepositoryTask.java new file mode 100644 index 00000000..ee9bfd4d --- /dev/null +++ b/buildSrc/src/main/java/com/epam/deltix/buildsrc/SonatypeCentralPortalUploadRepositoryTask.java @@ -0,0 +1,210 @@ +/* + * Copyright 2014-2025 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.deltix.buildsrc; + +import org.gradle.api.DefaultTask; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.TaskAction; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * This task performs manual steps to publish artifacts to Central Portal via OSSRH Staging API. + */ +public class SonatypeCentralPortalUploadRepositoryTask extends DefaultTask { + private static final String CENTRAL_PORTAL_OSSRH_API_URI = "https://ossrh-staging-api.central.sonatype.com"; + private static final int CONNECTION_TIMEOUT = 30000; + + private final Property portalUsername; + private final Property portalPassword; + private final Property groupId; + private final Property snapshotRelease; + + /** + * Create new task instance. + */ + public SonatypeCentralPortalUploadRepositoryTask() { + portalUsername = getProject().getObjects().property(String.class); + portalPassword = getProject().getObjects().property(String.class); + groupId = getProject().getObjects().property(String.class); + snapshotRelease = getProject().getObjects().property(Boolean.class); + } + + /** + * Return property to set Central Portal username. + * + * @return Central Portal username. + */ + @Input + public Property getPortalUsername() { + return portalUsername; + } + + /** + * Return property to set Central Portal password. + * + * @return Central Portal password. + */ + @Input + public Property getPortalPassword() { + return portalPassword; + } + + /** + * Return property to set {@code groupId} of the project. + * + * @return {@code groupId} of the project. + */ + @Input + public Property getGroupId() { + return groupId; + } + + /** + * Return property to set snapshot release. + * + * @return {@code true} if snapshot release. + */ + @Input + public Property getSnapshotRelease() { + return snapshotRelease; + } + + /** + * Publish staging repository to the Central Portal. + */ + @TaskAction + public void run() throws IOException, InterruptedException { + if (!portalUsername.isPresent()) { + return; + } + + if (snapshotRelease.get()) { + return; + } + + String userNameAndPassword = portalUsername.get() + ":" + portalPassword.get(); + String bearer = Base64.getEncoder().encodeToString(userNameAndPassword.getBytes(StandardCharsets.US_ASCII)); + URI apiUri = URI.create(CENTRAL_PORTAL_OSSRH_API_URI); + + String repositoryKey = findOpenRepository(apiUri, bearer); + uploadRepositoryToPortal(apiUri, bearer, repositoryKey); + dropRepository(apiUri, bearer, repositoryKey); + } + + private String findOpenRepository(URI apiUri, String bearer) throws IOException { + String endpoint = apiUri.resolve("/manual/search/repositories?ip=client").toString(); + HttpURLConnection conn = (HttpURLConnection) new URL(endpoint).openConnection(); + conn.setConnectTimeout(CONNECTION_TIMEOUT); + conn.setReadTimeout(CONNECTION_TIMEOUT); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Authorization", "Bearer " + bearer); + + int status = conn.getResponseCode(); + String body = readBody(conn); + if (status != 200) { + throw new IllegalStateException("Failed to query repositories: " + + "status=" + status + ", response=" + body); + } + + JSONArray repositories = new JSONObject(body).getJSONArray("repositories"); + if (repositories.isEmpty()) { + throw new IllegalStateException("No open repositories found!"); + } + + String repositoryKey = null; + String group = groupId.get(); + for (int i = 0; i < repositories.length(); i++) { + JSONObject repo = (JSONObject) repositories.get(i); + if ("open".equals(repo.getString("state"))) { + String key = repo.getString("key"); + if (key.contains(group)) { + repositoryKey = key; + break; + } + } + } + + if (null == repositoryKey) { + throw new IllegalStateException("No open repositories found!"); + } + return repositoryKey; + } + + private static void uploadRepositoryToPortal(URI apiUri, String bearer, String repositoryKey) throws IOException { + String endpoint = apiUri.resolve("/manual/upload/repository/" + repositoryKey + "?publishing_type=user_managed").toString(); + HttpURLConnection conn = (HttpURLConnection) new URL(endpoint).openConnection(); + conn.setConnectTimeout(CONNECTION_TIMEOUT); + conn.setReadTimeout(CONNECTION_TIMEOUT); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Authorization", "Bearer " + bearer); + conn.setDoOutput(true); + conn.getOutputStream().close(); + + int status = conn.getResponseCode(); + String body = readBody(conn); + if (status != 200) { + throw new IllegalStateException("Failed to upload repository: repository_key=" + repositoryKey + ", status=" + status + ", response=" + body); + } + } + + private static void dropRepository(URI apiUri, String bearer, String repositoryKey) throws IOException { + String endpoint = apiUri.resolve("/manual/drop/repository/" + repositoryKey).toString(); + HttpURLConnection conn = (HttpURLConnection) new URL(endpoint).openConnection(); + conn.setConnectTimeout(CONNECTION_TIMEOUT); + conn.setReadTimeout(CONNECTION_TIMEOUT); + conn.setRequestMethod("DELETE"); + conn.setRequestProperty("Authorization", "Bearer " + bearer); + + int status = conn.getResponseCode(); + String body = readBody(conn); + if (status != 204) { + throw new IllegalStateException("Failed to drop repository: repository_key=" + repositoryKey + ", status=" + status + ", response=" + body); + } + } + + private static String readBody(HttpURLConnection conn) throws IOException { + InputStream stream; + try { + stream = (conn.getResponseCode() < 400) ? conn.getInputStream() : conn.getErrorStream(); + if (stream == null) { + return ""; + } + } catch (IOException e) { + return ""; + } + + StringBuilder body = new StringBuilder(); + try (BufferedReader in = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String line; + while ((line = in.readLine()) != null) { + body.append(line); + } + } + return body.toString(); + } +} From eb498edb564112338a0f697a18f630da8f4c8fc2 Mon Sep 17 00:00:00 2001 From: Alex Karpovich Date: Thu, 24 Jul 2025 21:03:26 +0300 Subject: [PATCH 4/4] [*] CI --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf59c9c2..31acc489 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -103,7 +103,7 @@ jobs: with: java-version: 11 - name: Publish jars - run: ./gradlew :timebase-collections:publish :timebase-lang:publish :timebase-messages:publish :timebase-util:publish + run: ./gradlew publishAll uploadArtifactsToCentralPortal env: SONATYPE_REPOSITORY: ${{ secrets.SONATYPE_REPOSITORY }} SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}