Skip to content

Commit 4067ffe

Browse files
authored
Merge pull request #750 from apache/update_main_get_resource_file_bytes
Cleans up the org.apache.datasketches.common.Util.java class.
2 parents 73689aa + 1158cc3 commit 4067ffe

2 files changed

Lines changed: 145 additions & 32 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ under the License.
220220
<configuration>
221221
<rules>
222222
<!-- The Java enforcer rule is toolchain unaware
223-
and is only checks the compiler used by the Maven runtime.
223+
and only checks the compiler version used by the Maven runtime.
224224
In the context of toolchains this rule is useless.
225225
See: https://github.com/paulmoloney/maven-enforcer-toolchain-rules
226226
<requireJavaVersion> ... </requireJavaVersion>

src/main/java/org/apache/datasketches/common/Util.java

Lines changed: 144 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,18 @@
2727
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
2828
import static org.apache.datasketches.hash.MurmurHash3.hash;
2929

30+
import java.io.File;
31+
import java.io.IOException;
32+
import java.io.InputStream;
3033
import java.lang.foreign.MemorySegment;
34+
import java.net.URI;
35+
import java.net.URISyntaxException;
36+
import java.net.URL;
3137
import java.nio.ByteOrder;
38+
import java.nio.file.Files;
39+
import java.nio.file.StandardCopyOption;
3240
import java.util.Comparator;
41+
import java.util.Objects;
3342

