/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.core;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class TimeValue implements Comparable<TimeValue> {
/** How many nano-seconds in one milli-second */
public static final long NSEC_PER_MSEC = TimeUnit.NANOSECONDS.convert(1, TimeUnit.MILLISECONDS);
public static final TimeValue MINUS_ONE = timeValueMillis(-1);
public static final TimeValue ZERO = timeValueMillis(0);
public static final TimeValue MAX_VALUE = TimeValue.timeValueNanos(Long.MAX_VALUE);
private static final long C0 = 1L;
private static final long C1 = C0 * 1000L;
private static final long C2 = C1 * 1000L;
private static final long C3 = C2 * 1000L;
private static final long C4 = C3 * 60L;
private static final long C5 = C4 * 60L;
private static final long C6 = C5 * 24L;
private final long duration;
private final TimeUnit timeUnit;
public TimeValue(long millis) {
this(millis, TimeUnit.MILLISECONDS);
}
public TimeValue(long duration, TimeUnit timeUnit) {
if (duration < -1) {
throw new IllegalArgumentException("duration cannot be negative, was given [" + duration + "]");
}
this.duration = duration;
this.timeUnit = timeUnit;
}
public static TimeValue timeValueNanos(long nanos) {
return new TimeValue(nanos, TimeUnit.NANOSECONDS);
}
public static TimeValue timeValueMillis(long millis) {
return new TimeValue(millis, TimeUnit.MILLISECONDS);
}
public static TimeValue timeValueSeconds(long seconds) {
return new TimeValue(seconds, TimeUnit.SECONDS);
}
public static TimeValue timeValueMinutes(long minutes) {
return new TimeValue(minutes, TimeUnit.MINUTES);
}
public static TimeValue timeValueHours(long hours) {
return new TimeValue(hours, TimeUnit.HOURS);
}
public static TimeValue timeValueDays(long days) {
// 106751.9 days is Long.MAX_VALUE nanoseconds, so we cannot store 106752 days
if (days > 106751) {
throw new IllegalArgumentException("time value cannot store values greater than 106751 days");
}
return new TimeValue(days, TimeUnit.DAYS);
}
/**
* @return the unit used for the this time value, see {@link #duration()}
*/
public TimeUnit timeUnit() {
return timeUnit;
}
/**
* @return the number of {@link #timeUnit()} units this value contains
*/
public long duration() {
return duration;
}
public long nanos() {
return timeUnit.toNanos(duration);
}
public long getNanos() {
return nanos();
}
public long micros() {
return timeUnit.toMicros(duration);
}
public long getMicros() {
return micros();
}
public long millis() {
return timeUnit.toMillis(duration);
}
public long getMillis() {
return millis();
}
public long seconds() {
return timeUnit.toSeconds(duration);
}
public long getSeconds() {
return seconds();
}
public long minutes() {
return timeUnit.toMinutes(duration);
}
public long getMinutes() {
return minutes();
}
public long hours() {
return timeUnit.toHours(duration);
}
public long getHours() {
return hours();
}
public long days() {
return timeUnit.toDays(duration);
}
public long getDays() {
return days();
}
public double microsFrac() {
return ((double) nanos()) / C1;
}
public double getMicrosFrac() {
return microsFrac();
}
public double millisFrac() {
return ((double) nanos()) / C2;
}
public double getMillisFrac() {
return millisFrac();
}
public double secondsFrac() {
return ((double) nanos()) / C3;
}
public double getSecondsFrac() {
return secondsFrac();
}
public double minutesFrac() {
return ((double) nanos()) / C4;
}
public double getMinutesFrac() {
return minutesFrac();
}
public double hoursFrac() {
return ((double) nanos()) / C5;
}
public double getHoursFrac() {
return hoursFrac();
}
public double daysFrac() {
return ((double) nanos()) / C6;
}
public double getDaysFrac() {
return daysFrac();
}
/**
* Returns a {@link String} representation of the current {@link TimeValue}.
*
* Note that this method might produce fractional time values (ex 1.6m) which cannot be
* parsed by method like {@link TimeValue#parse(String, String, String, String)}.
*
* Also note that the maximum string value that will be generated is
* {@code 106751.9d} due to the way that values are internally converted
* to nanoseconds (106751.9 days is Long.MAX_VALUE nanoseconds)
*/
@Override
public String toString() {
return this.toHumanReadableString(1);
}
/**
* Returns a {@link String} representation of the current {@link TimeValue}.
*
* Note that this method might produce fractional time values (ex 1.6m) which cannot be
* parsed by method like {@link TimeValue#parse(String, String, String, String)}. The number of
* fractional decimals (up to 10 maximum) are truncated to the number of fraction pieces
* specified.
*
* Also note that the maximum string value that will be generated is
* {@code 106751.9d} due to the way that values are internally converted
* to nanoseconds (106751.9 days is Long.MAX_VALUE nanoseconds)
*
* @param fractionPieces the number of decimal places to include
*/
public String toHumanReadableString(int fractionPieces) {
if (duration < 0) {
return Long.toString(duration);
}
long nanos = nanos();
if (nanos == 0) {
return "0s";
}
double value = nanos;
String suffix = "nanos";
if (nanos >= C6) {
value = daysFrac();
suffix = "d";
} else if (nanos >= C5) {
value = hoursFrac();
suffix = "h";
} else if (nanos >= C4) {
value = minutesFrac();
suffix = "m";
} else if (nanos >= C3) {
value = secondsFrac();
suffix = "s";
} else if (nanos >= C2) {
value = millisFrac();
suffix = "ms";
} else if (nanos >= C1) {
value = microsFrac();
suffix = "micros";
}
// Limit fraction pieces to a min of 0 and maximum of 10
return formatDecimal(value, Math.min(10, Math.max(0, fractionPieces))) + suffix;
}
private static String formatDecimal(double value, int fractionPieces) {
String p = String.valueOf(value);
int totalLength = p.length();
int ix = p.indexOf('.') + 1;
int ex = p.indexOf('E');
// Location where the fractional values end
int fractionEnd = ex == -1 ? Math.min(ix + fractionPieces, totalLength) : ex;
// Determine the value of the fraction, so if it were .000 the
// actual long value is 0, in which case, it can be elided.
long fractionValue;
try {
fractionValue = Long.parseLong(p.substring(ix, fractionEnd));
} catch (NumberFormatException e) {
fractionValue = 0;
}
if (fractionValue == 0 || fractionPieces <= 0) {
// Either the part of the fraction we were asked to report is
// zero, or the user requested 0 fraction pieces, so return
// only the integral value
if (ex != -1) {
return p.substring(0, ix - 1) + p.substring(ex);
} else {
return p.substring(0, ix - 1);
}
} else {
// Build up an array of fraction characters, without going past
// the end of the string. This keeps track of trailing '0' chars
// that should be truncated from the end to avoid getting a
// string like "1.3000d" (returning "1.3d" instead) when the
// value is 1.30000009
char[] fractions = new char[fractionPieces];
int fracCount = 0;
int truncateCount = 0;
for (int i = 0; i < fractionPieces; i++) {
int position = ix + i;
if (position >= fractionEnd) {
// No more pieces, the fraction has ended
break;
}
char fraction = p.charAt(position);
if (fraction == '0') {
truncateCount++;
} else {
truncateCount = 0;
}
fractions[i] = fraction;
fracCount++;
}
// Generate the fraction string from the char array, truncating any trailing zeros
String fractionStr = new String(fractions, 0, fracCount - truncateCount);
if (ex != -1) {
return p.substring(0, ix) + fractionStr + p.substring(ex);
} else {
return p.substring(0, ix) + fractionStr;
}
}
}
public String getStringRep() {
if (duration < 0) {
return Long.toString(duration);
}
switch (timeUnit) {
case NANOSECONDS:
return duration + "nanos";
case MICROSECONDS:
return duration + "micros";
case MILLISECONDS:
return duration + "ms";
case SECONDS:
return duration + "s";
case MINUTES:
return duration + "m";
case HOURS:
return duration + "h";
case DAYS:
return duration + "d";
default:
throw new IllegalArgumentException("unknown time unit: " + timeUnit.name());
}
}
public static TimeValue parseTimeValue(String sValue, String settingName) {
Objects.requireNonNull(settingName);
Objects.requireNonNull(sValue);
return parseTimeValue(sValue, null, settingName);
}
public static TimeValue parseTimeValue(String sValue, TimeValue defaultValue, String settingName) {
settingName = Objects.requireNonNull(settingName);
if (sValue == null) {
return defaultValue;
}
final String normalized = sValue.toLowerCase(Locale.ROOT).trim();
if (normalized.endsWith("nanos")) {
return new TimeValue(parse(sValue, normalized, "nanos", settingName), TimeUnit.NANOSECONDS);
} else if (normalized.endsWith("micros")) {
return new TimeValue(parse(sValue, normalized, "micros", settingName), TimeUnit.MICROSECONDS);
} else if (normalized.endsWith("ms")) {
return new TimeValue(parse(sValue, normalized, "ms", settingName), TimeUnit.MILLISECONDS);
} else if (normalized.endsWith("s")) {
return new TimeValue(parse(sValue, normalized, "s", settingName), TimeUnit.SECONDS);
} else if (sValue.endsWith("m")) {
// parsing minutes should be case-sensitive as 'M' means "months", not "minutes"; this is the only special case.
return new TimeValue(parse(sValue, normalized, "m", settingName), TimeUnit.MINUTES);
} else if (normalized.endsWith("h")) {
return new TimeValue(parse(sValue, normalized, "h", settingName), TimeUnit.HOURS);
} else if (normalized.endsWith("d")) {
return new TimeValue(parse(sValue, normalized, "d", settingName), TimeUnit.DAYS);
} else if (normalized.matches("-0*1")) {
return TimeValue.MINUS_ONE;
} else if (normalized.matches("0+")) {
return TimeValue.ZERO;
} else {
// Missing units:
throw new IllegalArgumentException(
"failed to parse setting [" + settingName + "] with value [" + sValue + "] as a time value: unit is missing or unrecognized"
);
}
}
private static long parse(final String initialInput, final String normalized, final String suffix, String settingName) {
final String s = normalized.substring(0, normalized.length() - suffix.length()).trim();
try {
final long value = Long.parseLong(s);
if (value < -1) {
// -1 is magic, but reject any other negative values
throw new IllegalArgumentException(
"failed to parse setting ["
+ settingName
+ "] with value ["
+ initialInput
+ "] as a time value: negative durations are not supported"
);
}
return value;
} catch (final NumberFormatException e) {
try {
@SuppressWarnings("unused")
final double ignored = Double.parseDouble(s);
throw new IllegalArgumentException("failed to parse [" + initialInput + "], fractional time values are not supported", e);
} catch (final NumberFormatException ignored) {
throw new IllegalArgumentException("failed to parse [" + initialInput + "]", e);
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return this.compareTo(((TimeValue) o)) == 0;
}
@Override
public int hashCode() {
return Double.hashCode(((double) duration) * timeUnit.toNanos(1));
}
public static long nsecToMSec(long ns) {
return ns / NSEC_PER_MSEC;
}
@Override
public int compareTo(TimeValue timeValue) {
double thisValue = ((double) duration) * timeUnit.toNanos(1);
double otherValue = ((double) timeValue.duration) * timeValue.timeUnit.toNanos(1);
return Double.compare(thisValue, otherValue);
}
}
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.xcontent;
import org.elasticsearch.xcontent.cbor.CborXContent;
import org.elasticsearch.xcontent.json.JsonXContent;
import org.elasticsearch.xcontent.smile.SmileXContent;
import org.elasticsearch.xcontent.yaml.YamlXContent;
import java.util.Locale;
import java.util.Objects;
/**
* The content type of {@link org.elasticsearch.xcontent.XContent}.
*/
public enum XContentType {
/**
* A JSON based content type.
*/
JSON(0) {
@Override
public String mediaTypeWithoutParameters() {
return "application/json";
}
@Override
public String mediaType() {
return "application/json; charset=UTF-8";
}
@Override
public String shortName() {
return "json";
}
@Override
public XContent xContent() {
return JsonXContent.jsonXContent;
}
},
/**
* The jackson based smile binary format. Fast and compact binary format.
*/
SMILE(1) {
@Override
public String mediaTypeWithoutParameters() {
return "application/smile";
}
@Override
public String shortName() {
return "smile";
}
@Override
public XContent xContent() {
return SmileXContent.smileXContent;
}
},
/**
* A YAML based content type.
*/
YAML(2) {
@Override
public String mediaTypeWithoutParameters() {
return "application/yaml";
}
@Override
public String shortName() {
return "yaml";
}
@Override
public XContent xContent() {
return YamlXContent.yamlXContent;
}
},
/**
* A CBOR based content type.
*/
CBOR(3) {
@Override
public String mediaTypeWithoutParameters() {
return "application/cbor";
}
@Override
public String shortName() {
return "cbor";
}
@Override
public XContent xContent() {
return CborXContent.cborXContent;
}
};
/**
* Accepts either a format string, which is equivalent to {@link XContentType#shortName()} or a media type that optionally has
* parameters and attempts to match the value to an {@link XContentType}. The comparisons are done in lower case format and this method
* also supports a wildcard accept for {@code application/*}. This method can be used to parse the {@code Accept} HTTP header or a
* format query string parameter. This method will return {@code null} if no match is found
*/
public static XContentType fromMediaTypeOrFormat(String mediaType) {
if (mediaType == null) {
return null;
}
mediaType = removeVersionInMediaType(mediaType);
for (XContentType type : values()) {
if (isSameMediaTypeOrFormatAs(mediaType, type)) {
return type;
}
}
final String lowercaseMediaType = mediaType.toLowerCase(Locale.ROOT);
if (lowercaseMediaType.startsWith("application/*")) {
return JSON;
}
return null;
}
/**
* Clients compatible with ES 7.x might start sending media types with versioned media type
* in a form of application/vnd.elasticsearch+json;compatible-with=7.
* This has to be removed in order to be used in 7.x server.
* The same client connecting using that media type will be able to communicate with ES 8 thanks to compatible API.
* @param mediaType - a media type used on Content-Type header, might contain versioned media type.
*
* @return a media type string without
*/
private static String removeVersionInMediaType(String mediaType) {
if (mediaType.contains("vnd.elasticsearch")) {
return mediaType.replaceAll("vnd.elasticsearch\\+", "")
.replaceAll("\\s*;\\s*compatible-with=\\d+", "")
.replaceAll("\\s*;\\s*", ";"); // to allow matching using startsWith
}
return mediaType;
}
/**
* Attempts to match the given media type with the known {@link XContentType} values. This match is done in a case-insensitive manner.
* The provided media type should not include any parameters. This method is suitable for parsing part of the {@code Content-Type}
* HTTP header. This method will return {@code null} if no match is found
*/
public static XContentType fromMediaType(String mediaType) {
String lowercaseMediaType = Objects.requireNonNull(mediaType, "mediaType cannot be null").toLowerCase(Locale.ROOT);
lowercaseMediaType = removeVersionInMediaType(lowercaseMediaType);
for (XContentType type : values()) {
if (type.mediaTypeWithoutParameters().equals(lowercaseMediaType)) {
return type;
}
}
// we also support newline delimited JSON: http://specs.okfnlabs.org/ndjson/
if (lowercaseMediaType.toLowerCase(Locale.ROOT).equals("application/x-ndjson")) {
return XContentType.JSON;
}
return null;
}
private static boolean isSameMediaTypeOrFormatAs(String stringType, XContentType type) {
return type.mediaTypeWithoutParameters().equalsIgnoreCase(stringType)
|| stringType.toLowerCase(Locale.ROOT).startsWith(type.mediaTypeWithoutParameters().toLowerCase(Locale.ROOT) + ";")
|| type.shortName().equalsIgnoreCase(stringType);
}
private int index;
XContentType(int index) {
this.index = index;
}
public int index() {
return index;
}
public String mediaType() {
return mediaTypeWithoutParameters();
}
public abstract String shortName();
public abstract XContent xContent();
public abstract String mediaTypeWithoutParameters();
}