blob: 393bceb9f002cd3fea302042e2650d1b99d12ce1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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 org.apache.datasketches.memory.internal;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* @author Lee Rhodes
*/
final class Util {
static final String LS = System.getProperty("line.separator");
private Util() { }
/**
* Return true if all the masked bits of value are zero
* @param value the value to be tested
* @param bitMask defines the bits of interest
* @return true if all the masked bits of value are zero
*/
static final boolean isAllBitsClear(final long value, final long bitMask) {
return (~value & bitMask) == bitMask;
}
/**
* Return true if all the masked bits of value are one
* @param value the value to be tested
* @param bitMask defines the bits of interest
* @return true if all the masked bits of value are one
*/
static final boolean isAllBitsSet(final long value, final long bitMask) {
return (value & bitMask) == bitMask;
}
/**
* Return true if any the masked bits of value are zero
* @param value the value to be tested
* @param bitMask defines the bits of interest
* @return true if any the masked bits of value are zero
*/
static final boolean isAnyBitsClear(final long value, final long bitMask) {
return (~value & bitMask) != 0;
}
/**
* Return true if any the masked bits of value are one
* @param value the value to be tested
* @param bitMask defines the bits of interest
* @return true if any the masked bits of value are one
*/
static final boolean isAnyBitsSet(final long value, final long bitMask) {
return (value & bitMask) != 0;
}
//These getResource methods are duplicated in Java/ datasketches/common/Util
/**
* Gets the file defined by the given resource file's shortFileName.
* @param shortFileName the last name in the pathname's name sequence.
* @return the file defined by the given resource file's shortFileName.
* @throws URISyntaxException error
* @throws MalformedURLException error
* @throws IOException if an IO error occurs
*/
@SuppressWarnings("resource")
static File getResourceFile(final String shortFileName) throws IOException {
Objects.requireNonNull(shortFileName, "input parameter 'String shortFileName' cannot be null.");
final String slashName = (shortFileName.charAt(0) == '/') ? shortFileName : '/' + shortFileName;
final URL url = Util.class.getResource(slashName);
Objects.requireNonNull(url, "resource " + slashName + " returns null URL.");
File file = createTempFile(slashName);
if (url.getProtocol().equals("jar")) { //definitely a jar
try (final InputStream input = Util.class.getResourceAsStream(slashName);
final OutputStream out = new FileOutputStream(file)) {
Objects.requireNonNull(input, "InputStream is null.");
int numRead = 0;
final byte[] buf = new byte[1024];
while ((numRead = input.read(buf)) != -1) { out.write(buf, 0, numRead); }
}
} else { //protocol says resource is not a jar, must be a file
file = new File(getResourcePath(url));
}
file.setReadOnly();
return file;
}
/**
* Returns a byte array of the contents of the file defined by the given resource file's shortFileName.
* @param shortFileName the last name in the pathname's name sequence.
* @return a byte array of the contents of the file defined by the given resource file's shortFileName.
* @throws IllegalArgumentException if resource cannot be read.
* @throws IOException if an IO error occurs
*/
static byte[] getResourceBytes(final String shortFileName) throws IOException {
Objects.requireNonNull(shortFileName, "input parameter 'String shortFileName' cannot be null.");
final String slashName = (shortFileName.charAt(0) == '/') ? shortFileName : '/' + shortFileName;
final URL url = Util.class.getResource(slashName);
Objects.requireNonNull(url, "resource " + slashName + " returns null URL.");
final byte[] out;
if (url.getProtocol().equals("jar")) { //definitely a jar
try (final InputStream input = Util.class.getResourceAsStream(slashName)) {
out = readAllBytesFromInputStream(input);
}
} else { //protocol says resource is not a jar, must be a file
out = Files.readAllBytes(Paths.get(getResourcePath(url)));
}
return out;
}
/**
* Read all bytes from the given <i>InputStream</i>.
* This is limited to streams that are no longer than the maximum allocatable byte array determined by the VM.
* This may be a little smaller than <i>Integer.MAX_VALUE</i>.
* @param in the Input Stream
* @return byte array
* @throws IOException if an IO error occurs
*/
public static byte[] readAllBytesFromInputStream(final InputStream in) throws IOException {
return readBytesFromInputStream(Integer.MAX_VALUE, in);
}
private static final int BUF_SIZE = 1 << 13;
/**
* Read <i>numBytesToRead</i> bytes from an input stream into a single byte array.
* This is limited to streams that are no longer than the maximum allocatable byte array determined by the VM.
* This may be a little smaller than <i>Integer.MAX_VALUE</i>.
* @param numBytesToRead number of bytes to read
* @param in the InputStream
* @return the filled byte array from the input stream
* @throws IllegalArgumentException if array size grows larger than what can be safely allocated by some VMs.
* @throws IOException if an IO error occurs
*/
public static byte[] readBytesFromInputStream(final int numBytesToRead, final InputStream in) throws IOException {
if (numBytesToRead < 0) { throw new IllegalArgumentException("numBytesToRead must be positive or zero."); }
List<byte[]> buffers = null;
byte[] result = null;
int totalBytesRead = 0;
int remaining = numBytesToRead;
int chunkCnt;
do {
final byte[] partialBuffer = new byte[Math.min(remaining, BUF_SIZE)];
int numRead = 0;
// reads input stream in chunks of partial buffers, stops at EOF or when remaining is zero.
while ((chunkCnt = in.read(partialBuffer, numRead, Math.min(partialBuffer.length - numRead, remaining))) > 0) {
numRead += chunkCnt;
remaining -= chunkCnt;
}
if (numRead > 0) {
if (Integer.MAX_VALUE - Long.BYTES - totalBytesRead < numRead) {
throw new IllegalArgumentException(
"Input stream is larger than what can be safely allocated as a byte[] in some VMs."); }
totalBytesRead += numRead;
if (result == null) {
result = partialBuffer;
} else {
if (buffers == null) {
buffers = new ArrayList<>();
buffers.add(result);
}
buffers.add(partialBuffer);
}
}
} while (chunkCnt >= 0 && remaining > 0);
final byte[] out;
if (buffers == null) {
if (result == null) {
out = new byte[0];
} else {
out = result.length == totalBytesRead ? result : Arrays.copyOf(result, totalBytesRead);
}
return out;
}
result = new byte[totalBytesRead];
int offset = 0;
remaining = totalBytesRead;
for (byte[] b : buffers) {
final int count = Math.min(b.length, remaining);
System.arraycopy(b, 0, result, offset, count);
offset += count;
remaining -= count;
}
return result;
}
private static String getResourcePath(final URL url) { //must not be null
try {
final URI uri = url.toURI();
//decodes any special characters
final String path = uri.isAbsolute() ? Paths.get(uri).toAbsolutePath().toString() : uri.getPath();
return path;
} catch (final URISyntaxException e) {
throw new IllegalArgumentException("Cannot find resource: " + url.toString() + LS + e);
}
}
/**
* Create an empty temporary file.
* On a Mac these files are stored at the system variable $TMPDIR. They should be cleared on a reboot.
* @param shortFileName the name before prefixes and suffixes are added here and by the OS.
* The final extension will be the current extension. The prefix "temp_" is added here.
* @return a temp file,which will be eventually deleted by the OS
* @throws IOException if an IO error occurs
*/
private static File createTempFile(final String shortFileName) throws IOException {
//remove any leading slash
final String resName = (shortFileName.charAt(0) == '/') ? shortFileName.substring(1) : shortFileName;
final String suffix;
final String name;
final int lastIdx = resName.length() - 1;
final int lastIdxOfDot = resName.lastIndexOf('.');
if (lastIdxOfDot == -1) {
suffix = ".tmp";
name = resName;
} else if (lastIdxOfDot == lastIdx) {
suffix = ".tmp";
name = resName.substring(0, lastIdxOfDot);
} else { //has a real suffix
suffix = resName.substring(lastIdxOfDot);
name = resName.substring(0, lastIdxOfDot);
}
final File file = File.createTempFile("temp_" + name, suffix);
return file;
}
}