3443
/**
3544
* Common utility functions.
@@ -45,6 +54,8 @@ public final class Util {
4554
}
4655
}
4756

57+
/** Common static constants ******************/
58+
4859
/**
4960
* The java line separator character as a String.
5061
*/
@@ -100,7 +111,7 @@ public final class Util {
100111

101112
private Util() {}
102113

103-
//Byte Conversions
114+
/** Byte Conversions *************************/
104115

105116
/**
106117
* Returns an int extracted from a Little-Endian byte array.
@@ -162,7 +173,7 @@ public static byte[] longToBytes(final long v, final byte[] arr) {
162173
return arr;
163174
}
164175

165-
//Byte array conversions
176+
/** Byte array conversions *******************/
166177

167178
static long[] convertToLongArray(final byte[] byteArr, final boolean littleEndian) {
168179
final int len = byteArr.length;
@@ -187,7 +198,7 @@ static long[] convertToLongArray(final byte[] byteArr, final boolean littleEndia
187198
return longArr;
188199
}
189200

190-
//String Related
201+
/** String Related ***************************/
191202

192203
/**
193204
* Returns a string of spaced hex bytes in Big-Endian order.
@@ -295,7 +306,24 @@ public static String characterPad(final String s, final int fieldLength, final c
295306
return s;
296307
}
297308

298-
//Memory byte alignment
309+
/**
310+
* Converts the given number to a string prepended with spaces, if necessary, to
311+
* match the given length.
312+
*
313+
* <p>For example, assume a sequence of integers from 1 to 1000. The largest value has
314+
* four decimal digits. Convert the entire sequence of strings to the form " 1" to "1000".
315+
* When these strings are sorted they will be in numerical sequence: " 1", " 2", ... "1000".</p>
316+
*
317+
* @param number the given number
318+
* @param length the desired string length.
319+
* @return the given number to a string prepended with spaces
320+
*/
321+
public static String longToFixedLengthString(final long number, final int length) {
322+
final String num = Long.toString(number);
323+
return characterPad(num, length, ' ', false);
324+
}
325+
326+
/** Memory byte alignment ********************/
299327

300328
/**
301329
* Checks if parameter v is a multiple of 8 and greater than zero.
@@ -319,7 +347,7 @@ public static boolean isMultipleOf8AndGT0(final long v) {
319347
return ((v & 0X7L) == 0L) && (v > 0L);
320348
}
321349

322-
//Powers of 2 or powers of base related
350+
/** Powers of 2 or powers of base related ****/
323351

324352
/**
325353
* Returns true if given long argument is exactly a positive power of 2.
@@ -591,8 +619,8 @@ public static double floorPowerBaseOfDouble(final double base, final double n) {
591619
return Math.round(pow(base, floor(logBaseOfX(base, x))));
592620
}
593621

594-
// Logarithm related
595-
622+
/** Logarithm related ************************/
623+
596624
/**
597625
* The log<sub>2</sub>(value)
598626
* @param value the given value
@@ -690,8 +718,8 @@ public static int exactLog2OfLong(final long powerOf2) {
690718
return Long.numberOfTrailingZeros(powerOf2);
691719
}
692720

693-
//Checks that throw
694-
721+
/** Checks that throw ************************/
722+
695723
/**
696724
* Check the requested offset and length against the allocated size.
697725
* The invariants equation is: {@code 0 <= reqOff <= reqLen <= reqOff + reqLen <= allocSize}.
@@ -724,7 +752,7 @@ public static void checkProbability(final double p, final String argName) {
724752
+ "\" must be between 0.0 inclusive and 1.0 inclusive: " + p);
725753
}
726754

727-
//Boolean Checks
755+
/** Boolean Checks ***************************/
728756

729757
/**
730758
* Unsigned compare with longs.
@@ -754,7 +782,7 @@ public static boolean isOdd(final long n) {
754782
return (n & 1L) == 1L;
755783
}
756784

757-
//Other
785+
/** Bit Number ******************************/
758786

759787
/**
760788
* Returns a one if the bit at bitPos is a one, otherwise zero.
@@ -767,6 +795,8 @@ public static int bitAt(final long number, final int bitPos) {
767795
return (number & (1L << bitPos)) > 0 ? 1 : 0;
768796
}
769797

798+
/** Decimal Digits ***************************/
799+
770800
/**
771801
* Computes the number of decimal digits of the number n
772802
* @param n the given number
@@ -777,25 +807,8 @@ public static int numDigits(long n) {
777807
return (int) ceil(log(n) / log(10));
778808
}
779809

780-
/**
781-
* Converts the given number to a string prepended with spaces, if necessary, to
782-
* match the given length.
783-
*
784-
* <p>For example, assume a sequence of integers from 1 to 1000. The largest value has
785-
* four decimal digits. Convert the entire sequence of strings to the form " 1" to "1000".
786-
* When these strings are sorted they will be in numerical sequence: " 1", " 2", ... "1000".</p>
787-
*
788-
* @param number the given number
789-
* @param length the desired string length.
790-
* @return the given number to a string prepended with spaces
791-
*/
792-
public static String longToFixedLengthString(final long number, final int length) {
793-
final String num = Long.toString(number);
794-
return characterPad(num, length, ' ', false);
795-
}
796-
797-
//Generic tests
798-
810+
/** Generic relational tests *****************/
811+
799812
/**
800813
* Finds the minimum of two generic items
801814
* @param <T> the type
@@ -844,7 +857,7 @@ public static <T> boolean le(final Object item1, final Object item2, final Compa
844857
return c.compare((T)item1, (T)item2) <= 0;
845858
}
846859

847-
//MemorySegment related
860+
/** MemorySegment related ********************/
848861

849862
/**
850863
* Clears all bytes of this MemorySegment to zero.
@@ -957,6 +970,8 @@ public static void setBits(final MemorySegment seg, final long offsetBytes, fina
957970
seg.set(JAVA_BYTE, offsetBytes, (byte)(b | bitMask));
958971
}
959972

973+
/** Seed Hashes ******************************/
974+
960975
/**
961976
* Computes and checks the 16-bit seed hash from the given long seed.
962977
* The seed hash may not be zero in order to maintain compatibility with older serialized
@@ -989,5 +1004,103 @@ public static short checkSeedHashes(final short seedHashA, final short seedHashB
9891004
}
9901005
return seedHashA;
9911006
}
1007+
1008+
/** Files and File Bytes ******************************/
1009+
1010+
/**
1011+
* Windows, POSIX, and JAR friendly, returns a byte array of the contents of the file defined by the given
1012+
* resourceName.
1013+
* If the resource is in a JAR it will be copied into the File System as a temporary file first.
1014+
*
1015+
* @param resourceName the short name or the full path name.
1016+
* @return a byte array of the contents of the file defined by the given resourceName.
1017+
*/
1018+
public static byte[] getResourceBytes(final String resourceName) {
1019+
Objects.requireNonNull(resourceName, "Given resourceName must not be null");
1020+
1021+
String normalizedName = resourceName.replace('\\', '/');
1022+
if (normalizedName.startsWith("/")) {
1023+
normalizedName = normalizedName.substring(1);
1024+
}
1025+
1026+
final ClassLoader loader = Util.class.getClassLoader();
1027+
try (InputStream in = loader.getResourceAsStream(normalizedName)) {
1028+
if (in == null) {
1029+
throw new IllegalArgumentException("Resource not found: " + normalizedName);
1030+
}
1031+
return in.readAllBytes();
1032+
} catch (final IOException e) {
1033+
throw new IllegalArgumentException("Cannot read resource: " + normalizedName + Util.LS + e);
1034+
}
1035+
}
1036+
1037+
/**
1038+
* Windows, POSIX, and JAR friendly get Resource File.
1039+
* If the resource is in a JAR it will be copied into the File System as a temporary file first.
1040+
*
1041+
* <p>While tempFile.deleteOnExit() works, keep in mind that JVMs running long processes won't delete those files
1042+
* until the JVM completely terminates, which can lead to memory/disk leaks if this method is called frequently.
1043+
* If you're using this for memory-mapped files, consider explicitly deleting the file once the memory-mapped buffer
1044+
* is no longer in use, or registering a custom shutdown hook if deleteOnExit() is insufficient.</p>
1045+
* @param resourceName the simple file name or full path name.
1046+
* Any back-slashes will be converted to forward slashes and a leading forward slash will be removed.
1047+
* No other special characters allowed.
1048+
* @return a File System File
1049+
*/
1050+
public static File getResourceFile(final String resourceName) {
1051+
Objects.requireNonNull(resourceName, "Given resourceName must not be null");
1052+
if (resourceName.isEmpty()) { throw new IllegalArgumentException("Given resourceName must not be empty"); }
1053+
// Normalize name: ClassLoaders MUST use forward slashes even on Windows
1054+
String normalizedName = resourceName.replace('\\', '/');
1055+
if (normalizedName.startsWith("/")) { normalizedName = normalizedName.substring(1); }
1056+
1057+
final ClassLoader loader = Util.class.getClassLoader();
1058+
final URL url = loader.getResource(normalizedName);
1059+
if (url == null) { throw new IllegalArgumentException("Resource not found: " + normalizedName); }
1060+
1061+
// If it's a real file, return it directly
1062+
if ("file".equals(url.getProtocol())) {
1063+
try {
1064+
final URI uri = url.toURI();
1065+
return new File(uri); }
1066+
catch (final URISyntaxException e) { return new File(url.getPath()); }
1067+
}
1068+
1069+
// If it's in a JAR, we must extract it for Memory.map() to work
1070+
// We use a prefix that won't collide with Windows reserved names
1071+
// We use NIO Files.createTempFile to ensure secure default permissions (0600)
1072+
final File tempFile;
1073+
try { tempFile = Files.createTempFile("datasketches-", ".bin").toFile(); }
1074+
catch (final IOException e1) { throw new IllegalArgumentException(e1); }
1075+
tempFile.deleteOnExit();
1076+
1077+
try (InputStream in = loader.getResourceAsStream(normalizedName)) {
1078+
if (in == null) { throw new IllegalArgumentException("Could not open stream for " + normalizedName); }
1079+
1080+
// Use REPLACE_EXISTING to avoid "File Already Exists" errors on Windows retries
1081+
Files.copy(in, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
1082+
} catch (final IOException e) { throw new IllegalArgumentException(e); }
1083+
1084+
// Final Windows Fix: Ensure the file is actually writable if you need to setReadOnly later
1085+
//tempFile.setWritable(true);
1086+
1087+
return tempFile;
1088+
}
1089+
1090+
/**
1091+
* Windows, POSIX, and JAR friendly, checks if the given resourceName exists and sets it to Read-Only.
1092+
* If the resource is in a JAR it will be copied into the File System as a temporary file first.
1093+
* This will not work if the file is currently memory-mapped.
1094+
* If it is memory-mapped, close the mapping first.
1095+
* @param resourceName the given resource.
1096+
* @return the read only file.
1097+
*/
1098+
public static File setResourceReadOnly(final String resourceName) {
1099+
final File file = getResourceFile(resourceName);
1100+
if (!file.setReadOnly()) {
1101+
throw new IllegalArgumentException("Could not set read-only for resource file: " + file.getAbsolutePath());
1102+
}
1103+
return file;
1104+
}
9921105

9931106
}

0 commit comments

Comments
 (0)