renamed modules dir

This commit is contained in:
2022-07-20 13:28:01 +02:00
parent 7dcd92f72d
commit d2ca501203
205 changed files with 12 additions and 12 deletions

19
pandalib-util/pom.xml Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>pandalib-parent</artifactId>
<groupId>fr.pandacube.lib</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pandalib-util</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>

View File

@@ -0,0 +1,57 @@
package fr.pandacube.lib.util;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class AmountPerTimeLimiter {
private final double maxAmount;
private final long duration;
private List<Entry> entries = new ArrayList<>();
public AmountPerTimeLimiter(double a, long d) {
maxAmount = a;
duration = d;
}
private static class Entry {
private final long time;
private double amount;
public Entry(long t, double a) {
time = t; amount = a;
}
}
public synchronized double getAmountSinceDuration() {
long currT = System.currentTimeMillis();
entries = entries.stream()
.filter(e -> e.time > currT - duration)
.collect(Collectors.toList());
return entries.stream()
.mapToDouble(e -> e.amount)
.sum();
}
public synchronized void add(double amount) {
long currT = System.currentTimeMillis();
if (!entries.isEmpty() && entries.get(entries.size()-1).time > currT - 1000)
entries.get(entries.size()-1).amount += amount;
else
entries.add(new Entry(System.currentTimeMillis(), amount));
}
public boolean canAdd(double amount) {
return getAmountSinceDuration() + amount < maxAmount;
}
public double getRemainingForNow() {
return Math.max(0, maxAmount - getAmountSinceDuration());
}
}

View File

@@ -0,0 +1,125 @@
package fr.pandacube.lib.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class BiMap<K, V> implements Iterable<Entry<K, V>> {
protected final Map<K, V> map;
protected final Map<V, K> inversedMap;
private BiMap<V, K> reversedView = null;
@SuppressWarnings("unchecked")
public BiMap(Supplier<Map<?, ?>> mapSupplier) {
map = (Map<K, V>) mapSupplier.get();
inversedMap = (Map<V, K>) mapSupplier.get();
}
public BiMap() {
this(HashMap::new);
}
public BiMap(Map<K, V> source) {
this();
putAll(source);
}
/*
* Only used for #reversedView()
*/
private BiMap(BiMap<V, K> rev) {
map = rev.inversedMap;
inversedMap = rev.map;
reversedView = rev;
}
public synchronized void put(K k, V v) {
if (containsKey(k))
remove(k);
if (containsValue(v))
removeValue(v);
map.put(k, v);
inversedMap.put(v, k);
}
public synchronized void putAll(Map<? extends K, ? extends V> source) {
for (Map.Entry<? extends K, ? extends V> e : source.entrySet()) {
put(e.getKey(), e.getValue());
}
}
public synchronized V get(K k) {
return map.get(k);
}
public synchronized K getKey(V v) {
return inversedMap.get(v);
}
public synchronized boolean containsKey(K k) {
return map.containsKey(k);
}
public synchronized boolean containsValue(V v) {
return inversedMap.containsKey(v);
}
public synchronized V remove(K k) {
V v = map.remove(k);
inversedMap.remove(v);
return v;
}
public synchronized K removeValue(V v) {
K k = inversedMap.remove(v);
map.remove(k);
return k;
}
@Override
public Iterator<Entry<K, V>> iterator() {
return Collections.unmodifiableSet(map.entrySet()).iterator();
}
public Set<K> keySet() {
return Collections.unmodifiableSet(map.keySet());
}
public Set<V> valuesSet() {
return Collections.unmodifiableSet(inversedMap.keySet());
}
public Map<K, V> asMap() {
return Collections.unmodifiableMap(map);
}
public BiMap<V, K> reversedView() {
if (reversedView == null)
reversedView = new BiMap<>(this);
return reversedView;
}
public synchronized void forEach(BiConsumer<K, V> c) {
for(Entry<K, V> entry : this) {
c.accept(entry.getKey(), entry.getValue());
}
}
public int size() {
return map.size();
}
public synchronized void clear() {
map.clear();
inversedMap.clear();
}
}

View File

@@ -0,0 +1,642 @@
package fr.pandacube.lib.util;
// from https://github.com/frode-carlsen/cron/blob/master/java8/src/main/java/fc/cron/CronExpression.java
// if there are changes, indicate them here (there is none currently)
/*
* Copyright (C) 2012- Frode Carlsen.
*
* 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
*
* 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.
*
* Note: rewritten to standard Java 8 DateTime by zemiak (c) 2016
*/
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This provides cron support for java8 using java-time.
* <P>
*
* Parser for unix-like cron expressions: Cron expressions allow specifying combinations of criteria for time
* such as: &quot;Each Monday-Friday at 08:00&quot; or &quot;Every last friday of the month at 01:30&quot;
* <p>
* A cron expressions consists of 5 or 6 mandatory fields (seconds may be omitted) separated by space. <br>
* These are:
*
* <table cellspacing="8">
* <tr>
* <th align="left">Field</th>
* <th align="left">&nbsp;</th>
* <th align="left">Allowable values</th>
* <th align="left">&nbsp;</th>
* <th align="left">Special Characters</th>
* </tr>
* <tr>
* <td align="left"><code>Seconds (may be omitted)</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>0-59</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Minutes</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>0-59</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Hours</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>0-23</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Day of month</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>1-31</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * ? / L W</code></td>
* </tr>
* <tr>
* <td align="left"><code>Month</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>1-12 or JAN-DEC (note: english abbreviations)</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Day of week</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>1-7 or MON-SUN (note: english abbreviations)</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * ? / L #</code></td>
* </tr>
* </table>
*
* <P>
* '*' Can be used in all fields and means 'for all values'. E.g. &quot;*&quot; in minutes, means 'for all minutes'
* <P>
* '?' Can be used in Day-of-month and Day-of-week fields. Used to signify 'no special value'. It is used when one want
* to specify something for one of those two fields, but not the other.
* <P>
* '-' Used to specify a time interval. E.g. &quot;10-12&quot; in Hours field means 'for hours 10, 11 and 12'
* <P>
* ',' Used to specify multiple values for a field. E.g. &quot;MON,WED,FRI&quot; in Day-of-week field means &quot;for
* monday, wednesday and friday&quot;
* <P>
* '/' Used to specify increments. E.g. &quot;0/15&quot; in Seconds field means &quot;for seconds 0, 15, 30, ad
* 45&quot;. And &quot;5/15&quot; in seconds field means &quot;for seconds 5, 20, 35, and 50&quot;. If '*' s specified
* before '/' it is the same as saying it starts at 0. For every field there's a list of values that can be turned on or
* off. For Seconds and Minutes these range from 0-59. For Hours from 0 to 23, For Day-of-month it's 1 to 31, For Months
* 1 to 12. &quot;/&quot; character helsp turn some of these values back on. Thus &quot;7/6&quot; in Months field
* specify just Month 7. It doesn't turn on every 6 month following, since cron fields never roll over
* <P>
* 'L' Can be used on Day-of-month and Day-of-week fields. It signifies last day of the set of allowed values. In
* Day-of-month field it's the last day of the month (e.g.. 31 jan, 28 feb (29 in leap years), 31 march, etc.). In
* Day-of-week field it's Sunday. If there's a prefix, this will be subtracted (5L in Day-of-month means 5 days before
* last day of Month: 26 jan, 23 feb, etc.)
* <P>
* 'W' Can be specified in Day-of-Month field. It specifies closest weekday (monday-friday). Holidays are not accounted
* for. &quot;15W&quot; in Day-of-Month field means 'closest weekday to 15 i in given month'. If the 15th is a Saturday,
* it gives Friday. If 15th is a Sunday, the it gives following Monday.
* <P>
* '#' Can be used in Day-of-Week field. For example: &quot;5#3&quot; means 'third friday in month' (day 5 = friday, #3
* - the third). If the day does not exist (e.g. &quot;5#5&quot; - 5th friday of month) and there aren't 5 fridays in
* the month, then it won't match until the next month with 5 fridays.
* <P>
* <b>Case-sensitive</b> No fields are case-sensitive
* <P>
* <b>Dependencies between fields</b> Fields are always evaluated independently, but the expression doesn't match until
* the constraints of each field are met. Overlap of intervals are not allowed. That is: for
* Day-of-week field &quot;FRI-MON&quot; is invalid,but &quot;FRI-SUN,MON&quot; is valid
*
*/
public class CronExpression {
enum CronFieldType {
SECOND(0, 59, null) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getSecond();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
return dateTime.withSecond(value).withNano(0);
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
return dateTime.plusMinutes(1).withSecond(0).withNano(0);
}
},
MINUTE(0, 59, null) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getMinute();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
return dateTime.withMinute(value).withSecond(0).withNano(0);
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
return dateTime.plusHours(1).withMinute(0).withSecond(0).withNano(0);
}
},
HOUR(0, 23, null) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getHour();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
return dateTime.withHour(value).withMinute(0).withSecond(0).withNano(0);
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
return dateTime.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
}
},
DAY_OF_MONTH(1, 31, null) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getDayOfMonth();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
return dateTime.withDayOfMonth(value).withHour(0).withMinute(0).withSecond(0).withNano(0);
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
return dateTime.plusMonths(1).withDayOfMonth(0).withHour(0).withMinute(0).withSecond(0).withNano(0);
}
},
MONTH(1, 12,
Arrays.asList("JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC")) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getMonthValue();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
return dateTime.withMonth(value).withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
return dateTime.plusYears(1).withMonth(1).withHour(0).withDayOfMonth(1).withMinute(0).withSecond(0).withNano(0);
}
},
DAY_OF_WEEK(1, 7, Arrays.asList("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")) {
@Override
int getValue(ZonedDateTime dateTime) {
return dateTime.getDayOfWeek().getValue();
}
@Override
ZonedDateTime setValue(ZonedDateTime dateTime, int value) {
throw new UnsupportedOperationException();
}
@Override
ZonedDateTime overflow(ZonedDateTime dateTime) {
throw new UnsupportedOperationException();
}
};
final int from, to;
final List<String> names;
CronFieldType(int from, int to, List<String> names) {
this.from = from;
this.to = to;
this.names = names;
}
/**
* @param dateTime {@link ZonedDateTime} instance
* @return The field time or date value from {@code dateTime}
*/
abstract int getValue(ZonedDateTime dateTime);
/**
* @param dateTime Initial {@link ZonedDateTime} instance to use
* @param value to set for this field in {@code dateTime}
* @return {@link ZonedDateTime} with {@code value} set for this field and all smaller fields cleared
*/
abstract ZonedDateTime setValue(ZonedDateTime dateTime, int value);
/**
* Handle when this field overflows and the next higher field should be incremented
*
* @param dateTime Initial {@link ZonedDateTime} instance to use
* @return {@link ZonedDateTime} with the next greater field incremented and all smaller fields cleared
*/
abstract ZonedDateTime overflow(ZonedDateTime dateTime);
}
private final String expr;
private final SimpleField secondField;
private final SimpleField minuteField;
private final SimpleField hourField;
private final DayOfWeekField dayOfWeekField;
private final SimpleField monthField;
private final DayOfMonthField dayOfMonthField;
public CronExpression(final String expr) {
this(expr, true);
}
public CronExpression(final String expr, final boolean withSeconds) {
if (expr == null) {
throw new IllegalArgumentException("expr is null"); //$NON-NLS-1$
}
this.expr = expr;
final int expectedParts = withSeconds ? 6 : 5;
final String[] parts = expr.split("\\s+"); //$NON-NLS-1$
if (parts.length != expectedParts) {
throw new IllegalArgumentException(String.format("Invalid cron expression [%s], expected %s field, got %s", expr, expectedParts, parts.length));
}
int ix = withSeconds ? 1 : 0;
this.secondField = new SimpleField(CronFieldType.SECOND, withSeconds ? parts[0] : "0");
this.minuteField = new SimpleField(CronFieldType.MINUTE, parts[ix++]);
this.hourField = new SimpleField(CronFieldType.HOUR, parts[ix++]);
this.dayOfMonthField = new DayOfMonthField(parts[ix++]);
this.monthField = new SimpleField(CronFieldType.MONTH, parts[ix++]);
this.dayOfWeekField = new DayOfWeekField(parts[ix]);
}
public static CronExpression create(final String expr) {
return new CronExpression(expr, true);
}
public static CronExpression createWithoutSeconds(final String expr) {
return new CronExpression(expr, false);
}
public ZonedDateTime nextTimeAfter(ZonedDateTime afterTime) {
// will search for the next time within the next 4 years. If there is no
// time matching, an InvalidArgumentException will be thrown (it is very
// likely that the cron expression is invalid, like the February 30th).
return nextTimeAfter(afterTime, afterTime.plusYears(4));
}
public LocalDateTime nextLocalDateTimeAfter(LocalDateTime dateTime) {
return nextTimeAfter(ZonedDateTime.of(dateTime, ZoneId.systemDefault())).toLocalDateTime();
}
public ZonedDateTime nextTimeAfter(ZonedDateTime afterTime, long durationInMillis) {
// will search for the next time within the next durationInMillis
// millisecond. Be aware that the duration is specified in millis,
// but in fact the limit is checked on a day-to-day basis.
return nextTimeAfter(afterTime, afterTime.plus(Duration.ofMillis(durationInMillis)));
}
public ZonedDateTime nextTimeAfter(ZonedDateTime afterTime, ZonedDateTime dateTimeBarrier) {
ZonedDateTime[] nextDateTime = { afterTime.plusSeconds(1).withNano(0) };
while (true) {
checkIfDateTimeBarrierIsReached(nextDateTime[0], dateTimeBarrier);
if (!monthField.nextMatch(nextDateTime)) {
continue;
}
if (!findDay(nextDateTime)) {
continue;
}
if (!hourField.nextMatch(nextDateTime)) {
continue;
}
if (!minuteField.nextMatch(nextDateTime)) {
continue;
}
if (!secondField.nextMatch(nextDateTime)) {
continue;
}
checkIfDateTimeBarrierIsReached(nextDateTime[0], dateTimeBarrier);
return nextDateTime[0];
}
}
/**
* Find the next match for the day field.
* <p>
* This is handled different than all other fields because there are two ways to describe the day and it is easier
* to handle them together in the same method.
*
* @param dateTime Initial {@link ZonedDateTime} instance to start from
* @return {@code true} if a match was found for this field or {@code false} if the field overflowed
* @see SimpleField#nextMatch(ZonedDateTime[])
*/
private boolean findDay(ZonedDateTime[] dateTime) {
int month = dateTime[0].getMonthValue();
while (!(dayOfMonthField.matches(dateTime[0].toLocalDate())
&& dayOfWeekField.matches(dateTime[0].toLocalDate()))) {
dateTime[0] = dateTime[0].plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
if (dateTime[0].getMonthValue() != month) {
return false;
}
}
return true;
}
private static void checkIfDateTimeBarrierIsReached(ZonedDateTime nextTime, ZonedDateTime dateTimeBarrier) {
if (nextTime.isAfter(dateTimeBarrier)) {
throw new IllegalArgumentException("No next execution time could be determined that is before the limit of " + dateTimeBarrier);
}
}
@Override
public String toString() {
return getClass().getSimpleName() + "<" + expr + ">";
}
static class FieldPart implements Comparable<FieldPart> {
private int from = -1, to = -1, increment = -1;
private String modifier, incrementModifier;
@Override
public int compareTo(FieldPart o) {
return Integer.compare(from, o.from);
}
}
abstract static class BasicField {
@SuppressWarnings({"RegExpRepeatedSpace", "RegExpSimplifiable", "RegExpSingleCharAlternation", "RegExpRedundantEscape"})
private static final Pattern CRON_FIELD_REGEXP = Pattern
.compile("""
(?: # start of group 1
(?:(?<all>\\*)|(?<ignore>\\?)|(?<last>L)) # global flag (L, ?, *)
| (?<start>[0-9]{1,2}|[a-z]{3,3}) # or start number or symbol
(?: # start of group 2
(?<mod>L|W) # modifier (L,W)
| -(?<end>[0-9]{1,2}|[a-z]{3,3}) # or end nummer or symbol (in range)
)? # end of group 2
) # end of group 1
(?:(?<incmod>/|\\#)(?<inc>[0-9]{1,7}))? # increment and increment modifier (/ or \\#)
""",
Pattern.CASE_INSENSITIVE | Pattern.COMMENTS);
final CronFieldType fieldType;
final List<FieldPart> parts = new ArrayList<>();
private BasicField(CronFieldType fieldType, String fieldExpr) {
this.fieldType = fieldType;
parse(fieldExpr);
}
private void parse(String fieldExpr) { // NOSONAR
String[] rangeParts = fieldExpr.split(",");
for (String rangePart : rangeParts) {
Matcher m = CRON_FIELD_REGEXP.matcher(rangePart);
if (!m.matches()) {
throw new IllegalArgumentException("Invalid cron field '" + rangePart + "' for field [" + fieldType + "]");
}
String startNummer = m.group("start");
String modifier = m.group("mod");
String sluttNummer = m.group("end");
String incrementModifier = m.group("incmod");
String increment = m.group("inc");
FieldPart part = new FieldPart();
part.increment = 999;
if (startNummer != null) {
part.from = mapValue(startNummer);
part.modifier = modifier;
if (sluttNummer != null) {
part.to = mapValue(sluttNummer);
part.increment = 1;
} else if (increment != null) {
part.to = fieldType.to;
} else {
part.to = part.from;
}
} else if (m.group("all") != null) {
part.from = fieldType.from;
part.to = fieldType.to;
part.increment = 1;
} else if (m.group("ignore") != null) {
part.modifier = m.group("ignore");
} else if (m.group("last") != null) {
part.modifier = m.group("last");
} else {
throw new IllegalArgumentException("Invalid cron part: " + rangePart);
}
if (increment != null) {
part.incrementModifier = incrementModifier;
part.increment = Integer.parseInt(increment);
}
validateRange(part);
validatePart(part);
parts.add(part);
}
Collections.sort(parts);
}
protected void validatePart(FieldPart part) {
if (part.modifier != null) {
throw new IllegalArgumentException(String.format("Invalid modifier [%s]", part.modifier));
} else if (part.incrementModifier != null && !"/".equals(part.incrementModifier)) {
throw new IllegalArgumentException(String.format("Invalid increment modifier [%s]", part.incrementModifier));
}
}
private void validateRange(FieldPart part) {
if ((part.from != -1 && part.from < fieldType.from) || (part.to != -1 && part.to > fieldType.to)) {
throw new IllegalArgumentException(String.format("Invalid interval [%s-%s], must be %s<=_<=%s", part.from, part.to, fieldType.from,
fieldType.to));
} else if (part.from != -1 && part.to != -1 && part.from > part.to) {
throw new IllegalArgumentException(
String.format(
"Invalid interval [%s-%s]. Rolling periods are not supported (ex. 5-1, only 1-5) since this won't give a deterministic result. Must be %s<=_<=%s",
part.from, part.to, fieldType.from, fieldType.to));
}
}
protected int mapValue(String value) {
int idx;
if (fieldType.names != null && (idx = fieldType.names.indexOf(value.toUpperCase(Locale.getDefault()))) >= 0) {
return idx + fieldType.from;
}
return Integer.parseInt(value);
}
protected boolean matches(int val, FieldPart part) {
return val >= part.from && val <= part.to && (val - part.from) % part.increment == 0;
}
protected int nextMatch(int val, FieldPart part) {
if (val > part.to) {
return -1;
}
int nextPotential = Math.max(val, part.from);
if (part.increment == 1 || nextPotential == part.from) {
return nextPotential;
}
int remainder = ((nextPotential - part.from) % part.increment);
if (remainder != 0) {
nextPotential += part.increment - remainder;
}
return nextPotential <= part.to ? nextPotential : -1;
}
}
static class SimpleField extends BasicField {
SimpleField(CronFieldType fieldType, String fieldExpr) {
super(fieldType, fieldExpr);
}
/**
* Find the next match for this field. If a match cannot be found force an overflow and increase the next
* greatest field.
*
* @param dateTime {@link ZonedDateTime} array so the reference can be modified
* @return {@code true} if a match was found for this field or {@code false} if the field overflowed
*/
protected boolean nextMatch(ZonedDateTime[] dateTime) {
int value = fieldType.getValue(dateTime[0]);
for (FieldPart part : parts) {
int nextMatch = nextMatch(value, part);
if (nextMatch > -1) {
if (nextMatch != value) {
dateTime[0] = fieldType.setValue(dateTime[0], nextMatch);
}
return true;
}
}
dateTime[0] = fieldType.overflow(dateTime[0]);
return false;
}
}
static class DayOfWeekField extends BasicField {
DayOfWeekField(String fieldExpr) {
super(CronFieldType.DAY_OF_WEEK, fieldExpr);
}
boolean matches(LocalDate dato) {
for (FieldPart part : parts) {
if ("L".equals(part.modifier)) {
YearMonth ym = YearMonth.of(dato.getYear(), dato.getMonth().getValue());
return dato.getDayOfWeek() == DayOfWeek.of(part.from) && dato.getDayOfMonth() > (ym.lengthOfMonth() - 7);
} else if ("#".equals(part.incrementModifier)) {
if (dato.getDayOfWeek() == DayOfWeek.of(part.from)) {
int num = dato.getDayOfMonth() / 7;
return part.increment == (dato.getDayOfMonth() % 7 == 0 ? num : num + 1);
}
return false;
} else if (matches(dato.getDayOfWeek().getValue(), part)) {
return true;
}
}
return false;
}
@Override
protected int mapValue(String value) {
// Use 1-7 for weedays, but 0 will also represent sunday (linux practice)
return "0".equals(value) ? 7 : super.mapValue(value);
}
@Override
protected boolean matches(int val, FieldPart part) {
return "?".equals(part.modifier) || super.matches(val, part);
}
@Override
protected void validatePart(FieldPart part) {
if (part.modifier != null && !Arrays.asList("L", "?").contains(part.modifier)) {
throw new IllegalArgumentException(String.format("Invalid modifier [%s]", part.modifier));
} else if (part.incrementModifier != null && !Arrays.asList("/", "#").contains(part.incrementModifier)) {
throw new IllegalArgumentException(String.format("Invalid increment modifier [%s]", part.incrementModifier));
}
}
}
static class DayOfMonthField extends BasicField {
DayOfMonthField(String fieldExpr) {
super(CronFieldType.DAY_OF_MONTH, fieldExpr);
}
boolean matches(LocalDate dato) {
for (FieldPart part : parts) {
if ("L".equals(part.modifier)) {
YearMonth ym = YearMonth.of(dato.getYear(), dato.getMonth().getValue());
return dato.getDayOfMonth() == (ym.lengthOfMonth() - (part.from == -1 ? 0 : part.from));
} else if ("W".equals(part.modifier)) {
if (dato.getDayOfWeek().getValue() <= 5) {
if (dato.getDayOfMonth() == part.from) {
return true;
} else if (dato.getDayOfWeek().getValue() == 5) {
return dato.plusDays(1).getDayOfMonth() == part.from;
} else if (dato.getDayOfWeek().getValue() == 1) {
return dato.minusDays(1).getDayOfMonth() == part.from;
}
}
} else if (matches(dato.getDayOfMonth(), part)) {
return true;
}
}
return false;
}
@Override
protected void validatePart(FieldPart part) {
if (part.modifier != null && !Arrays.asList("L", "W", "?").contains(part.modifier)) {
throw new IllegalArgumentException(String.format("Invalid modifier [%s]", part.modifier));
} else if (part.incrementModifier != null && !"/".equals(part.incrementModifier)) {
throw new IllegalArgumentException(String.format("Invalid increment modifier [%s]", part.incrementModifier));
}
}
@Override
protected boolean matches(int val, FieldPart part) {
return "?".equals(part.modifier) || super.matches(val, part);
}
}
}

View File

@@ -0,0 +1,52 @@
package fr.pandacube.lib.util;
import java.text.DecimalFormat;
import java.util.Arrays;
public class DistanceUtil {
public static String distanceToString(double meterDist, int precision, DistanceUnit... desiredUnits) {
Arrays.sort(desiredUnits);
DistanceUnit choosenUnit = desiredUnits[0]; // la plus petite unitée
for (DistanceUnit unit : desiredUnits) {
if (meterDist / unit.multiplicator < 1) continue;
choosenUnit = unit;
}
if (choosenUnit != desiredUnits[0] && precision <= 2) precision = 2;
String precisionFormat = "##0";
if (precision > 0) precisionFormat += ".";
precisionFormat += "0".repeat(precision);
DecimalFormat df = new DecimalFormat(precisionFormat);
double dist = meterDist / choosenUnit.multiplicator;
return df.format(dist) + choosenUnit.unitStr;
}
public static String distanceToString(double meterDist, int precision) {
return distanceToString(meterDist, precision, DistanceUnit.M, DistanceUnit.KM);
}
public enum DistanceUnit {
NM(0.000000001, "nm"),
UM(0.000001, "µm"),
MM(0.001, "mm"),
CM(0.01, "cm"),
M(1, "m"),
KM(1000, "km");
private final double multiplicator;
private final String unitStr;
DistanceUnit(double mult, String s) {
multiplicator = mult;
unitStr = s;
}
}
}

View File

@@ -0,0 +1,85 @@
package fr.pandacube.lib.util;
import java.util.Arrays;
import java.util.stream.Collectors;
public class EnumUtil {
/**
* List all enum constants which are in the specified enum class.
*
* @param enumType the enum class.
* @param separator a string which will be used as a separator
* @return a string representation of the enum class.
*/
public static <T extends Enum<T>> String enumList(Class<T> enumType, String separator) {
return Arrays.stream(enumType.getEnumConstants())
.map(Enum::name)
.collect(Collectors.joining(separator));
}
/**
* List all enum constants which are in the specified enum class. It is
* equivalent to call
* {@link #enumList(Class, String)} with the second parameter
* <code>", "</code>
*
* @param enumType the enum class.
* @return a string representation of the enum class.
*/
public static <T extends Enum<T>> String enumList(Class<T> enumType) {
return enumList(enumType, ", ");
}
/**
* Permet de rechercher l'existance d'un élément dans un enum, de façon
* insensible à la casse
*
* @param enumType la classe correpondant à l'enum à lister
* @param search l'élément à rechercher, insensible à la casse
* @return l'élément de l'énumarétion, si elle a été trouvée, null sinon
*/
public static <T extends Enum<T>> T searchEnum(Class<T> enumType, String search) {
T[] elements = enumType.getEnumConstants();
for (T el : elements)
if (el.name().equalsIgnoreCase(search)) return el;
return null;
}
/**
* Permet de rechercher l'existance d'un élément dans un enum, de façon
* insensible à la casse
* La validité de la classe passé en premier paramètre est vérifiée
* dynamiquement et non
* statiquement. Préférez l'utilisation de
* {@link #searchEnum(Class, String)} quand c'est possible.
*
* @param enumType la classe correpondant à l'enum à lister
* @param search l'élément à rechercher, insensible à la casse
* @return l'élément de l'énumération, si elle a été trouvée et si la classe
* passée en paramètre est un enum, null dans les autres cas
*/
public static Enum<?> searchUncheckedEnum(Class<?> enumType, String search) {
if (!enumType.isEnum()) return null;
Enum<?>[] elements = (Enum<?>[]) enumType.getEnumConstants();
for (Enum<?> el : elements)
if (el.name().equalsIgnoreCase(search)) return el;
return null;
}
/**
* Retourne une valeur aléatoire parmis les élément de l'Enum spécifié en
* paramètre.
*
* @param enumType l'enum dans lequel piocher la valeur
* @return une des valeurs de l'enum
*/
public static <T extends Enum<T>> T randomValue(Class<T> enumType) {
T[] elements = enumType.getEnumConstants();
return elements[RandomUtil.rand.nextInt(elements.length)];
}
}

View File

@@ -0,0 +1,38 @@
package fr.pandacube.lib.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
public class FileUtils {
public static void delete(File target) {
if (target.isDirectory())
for (File child : target.listFiles())
delete(child);
target.delete();
}
public static void copy(File source, File target) throws IOException {
if (target.exists() && !target.isDirectory()) {
throw new IllegalStateException("target file already exists: " + target);
}
BasicFileAttributes sourceAttr = Files.readAttributes(source.toPath(), BasicFileAttributes.class);
if (sourceAttr.isDirectory()) {
if (target.mkdir()) {
for (String child : source.list())
copy(new File(source, child), new File(target, child));
}
else {
throw new IOException("Cannot create directory " + target);
}
}
else if (sourceAttr.isRegularFile()) {
Files.copy(source.toPath(), target.toPath());
}
}
}

View File

@@ -0,0 +1,74 @@
package fr.pandacube.lib.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class IteratorIterator<T> implements Iterator<T> {
public static <T> IteratorIterator<T> ofCollectionOfIterable(Collection<Iterable<T>> coll) {
return new IteratorIterator<>(coll.stream().map(Iterable::iterator).iterator());
}
public static <T> IteratorIterator<T> ofCollectionOfIterator(Collection<Iterator<T>> coll) {
return new IteratorIterator<>(new ArrayList<>(coll).iterator());
}
@SafeVarargs
public static <T> IteratorIterator<T> ofArrayOfIterable(Iterable<T>... arr) {
return new IteratorIterator<>(Arrays.stream(arr).map(Iterable::iterator).iterator());
}
@SafeVarargs
public static <T> IteratorIterator<T> ofArrayOfIterator(Iterator<T>... arr) {
return new IteratorIterator<>(Arrays.asList(arr).iterator());
}
private final Iterator<Iterator<T>> iterators;
private Iterator<T> currentIterator = null;
private IteratorIterator(Iterator<Iterator<T>> iterators) {
this.iterators = iterators;
}
private void fixCurrentIterator() {
if (currentIterator != null && !currentIterator.hasNext()) {
currentIterator = null;
}
}
private void fixState() {
fixCurrentIterator();
while (currentIterator == null && iterators.hasNext()) {
currentIterator = iterators.next();
fixCurrentIterator();
}
}
@Override
public boolean hasNext() {
fixState();
return currentIterator != null && currentIterator.hasNext();
}
@Override
public T next() {
if (!hasNext())
throw new NoSuchElementException("No next value found in iterator.");
return currentIterator.next();
}
/**
* @implNote The current implementation of {@link IteratorIterator} may not support
* running this method if the current position is the last value of one of
* the underlying iterable, and if the {@link #hasNext()} method has been called before this one.
*/
@Override
public void remove() {
// TODO change code to avoid currentIterator being null because we are about to change currentIterator
if (currentIterator != null)
currentIterator.remove();
}
}

View File

@@ -0,0 +1,736 @@
package fr.pandacube.lib.util;
//******************************************************************************
//***
//*** INTERPRETEUR ARITHMETIQUE version 2.1 Version Java
//***
//***
//***
//******************************************************************************
/*
Historique:
2.1:
- Version Java disponible:
# les operateurs mathematiques disponibles sont ceux de Java donc certains manquent.
2.0:
- Portage en C++ et debut en Java
Version C++:
- Meilleure gestion memoire lors de la construction de l'expression.
- Acceleration de certaines operations.
Version Java:
- Premiere version. Normalement ca doit marcher
1.3b: ajoute les fonctions suivantes: (NON DISTRIBUEE)
- reconnaissance du symbole <20>
- ecriture formatee d'expression (<28> ameliorer)
1.2: corrige les bugs suivants:
- erreur sur l'interpretation de fonctions unaires imbriquees telles que ln(exp(x)).
- la fonction puissance (pour les puissances entieres).
ajoute:
- la verification de la chaine entree (voir code d'erreur)
- la verification de l'existence de l'evaluation (voir code d'erreur)
1.1: corrige un bug au niveau de l'interpretation des fonctions du type:
exp(-(x+y))
1.0: premiere version
Le code source peut etre librement modifie et distribue.
Puisqu'il s'agit d'un essai en Java, le code ne doit pas etre super genial et merite sans doute
quelques modifications.En particulier, il serait interessant de rajouter le support des Exceptions
pour les calculs (division par zero, etc...).
*/
// Classe servant à palier l'absence de passage par variables ou reference
class VariableInt {
public int mValue;
}
// Classe principale
public class JArithmeticInterpreter {
// Variables
final int mOperator;
final double mValue;
JArithmeticInterpreter fg, fd;
// Methods
// ....................................................................................
// Node
private JArithmeticInterpreter() {
this(0, 0);
}
// ....................................................................................
// Node
private JArithmeticInterpreter(int Operator, double Value) {
mOperator = Operator;
mValue = Value;
}
// ....................................................................................
// Construct_Tree
private static JArithmeticInterpreter constructTree(StringBuffer string, int length) {
int imbric, Bimbric;
int priorite, ope;
int position, positionv, i, j;
int opetemp = 0;
int espa = 0, espat = 0;
int caspp = 0;
JArithmeticInterpreter node;
// Initialisation des variables
if (length <= 0) {
return null;
}
ope = 0;
imbric = 0;
Bimbric = 128;
priorite = 6;
i = 0;
positionv = position = 0;
// Mise en place des donnees sur le morceau de chaine
while (i < length)
if (((string.charAt(i) > 47) && (string.charAt(i) < 58)) || (string.charAt(i) == '<27>')) {
if (priorite > 5) {
priorite = 5;
positionv = i;
}
i++;
}
else if ((string.charAt(i) > 96) && (string.charAt(i) < 117)) {
VariableInt Vopetemp, Vespat;
Vopetemp = new VariableInt();
Vespat = new VariableInt();
Vopetemp.mValue = opetemp;
Vespat.mValue = espat;
FindOperator(Vopetemp, Vespat, string, i);
opetemp = Vopetemp.mValue;
espat = Vespat.mValue;
if (opetemp >= 0) {
if (imbric < Bimbric) {
Bimbric = imbric;
ope = opetemp;
position = i;
priorite = 4;
espa = espat;
}
else if ((imbric == Bimbric) && (priorite >= 4)) {
ope = opetemp;
position = i;
priorite = 4;
espa = espat;
}
j = i + 1;
i += espat;
while (j < i)
j++;
}
else if (string.charAt(i) == 't') {
if (priorite == 6) ope = -1;
i++;
}
else if (string.charAt(i) == 'p') {
if (priorite == 6) ope = -2;
i++;
}
else if (string.charAt(i) == 'r') {
if (priorite == 6) ope = -2;
i++;
}
else if (string.charAt(i) == 'n') {
if (priorite == 6) ope = -1;
i++;
}
else {
return null;
}
}
else
switch (string.charAt(i)) {
case '(':
imbric++;
i++;
break;
case ')':
imbric--;
i++;
break;
case '+':
if (imbric < Bimbric) {
Bimbric = imbric;
priorite = 1;
ope = 1;
position = i;
caspp = 0;
}
else if (imbric == Bimbric) {
priorite = 1;
ope = 1;
position = i;
caspp = 0;
}
i++;
break;
case '-':
if (imbric < Bimbric) {
if ((i - 1) < 0) {
if (priorite > 5) {
priorite = 1;
position = i;
ope = 2;
Bimbric = imbric;
caspp = 1;
}
}
else if (string.charAt(i - 1) == '(') {
if (priorite > 1) {
priorite = 1;
position = i;
Bimbric = imbric;
caspp = 1;
ope = 2;
}
}
else {
Bimbric = imbric;
priorite = 1;
ope = 2;
position = i;
caspp = 0;
}
}
else if (imbric == Bimbric) if ((i - 1) < 0) {
if (priorite > 5) {
priorite = 1;
position = i;
ope = 2;
caspp = 1;
}
}
else if (string.charAt(i - 1) == '(') {
if (priorite > 1) {
priorite = 1;
position = i;
caspp = 1;
ope = 2;
}
}
else {
priorite = 1;
ope = 2;
position = i;
caspp = 0;
}
i++;
break;
case '*':
if (imbric < Bimbric) {
Bimbric = imbric;
priorite = 2;
ope = 3;
position = i;
}
else if ((imbric == Bimbric) && (priorite >= 2)) {
priorite = 2;
ope = 3;
position = i;
}
i++;
break;
case '/':
if (imbric < Bimbric) {
Bimbric = imbric;
priorite = 2;
ope = 4;
position = i;
}
else if ((imbric == Bimbric) && (priorite >= 2)) {
priorite = 2;
ope = 4;
position = i;
}
i++;
break;
case '^':
if (imbric < Bimbric) {
Bimbric = imbric;
priorite = 3;
ope = 5;
position = i;
}
else if ((imbric == Bimbric) && (priorite >= 3)) {
priorite = 3;
ope = 5;
position = i;
}
i++;
break;
case '.':
i++;
break;
default:
return null;
}
if (imbric != 0) {
return null;
}
// Traitement des donnees
if (priorite == 6) {
node = new JArithmeticInterpreter(ope, 0.0);
return node;
}
else if (caspp == 1) {
node = new JArithmeticInterpreter(2, 0);
node.fg = new JArithmeticInterpreter(0, 0);
node.fd = new JArithmeticInterpreter();
if ((length - position - 1 - Bimbric) == 0) { // argument absent
return null;
}
StringBuffer temp = CopyPartialString(string, (position + 1), (length - 1 - Bimbric));
node.fd = constructTree(temp, (length - position - 1 - Bimbric));
return node;
}
else if (priorite == 5) {
node = new JArithmeticInterpreter(0, calc_const(string, positionv));
return node;
}
else if (ope > 5) {
node = new JArithmeticInterpreter(ope, 0);
if ((length - position - espa - Bimbric) == 0) { // argument absent
return null;
}
StringBuffer temp = CopyPartialString(string, (position + espa), (length - 1));
node.fg = constructTree(temp, (length - position - espa - Bimbric));
return node;
}
else {
node = new JArithmeticInterpreter(ope, 0);
if ((position - Bimbric) == 0) { // argument absent
return null;
}
StringBuffer temp = CopyPartialString(string, Bimbric, (position - 1));
node.fg = constructTree(temp, (position - Bimbric));
if ((length - position - 1 - Bimbric) == 0) { // argument absent
return null;
}
temp = CopyPartialString(string, (position + 1), (length - 1 - Bimbric));
node.fd = constructTree(temp, (length - position - 1 - Bimbric));
return node;
}
}
// ....................................................................................
private double computeTree() {
if (mOperator == 0) return mValue;
double valueL = fg.computeTree();
double valueR = 0;
if (fd != null) valueR = fd.computeTree();
switch (mOperator) {
case 1: // +
return (valueL + valueR);
case 2: // -
return (valueL - valueR);
case 3: // *
return (valueL * valueR);
case 4: // -
if (valueR == 0) {
return 0;
}
return (valueL / valueR);
case 5: // ^
return Math.pow(valueL, valueR);
case 6: // exp
return Math.exp(valueL);
case 7: // ln
if (valueL <= 0) {
return 0;
}
return (Math.log(valueL) / Math.log(2));
case 8: // log
if (valueL <= 0) {
return 0;
}
return Math.log(valueL);
case 9: // sqrt
if (valueL < 0) {
return 0;
}
return Math.sqrt(valueL);
case 10: // abs
return Math.abs(valueL);
case 11:
return Math.sin(valueL); // sin
case 12:
return Math.cos(valueL); // cos
case 13:
return Math.tan(valueL); // tan
case 14:
return Math.asin(valueL); // asin
case 15:
return Math.acos(valueL); // acos
case 16:
return Math.atan(valueL); // atan
default:
return 0;
}
}
// ....................................................................................
// Write_Tree
private void writeTree(StringBuffer string) {
boolean parenthese = false;
switch (mOperator) {
case 0:
string.append(StringUtil.formatDouble(mValue));
break;
case 1:
fg.writeTree(string);
string.append('+');
fd.writeTree(string);
break;
case 2:
if (fg.mOperator != 0 || fg.mValue != 0)
fg.writeTree(string);
string.append('-');
if ((fd.mOperator == 1) || (fd.mOperator == 2)) {
parenthese = true;
string.append('(');
}
fd.writeTree(string);
if (parenthese) string.append(')');
break;
case 3:
if ((fg.mOperator == 1) || (fg.mOperator == 2)) {
parenthese = true;
string.append('(');
}
fg.writeTree(string);
if (parenthese) string.append(')');
parenthese = false;
string.append('*');
if ((fd.mOperator == 1) || (fd.mOperator == 2)) {
parenthese = true;
string.append('(');
}
fd.writeTree(string);
if (parenthese) string.append(')');
break;
case 4:
if ((fg.mOperator == 1) || (fg.mOperator == 2)) {
parenthese = true;
string.append('(');
}
fg.writeTree(string);
if (parenthese) string.append(')');
parenthese = false;
string.append('/');
if ((fd.mOperator > 0) && (fd.mOperator < 5)) {
parenthese = true;
string.append('(');
}
fd.writeTree(string);
if (parenthese) string.append(')');
break;
case 5:
if ((fg.mOperator > 0) && (fg.mOperator < 5)) {
parenthese = true;
string.append('(');
}
fg.writeTree(string);
if (parenthese) string.append(')');
parenthese = false;
string.append('^');
if ((fd.mOperator > 0) && (fd.mOperator < 5)) {
parenthese = true;
string.append('(');
}
fd.writeTree(string);
if (parenthese) string.append(')');
break;
case 6:
string.append("exp(");
fg.writeTree(string);
string.append(')');
break;
case 7:
string.append("log(");
fg.writeTree(string);
string.append(')');
break;
case 8:
string.append("ln(");
fg.writeTree(string);
string.append(')');
break;
case 9:
string.append("sqrt(");
fg.writeTree(string);
string.append(')');
break;
case 10:
string.append("|");
fg.writeTree(string);
string.append('|');
break;
case 11:
string.append("sin(");
fg.writeTree(string);
string.append(')');
break;
case 12:
string.append("cos(");
fg.writeTree(string);
string.append(')');
break;
case 13:
string.append("tan(");
fg.writeTree(string);
string.append(')');
break;
case 14:
string.append("asin(");
fg.writeTree(string);
string.append(')');
break;
case 15:
string.append("acos(");
fg.writeTree(string);
string.append(')');
break;
case 16:
string.append("atan(");
fg.writeTree(string);
string.append(')');
break;
}
}
// ....................................................................................
// calc_const
private static double calc_const(StringBuffer chaine, int pos) {
int i = pos, j;
double temp = 0;
int signe = 1;
int longueur = chaine.length();
if (chaine.charAt(i) == '-') {
signe = -1;
i++;
}
if (chaine.charAt(i) == 'π') return signe * Math.PI;
while (i < longueur && chaine.charAt(i) > 47 && chaine.charAt(i) < 58) {
temp = temp * 10 + (chaine.charAt(i) - 48);
i++;
}
if (i < longueur && chaine.charAt(i) == '.') {
i++;
j = 1;
while (i < longueur && chaine.charAt(i) > 47 && chaine.charAt(i) < 58) {
temp = temp + (chaine.charAt(i) - 48) * Math.exp(-j * 2.30258509);
i++;
j++;
}
}
return (signe * temp);
}
// ....................................................................................
// FindOperator
private static void FindOperator(VariableInt oper, VariableInt esp, StringBuffer chaine, int pos) {
switch (chaine.charAt(pos)) {
case 'a':
switch (chaine.charAt(pos + 1)) {
case 'b':
esp.mValue = 3;
oper.mValue = 10;
break;
case 'c':
esp.mValue = 4;
oper.mValue = 15;
break;
case 's':
esp.mValue = 4;
oper.mValue = 14;
break;
case 't':
esp.mValue = 4;
oper.mValue = 16;
break;
}
break;
case 'c':
if (chaine.charAt(pos + 1) == 'h') {
esp.mValue = 2;
oper.mValue = 18;
}
else if ((chaine.charAt(pos + 1) == 'o') && (chaine.charAt(pos + 2) == 's'))
if (chaine.charAt(pos + 3) == 'h') {
esp.mValue = 4;
oper.mValue = 18;
}
else {
esp.mValue = 3;
oper.mValue = 12;
}
break;
case 'e':
if ((chaine.charAt(pos + 1) == 'x') && (chaine.charAt(pos + 2) == 'p')) {
esp.mValue = 3;
oper.mValue = 6;
}
else
oper.mValue = -10;
break;
case 'l':
if (chaine.charAt(pos + 1) == 'n') {
esp.mValue = 2;
oper.mValue = 7;
}
else if ((chaine.charAt(pos + 1) == 'o') && (chaine.charAt(pos + 2) == 'g')) {
esp.mValue = 3;
oper.mValue = 8;
}
else
oper.mValue = -10;
break;
case 's':
if (chaine.charAt(pos + 1) == 'h') {
esp.mValue = 2;
oper.mValue = 17;
}
else if (chaine.charAt(pos + 1) == 'q') {
esp.mValue = 4;
oper.mValue = 9;
}
else if (chaine.charAt(pos + 3) == 'h') {
esp.mValue = 4;
oper.mValue = 17;
}
else {
esp.mValue = 3;
oper.mValue = 11;
}
break;
case 't':
if (chaine.charAt(pos + 1) == 'h') {
esp.mValue = 2;
oper.mValue = 19;
}
else if ((chaine.charAt(pos + 1) == 'a') && (chaine.charAt(pos + 2) == 'n')) {
if (chaine.charAt(pos + 3) == 'h') {
esp.mValue = 4;
oper.mValue = 19;
}
else {
esp.mValue = 3;
oper.mValue = 13;
}
}
else
oper.mValue = -10;
break;
default:
oper.mValue = -10;
break;
}
}
// ....................................................................................
// CopyPartialString
private static StringBuffer CopyPartialString(StringBuffer chaine, int debut, int fin) {
StringBuffer chartemp;
int a = fin - debut + 1;
chartemp = new StringBuffer(a + 1);
for (int i = 0; i < a; i++)
chartemp.append(chaine.charAt(debut + i));
return chartemp;
}
public static double getResultFromExpression(String expr, StringBuffer writeTree) {
StringBuffer input = new StringBuffer(expr);
JArithmeticInterpreter jai = null;
try {
jai = JArithmeticInterpreter.constructTree(input, input.length());
} catch (Exception ignored) {}
if (jai == null) throw new IllegalArgumentException("Le calcul passé en paramètre est invalide");
if (writeTree != null) {
writeTree.setLength(0);
jai.writeTree(writeTree);
}
return jai.computeTree();
}
public static double getResultFromExpression(String expr) {
return getResultFromExpression(expr, null);
}
public static void main(String[] args) {
StringBuffer b = new StringBuffer(0);
String disp_res = StringUtil.formatDouble(JArithmeticInterpreter.getResultFromExpression("1245.25*2", b));
System.out.println(disp_res);
System.out.println(b);
} // */
}

View File

@@ -0,0 +1,48 @@
package fr.pandacube.lib.util;
import java.util.Objects;
import java.util.function.Supplier;
/**
* Represents a lazy loaded value.
*
* The value will be computed using the Supplier provided in the
* constructor, only the first time the {@link #get()} method is
* called.
*
* @param <T> the type of the enclosed value.
*/
public class Lazy<T> implements Supplier<T> {
private T cachedValue;
private final Supplier<T> supplier;
private boolean cached = false;
public Lazy(Supplier<T> s) {
supplier = Objects.requireNonNull(s);
}
/**
* Get the wrapped value, from cache or from the provider if it is not yet cached.
*
* If the provider throws an exception, it will be redirected to the caller as is, and no value will be cached (the next call to this method will
* execute the supplier again).
*/
@Override
public synchronized T get() {
if (!cached) // check outside synchronized method to reduce useless synchronization if value is already cached
set(supplier.get());
return cachedValue;
}
public synchronized void reset() {
cached = false;
cachedValue = null;
}
public synchronized void set(T value) {
cachedValue = value;
cached = true;
}
}

View File

@@ -0,0 +1,48 @@
package fr.pandacube.lib.util;
import java.util.Objects;
import fr.pandacube.lib.util.ThrowableUtil.SupplierException;
/**
* Represents a lazy loaded value.
*
* The value will be computed using the Supplier provided in the
* constructor, only the first time the {@link #get()} method is
* called.
*
* @param <T> the type of the enclosed value.
*/
public class LazyOrException<T> {
private T cachedValue;
private final SupplierException<T> supplier;
private boolean cached = false;
public LazyOrException(SupplierException<T> s) {
supplier = Objects.requireNonNull(s);
}
/**
* Get the wrapped value, from cache or from the provider if it is not yet cached.
*
* If the provider throws an exception, it will be redirected to the caller as is, and no value will be cached (the next call to this method will
* execute the supplier again).
*/
public synchronized T get() throws Exception {
if (!cached) // check outside synchronized method to reduce useless synchronization if value is already cached
set(supplier.get());
return cachedValue;
}
public synchronized void reset() {
cached = false;
cachedValue = null;
}
public synchronized void set(T value) {
cachedValue = value;
cached = true;
}
}

View File

@@ -0,0 +1,68 @@
package fr.pandacube.lib.util;
import java.util.Objects;
import java.util.function.ToIntBiFunction;
public class LevenshteinDistance {
private final String initialList;
private final int elementAdditionScore;
private final int elementDeletionScore;
private final ToIntBiFunction<Character, Character> elementDistanceFunction;
private int[] prev, curr;
private int progress = 0;
public LevenshteinDistance(String initList, String finList, int addScore, int delScore, ToIntBiFunction<Character, Character> elemDistFn) {
initialList = initList == null ? "" : initList;
elementAdditionScore = addScore;
elementDeletionScore = delScore;
elementDistanceFunction = elemDistFn == null ? ((e1, e2) -> Objects.equals(e1, e2) ? 0 : 1) : elemDistFn;
prev = new int[initialList.length() + 1];
curr = new int[initialList.length() + 1];
for (int i = 0; i < curr.length; i++)
curr[i] = i * elementDeletionScore;
add(finList);
}
public LevenshteinDistance() {
this(null, null, 1, 1, null);
}
public LevenshteinDistance(String initList) {
this(initList, null, 1, 1, null);
}
public int getCurrentDistance() {
return curr[curr.length - 1];
}
public void add(String els) {
for (char el : els.toCharArray())
add(el);
}
public void add(char el) {
progress++;
// swap score arrays
int[] tmp = prev; prev = curr; curr = tmp;
curr[0] = progress * elementAdditionScore;
for (int i = 1; i < curr.length; i++) {
int S = prev[i - 1] + elementDistanceFunction.applyAsInt(initialList.charAt(i - 1), el);
int A = prev[i] + elementAdditionScore;
int D = curr[i - 1] + elementDeletionScore;
curr[i] = Math.min(S, Math.min(A, D));
}
}
}

View File

@@ -0,0 +1,13 @@
package fr.pandacube.lib.util;
import java.util.List;
public class ListUtil {
public static void addLongRangeToList(List<Long> list, long min, long max) {
for (long i = min; i <= max; i++) {
list.add(i);
}
}
}

View File

@@ -0,0 +1,71 @@
package fr.pandacube.lib.util;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class Log {
private static Logger logger = Logger.getGlobal();
private static final AtomicBoolean logDebug = new AtomicBoolean(false);
public static void setDebug(boolean newVal) {
logDebug.set(newVal);
}
public static boolean isDebugEnabled() {
return logDebug.get();
}
public static Logger getLogger() {
return logger;
}
public static void setLogger(Logger l) {
logger = l;
}
public static void info(String message) {
logger.info(message);
}
public static void warning(String message, Throwable t) {
logger.log(Level.WARNING, message, t);
}
public static void warning(Throwable t) {
logger.log(Level.WARNING, "", t);
}
public static void warning(String message) {
logger.warning(message);
}
public static void severe(String message, Throwable t) {
logger.log(Level.SEVERE, message, t);
}
public static void severe(Throwable t) {
logger.log(Level.SEVERE, "", t);
}
public static void severe(String message) {
logger.severe(message);
}
public static void debug(String message, Throwable t) {
if (!logDebug.get()) return;
logger.log(Level.INFO, message, t);
}
public static void debug(Throwable t) {
if (!logDebug.get()) return;
logger.log(Level.INFO, "", t);
}
public static void debug(String message) {
if (!logDebug.get()) return;
logger.info(message);
}
}

View File

@@ -0,0 +1,188 @@
package fr.pandacube.lib.util;
import java.util.AbstractList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Function;
/**
* A Wrapper list that provides a mapped view of the backend list.
* Every modification of this list will modify the bakend list.
* For each time a value is accessed or modified, the appropriate
* setter or getter is used to convert the value between the source {@code S}
* and the visible {@code T} type.
* @param <S> the source (backend) type
* @param <T> the visible (mapped) type
*/
public class MappedListView<S, T> extends AbstractList<T> {
protected final List<S> backend;
private final Function<S, T> getter;
private final Function<T, S> setter;
/**
*
* @param backend the list backing this list
* @param getter the function converting the value from the backing list to the value of this list.
* It is used for every operation involving reading data from the backing list and comparing existing data.
* For instance, {@link #indexOf(Object)} iterate through the backing list, converting all the values
* with {@code getter} before comparing them with the parameter of {@link #indexOf(Object)}.
* before comparing
* @param setter used for modification of the data in the list (typically {@code add} and {@code set} methods)
*/
public MappedListView(List<S> backend, Function<S, T> getter, Function<T, S> setter) {
this.backend = backend;
this.getter = getter;
this.setter = setter;
}
@Override
public int size() {
return backend.size();
}
@Override
public T get(int index) {
return getter.apply(backend.get(index));
}
@Override
public T set(int index, T element) {
return getter.apply(backend.set(index, setter.apply(element)));
}
@Override
public boolean add(T element) {
return backend.add(setter.apply(element));
}
@Override
public void add(int index, T element) {
backend.add(index, setter.apply(element));
}
@Override
public T remove(int index) {
return getter.apply(backend.remove(index));
}
@Override
public void clear() {
backend.clear();
}
@Override
public List<T> subList(int fromIndex, int toIndex) {
return new MappedListView<>(backend.subList(fromIndex, toIndex), getter, setter);
}
@Override
protected void removeRange(int fromIndex, int toIndex) {
backend.subList(fromIndex, toIndex).clear();
}
@Override
public boolean equals(Object o) {
return backend.equals(o);
}
@Override
public int hashCode() {
return backend.hashCode();
}
@SuppressWarnings("unchecked")
@Override
public int indexOf(Object o) {
return backend.indexOf(setter.apply((T) o));
}
@SuppressWarnings("unchecked")
@Override
public int lastIndexOf(Object o) {
return backend.lastIndexOf(setter.apply((T) o));
}
@Override
public Iterator<T> iterator() {
return new Iterator<>() {
final Iterator<S> wrappedIt = backend.iterator();
@Override
public boolean hasNext() {
return wrappedIt.hasNext();
}
@Override
public T next() {
return getter.apply(wrappedIt.next());
}
@Override
public void remove() {
wrappedIt.remove();
}
};
}
@Override
public ListIterator<T> listIterator() {
return listIterator(0);
}
@Override
public ListIterator<T> listIterator(int index) {
return new ListIterator<>() {
final ListIterator<S> wrappedIt = backend.listIterator(index);
@Override
public boolean hasNext() {
return wrappedIt.hasNext();
}
@Override
public T next() {
return getter.apply(wrappedIt.next());
}
@Override
public boolean hasPrevious() {
return wrappedIt.hasPrevious();
}
@Override
public T previous() {
return getter.apply(wrappedIt.previous());
}
@Override
public int nextIndex() {
return wrappedIt.nextIndex();
}
@Override
public int previousIndex() {
return wrappedIt.previousIndex();
}
@Override
public void remove() {
wrappedIt.remove();
}
@Override
public void set(T w) {
wrappedIt.set(setter.apply(w));
}
@Override
public void add(T w) {
wrappedIt.add(setter.apply(w));
}
};
}
}

View File

@@ -0,0 +1,74 @@
package fr.pandacube.lib.util;
import java.text.DecimalFormat;
public class MemoryUtil {
public enum MemoryUnit {
B(1, 1, null),
KB(1024, 1000, "k"),
MB(1024 * 1024, 1000_000, "M"),
GB(1024 * 1024 * 1024, 1000_000_000, "G");
final long valueTrad;
final long valueSI;
final String unitMultiplier;
public long toUnitRound(long byteCount, boolean si) {
return byteCount / value(si);
}
public double toUnit(long byteCount, boolean si) {
return byteCount / (double)value(si);
}
public long value(boolean si) {
return si ? valueSI : valueTrad;
}
public String unit(boolean si) {
return unitMultiplier == null ? "o" : (unitMultiplier + (si ? "o" : "io"));
}
MemoryUnit(long vTrad, long vSI, String uMult) {
valueTrad = vTrad;
valueSI = vSI;
unitMultiplier = uMult;
}
}
private static final DecimalFormat format = new DecimalFormat("#####0.00");
public static String humanReadableSize(long octet, MemoryUnit roundTo, boolean si) {
boolean neg = octet < 0;
long size = Math.abs(octet);
MemoryUnit unit = roundTo;
for (int ui = MemoryUnit.values().length - 1; ui >= 0; ui--) {
MemoryUnit u = MemoryUnit.values()[ui];
if (u == roundTo)
break;
if (size < u.value(si))
continue;
unit = u;
break;
}
String dispValue;
if (unit == roundTo) {
dispValue = ""+unit.toUnitRound(size, si);
}
else {
dispValue = format.format(unit.toUnit(size, si));
}
return (neg ? "-" : "") + dispValue + unit.unit(si);
}
public static String humanReadableSize(long octet) {
return humanReadableSize(octet, MemoryUnit.B, false);
}
}

View File

@@ -0,0 +1,228 @@
package fr.pandacube.lib.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public enum MinecraftVersion {
v1_7_2_to_1_7_5(4, "1.7.2-1.7.5"),
v1_7_6_to_1_7_10(5, "1.7.6-1.7.10"),
v1_8(47, "1.8.x"),
v1_9(107, "1.9"),
v1_9_1(108, "1.9.1"),
v1_9_2(109, "1.9.2"),
v1_9_3_to_1_9_4(110, "1.9.3", "1.9.4"),
v1_10(210, "1.10.x"),
v1_11(315, "1.11"),
v1_11_1_to_1_11_2(316, "1.11.1", "1.11.2"),
v1_12(335, "1.12"),
v1_12_1(338, "1.12.1"),
v1_12_2(340, "1.12.2"),
v1_13(393, "1.13"),
v1_13_1(401, "1.13.1"),
v1_13_2(404, "1.13.2"),
v1_14(477, "1.14"),
v1_14_1(480, "1.14.1"),
v1_14_2(485, "1.14.2"),
v1_14_3(490, "1.14.3"),
v1_14_4(498, "1.14.4"),
v1_15(573, "1.15"),
v1_15_1(575, "1.15.1"),
v1_15_2(578, "1.15.2"),
v1_16(735, "1.16"),
v1_16_1(736, "1.16.1"),
v1_16_2(751, "1.16.2"),
v1_16_3(753, "1.16.3"),
v1_16_4_to_1_16_5(754, "1.16.4", "1.16.5"),
v1_17(755, "1.17"),
v1_17_1(756, "1.17.1"),
v1_18_to_1_18_1(757, "1.18", "1.18.1"),
v1_18_2(758, "1.18.2"),
v1_19(759, "1.19");
// IMPORTANT: don't forget to update the versionMergeDisplay value when adding a new version;
private static final Map<EnumSet<MinecraftVersion>, List<String>> versionMergeDisplay;
static {
versionMergeDisplay = new HashMap<>();
versionMergeDisplay.put(EnumSet.of(v1_7_2_to_1_7_5, v1_7_6_to_1_7_10),
List.of("1.7.2-1.7.10"));
versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1, v1_9_2, v1_9_3_to_1_9_4),
List.of("1.9.x"));
versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1, v1_9_2),
List.of("1.9-1.9.2"));
versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1),
List.of("1.9", "1.9.1"));
versionMergeDisplay.put(EnumSet.of(v1_9_1, v1_9_2, v1_9_3_to_1_9_4),
List.of("1.9.1-1.9.4"));
versionMergeDisplay.put(EnumSet.of(v1_9_1, v1_9_2),
List.of("1.9.1", "1.9.2"));
versionMergeDisplay.put(EnumSet.of(v1_9_2, v1_9_3_to_1_9_4),
List.of("1.9.2-1.9.4"));
versionMergeDisplay.put(EnumSet.of(v1_11, v1_11_1_to_1_11_2),
List.of("1.11.x"));
versionMergeDisplay.put(EnumSet.of(v1_12, v1_12_1, v1_12_2),
List.of("1.12.x"));
versionMergeDisplay.put(EnumSet.of(v1_12, v1_12_1),
List.of("1.12", "1.12.1"));
versionMergeDisplay.put(EnumSet.of(v1_12_1, v1_12_2),
List.of("1.12.1", "1.12.2"));
versionMergeDisplay.put(EnumSet.of(v1_13, v1_13_1, v1_13_2),
List.of("1.13.x"));
versionMergeDisplay.put(EnumSet.of(v1_13, v1_13_1),
List.of("1.13", "1.13.1"));
versionMergeDisplay.put(EnumSet.of(v1_13_1, v1_13_2),
List.of("1.13.1", "1.13.2"));
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2, v1_14_3, v1_14_4),
List.of("1.14.x"));
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2, v1_14_3),
List.of("1.14-1.14.3"));
versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2, v1_14_3, v1_14_4),
List.of("1.14.1-1.14.4"));
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2),
List.of("1.14-1.14.2"));
versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2, v1_14_3),
List.of("1.14.1-1.14.3"));
versionMergeDisplay.put(EnumSet.of(v1_14_2, v1_14_3, v1_14_4),
List.of("1.14.2-1.14.4"));
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1),
List.of("1.14", "1.14.1"));
versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2),
List.of("1.14.1", "1.14.2"));
versionMergeDisplay.put(EnumSet.of(v1_14_2, v1_14_3),
List.of("1.14.2", "1.14.3"));
versionMergeDisplay.put(EnumSet.of(v1_14_3, v1_14_4),
List.of("1.14.3", "1.14.4"));
versionMergeDisplay.put(EnumSet.of(v1_15, v1_15_1, v1_15_2),
List.of("1.15.x"));
versionMergeDisplay.put(EnumSet.of(v1_15, v1_15_1),
List.of("1.15", "1.15.1"));
versionMergeDisplay.put(EnumSet.of(v1_15_1, v1_15_2),
List.of("1.15.1", "1.15.2"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3, v1_16_4_to_1_16_5),
List.of("1.16.x"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3),
List.of("1.16-1.16.3"));
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2, v1_16_3, v1_16_4_to_1_16_5),
List.of("1.16.1-1.16.5"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2),
List.of("1.16-1.16.2"));
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2, v1_16_3),
List.of("1.16.1-1.16.3"));
versionMergeDisplay.put(EnumSet.of(v1_16_2, v1_16_3, v1_16_4_to_1_16_5),
List.of("1.16.2-1.16.5"));
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1),
List.of("1.16", "1.16.1"));
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2),
List.of("1.16.1", "1.16.2"));
versionMergeDisplay.put(EnumSet.of(v1_16_2, v1_16_3),
List.of("1.16.2", "1.16.3"));
versionMergeDisplay.put(EnumSet.of(v1_16_3, v1_16_4_to_1_16_5),
List.of("1.16.3-1.16.5"));
versionMergeDisplay.put(EnumSet.of(v1_17, v1_17_1),
List.of("1.17.x"));
versionMergeDisplay.put(EnumSet.of(v1_18_to_1_18_1, v1_18_2),
List.of("1.18.x"));
}
public final int id;
public final List<String> versionDisplay;
MinecraftVersion(int v, String... d) {
id = v;
versionDisplay = Arrays.asList(d);
}
@Override
public String toString() {
return toStringAnd();
}
public String toStringAnd() {
return StringUtil.joinGrammatically(", ", " et ", versionDisplay);
}
public String toStringOr() {
return StringUtil.joinGrammatically(", ", " ou ", versionDisplay);
}
public static MinecraftVersion getVersion(int v) {
for (MinecraftVersion mcV : values())
if (mcV.id == v) return mcV;
return null;
}
public static String displayOptimizedListOfVersionsAnd(List<MinecraftVersion> versions) {
return StringUtil.joinGrammatically(", ", " et ", getVersionsDisplayList(versions));
}
public static String displayOptimizedListOfVersionsOr(List<MinecraftVersion> versions) {
return StringUtil.joinGrammatically(", ", " ou ", getVersionsDisplayList(versions));
}
public static List<String> getVersionsDisplayList(List<MinecraftVersion> vList) {
if (vList == null)
return new ArrayList<>();
Set<MinecraftVersion> vSet = EnumSet.copyOf(vList);
List<String> ret = new ArrayList<>();
for (int i = 0; i < values().length; i++) {
if (!vSet.contains(values()[i]))
continue;
EnumSet<MinecraftVersion> vSubSet = EnumSet.of(values()[i]);
while (i + 1 < values().length && vSet.contains(values()[i + 1])) {
i++;
vSubSet.add(values()[i]);
if (!versionMergeDisplay.containsKey(vSubSet)) {
vSubSet.remove(values()[i]);
i--;
break;
}
}
if (vSubSet.size() == 1) {
ret.addAll(values()[i].versionDisplay);
}
else {
ret.addAll(versionMergeDisplay.get(vSubSet));
}
}
return ret;
}
}

View File

@@ -0,0 +1,93 @@
package fr.pandacube.lib.util;
public class MinecraftWebUtil {
/**
Convert a legacy Minecraft color coded String into HTML Format.
*/
// TODO update to support RGB colors (Bungee and Essentials notation).
// See JavaScript implementation at https://www.pandacube.fr/assets/js/global.js
public static String fromMinecraftColorCodeToHTML(char code_prefix, String ligne)
{
String color_char = "0123456789abcdefr";
StringBuilder builder = new StringBuilder();
char currentColor = 'r';
boolean bold = false, italic = false, underlined = false, strikethrough = false;
for (int i=0; i<ligne.length(); i++) {
char c = ligne.charAt(i);
if (c == code_prefix && (i<ligne.length()-1)) {
i++;
c = ligne.charAt(i);
if (color_char.contains(String.valueOf(Character.toLowerCase(c)))) {
if (bold) {
builder.append("</span>");
bold = false;
}
if (italic) {
builder.append("</span>");
italic = false;
}
if (underlined) {
builder.append("</span>");
underlined = false;
}
if (strikethrough) {
builder.append("</span>");
strikethrough = false;
}
if (Character.toLowerCase(c) != currentColor) {
if (currentColor != 'r')
builder.append("</span>");
if (Character.toLowerCase(c) != 'r')
builder.append("<span class=\"c").append(Character.toUpperCase(c)).append("\">");
currentColor = Character.toLowerCase(c);
}
}
else if (Character.toLowerCase(c) == 'l') {
if (!bold) {
builder.append("<span class=\"cL\">");
bold = true;
}
}
else if (Character.toLowerCase(c) == 'm') {
if (!strikethrough) {
builder.append("<span class=\"cM\">");
strikethrough = true;
}
}
else if (Character.toLowerCase(c) == 'n') {
if (!underlined) {
builder.append("<span class=\"cN\">");
underlined = true;
}
}
else if (Character.toLowerCase(c) == 'o') {
if (!italic) {
builder.append("<span class=\"cO\">");
italic = true;
}
}
else {
builder.append(code_prefix + c);
}
}
else
builder.append(c);
}
return builder.toString();
}
}

View File

@@ -0,0 +1,45 @@
package fr.pandacube.lib.util;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.Scanner;
import java.util.UUID;
public class OfflineUUID {
public static UUID getFromNickName(String nickname) {
byte[] from_str = ("OfflinePlayer:" + nickname).getBytes(StandardCharsets.UTF_8);
return UUID.nameUUIDFromBytes(from_str);
}
public static UUID[] getFromNickNames(String[] nicknames) {
Objects.requireNonNull(nicknames);
UUID[] uuids = new UUID[nicknames.length];
for (int i = 0; i < nicknames.length; i++)
uuids[i] = getFromNickName(nicknames[i]);
return uuids;
}
public static void main(String[] args) {
if (args.length == 0) {
try (Scanner s = new Scanner(System.in)) {
for(;;) {
System.out.print("Please input a player name: ");
if (!s.hasNext())
break;
String line = s.nextLine();
System.out.println(getFromNickName(line));
}
}
}
else {
for (String arg : args)
System.out.println("" + arg + ":" + getFromNickName(arg));
}
}
}

View File

@@ -0,0 +1,102 @@
package fr.pandacube.lib.util;
import java.util.List;
import java.util.Random;
import java.util.Set;
public class RandomUtil {
public static final Random rand = new Random();
public static int nextIntBetween(int minInclu, int maxExclu) {
return rand.nextInt(maxExclu - minInclu) + minInclu;
}
public static double nextDoubleBetween(double minInclu, double maxExclu) {
return rand.nextDouble() * (maxExclu - minInclu) + minInclu;
}
public static <T> T arrayElement(T[] arr) {
return (arr == null || arr.length == 0) ? null : arr[rand.nextInt(arr.length)];
}
public static <T> T listElement(List<T> arr) {
return (arr == null || arr.isEmpty()) ? null : arr.get(rand.nextInt(arr.size()));
}
public static char stringChar(String arr) {
return (arr == null || arr.isEmpty()) ? '\0' : arr.charAt(rand.nextInt(arr.length()));
}
/**
* Returns a random value from a set.
*
* May not be optimized (Actually O(n) )
* @param set the Set from which to pick a random value
* @return a random value from the set
*/
public static <T> T setElement(Set<T> set) {
if (set.isEmpty())
throw new IllegalArgumentException("set is empty");
int retI = rand.nextInt(set.size()), i = 0;
for (T e : set) {
if (retI == i)
return e;
i++;
}
throw new RuntimeException("Should never go to this line of code");
}
/**
* Return a value between 0 and the number of parameter minus 1, using the provided frequencies.
*
* The probability of each value to be returned depends of the frequencies provided.
* @param f the frequencies of each entries
* @return the index of an entry, or -1 if it is unable to pick anything (all the frequencies are 0 or there is not provided frequency)
*/
public static int randomIndexOfFrequencies(double... f) {
if (f == null)
throw new IllegalArgumentException("f cannot be null");
int n = f.length;
double[] fSums = new double[n];
double sum = 0;
for (int i = 0; i < n; i++) {
if (f[i] < 0)
throw new IllegalArgumentException("f[" + i + "] cannot be negative.");
fSums[i] = (sum += f[i]);
}
double r = rand.nextDouble() * sum;
for (int i = 0; i < n; i++) {
if (fSums[i] > r)
return i;
}
return n - 1;
}
public static final String PASSWORD_CHARSET_LATIN_LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
public static final String PASSWORD_CHARSET_LATIN_UPPERCASE = PASSWORD_CHARSET_LATIN_LOWERCASE.toUpperCase();
public static final String PASSWORD_CHARSET_DIGIT = "0123456789";
public static final String PASSWORD_CHARSET_SPECIAL = "@#+*/-;:,.?!='()[]{}&";
public static final String PASSWORD_CHARSET_NO_ANBIGUITY = "abcdefghkmnpqrstwxyzACDEFGHKLMNPQRSTWXYZ2345679";
public static String randomPassword(int length) {
return randomPassword(length, PASSWORD_CHARSET_NO_ANBIGUITY);
}
public static String randomPassword(int length, String charset) {
char[] pw = new char[length];
for (int i = 0; i < length; i++) {
pw[i] = stringChar(charset);
}
return String.valueOf(pw);
}
}

View File

@@ -0,0 +1,44 @@
package fr.pandacube.lib.util;
import java.util.Arrays;
import java.util.List;
public class StringUtil {
public static String formatDouble(double d) {
if (d == (long) d)
return String.format("%d", (long) d);
return String.valueOf(d);
}
/**
* @param s Chaine de caractère à parcourir
* @param c_match le caractère dont on doit retourner le nombre d'occurence
* @return nombre d'occurence de <b>c_match</b> dans <b>s</b>
*/
public static int char_count(CharSequence s, char c_match) {
char[] chars = s.toString().toCharArray();
int count = 0;
for (char c : chars)
if (c == c_match) count++;
return count;
}
public static String joinGrammatically(CharSequence sep1, CharSequence sepFinal, List<String> strings) {
int size = strings == null ? 0 : strings.size();
return size == 0 ? "" : size == 1 ? strings.get(0) : String.join(sep1, strings.subList(0, --size)) + sepFinal + strings.get(size);
}
public static String repeat(char base, int count) {
char[] chars = new char[count];
Arrays.fill(chars, base);
return new String(chars);
}
}

View File

@@ -0,0 +1,151 @@
package fr.pandacube.lib.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
public class ThrowableUtil {
public static String stacktraceToString(Throwable t) {
if (t == null) return null;
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
try (PrintStream ps = new PrintStream(os, false, StandardCharsets.UTF_8)) {
t.printStackTrace(ps);
ps.flush();
}
return os.toString(StandardCharsets.UTF_8);
} catch (IOException e) {
return null;
}
}
/**
* Wraps a {@link SupplierException} into a try catch.
* @param supp the {@link SupplierException} to run and get the value from.
* @return the value returned by the provided supplier.
* @throws RuntimeException if the provided {@link SupplierException} throws a checked exception.
*/
public static <T> T wrapEx(SupplierException<T> supp) {
try {
return supp.get();
} catch (Exception e) {
throw uncheck(e, false);
}
}
/**
* Wraps a {@link RunnableException} into a try catch.
* @param run the {@link RunnableException} to run.
* @throws RuntimeException if the provided {@link RunnableException} throws a checked exception.
*/
public static void wrapEx(RunnableException run) {
try {
run.run();
} catch (Exception e) {
throw uncheck(e, false);
}
}
/**
* Wraps a {@link SupplierException} into a try catch.
* @param supp the {@link SupplierException} to run and get the value from.
* @return the value returned by the provided supplier.
* @throws RuntimeException if the provided {@link SupplierException} throws a checked exception.
*/
public static <T> T wrapReflectEx(SupplierException<T> supp) {
try {
return supp.get();
} catch (Exception e) {
throw uncheck(e, true);
}
}
/**
* Wraps a {@link RunnableException} into a try catch.
* @param run the {@link RunnableException} to run.
* @throws RuntimeException if the provided {@link RunnableException} throws a checked exception.
*/
public static void wrapReflectEx(RunnableException run) {
try {
run.run();
} catch (Exception e) {
throw uncheck(e, true);
}
}
/**
* A supplier that can possibly throw a checked exception
*/
@FunctionalInterface
public interface SupplierException<T> {
T get() throws Exception;
}
/**
* A runnable that can possibly throw a checked exception
*/
@FunctionalInterface
public interface RunnableException {
void run() throws Exception;
}
private static RuntimeException uncheck(Throwable t, boolean convertReflectionExceptionToError) {
if (t instanceof Error er) {
throw er;
}
if (t instanceof RuntimeException re)
return re;
if (convertReflectionExceptionToError) {
Error er = null;
if (t instanceof ClassNotFoundException ce) {
er = new NoClassDefFoundError();
er.initCause(ce);
}
else if (t instanceof IllegalAccessException ce) {
er = new IllegalAccessError();
er.initCause(ce);
}
else if (t instanceof NoSuchFieldException ce) {
er = new NoSuchFieldError();
er.initCause(ce);
}
else if (t instanceof NoSuchMethodException ce) {
er = new NoSuchMethodError();
er.initCause(ce);
}
else if (t instanceof InstantiationException ce) {
er = new InstantiationError();
er.initCause(ce);
}
if (er != null)
throw er;
if (t instanceof InvocationTargetException ce) {
Throwable cause = ce.getCause();
return uncheck(cause, false);
}
}
return new RuntimeException(t);
}
}

View File

@@ -0,0 +1,30 @@
package fr.pandacube.lib.util;
public class Tick {
/**
* Returns the number of tick is the provided duration, in second
* @param seconds the duration in second
* @return the same duration as provided, but in Minecraft server ticks
*/
public static long sec(long seconds) {
return seconds * 20;
}
/**
* Returns the number of tick is the provided duration, in second
* @param minutes the duration in minutes
* @return the same duration as provided, but in Minecraft server ticks
*/
public static long min(long minutes) {
return minutes * 1200;
}
}

View File

@@ -0,0 +1,339 @@
package fr.pandacube.lib.util;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class TimeUtil {
private static final DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Locale.getDefault());
private static final DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Locale.getDefault());
private static final DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Locale.getDefault());
private static final DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Locale.getDefault());
private static final DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Locale.getDefault());
private static final DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Locale.getDefault());
private static final DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.getDefault());
private static final DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Locale.getDefault());
private static final DateTimeFormatter HFormatter = DateTimeFormatter.ofPattern("H'h'", Locale.getDefault());
public static String relativeDateFr(long displayTime, boolean showSeconds, boolean compactWords) {
return relativeDateFr(displayTime,
showSeconds ? RelativePrecision.SECONDS : RelativePrecision.MINUTES,
showSeconds ? DisplayPrecision.SECONDS : DisplayPrecision.MINUTES,
compactWords);
}
public static String relativeDateFr(long displayTime, RelativePrecision relPrecision, DisplayPrecision dispPrecision, boolean compactWords) {
long currentTime = System.currentTimeMillis();
LocalDateTime displayDateTime = toLocalDateTime(displayTime);
LocalDateTime currentDateTime = toLocalDateTime(currentTime);
long timeDiff = currentTime - displayTime;
long timeDiffSec = timeDiff / 1000;
if (timeDiffSec < -1) {
// in the future
if (relPrecision == RelativePrecision.SECONDS) {
if (timeDiffSec > -60)
return "dans " + (-timeDiffSec) + (compactWords ? "s" : " secondes");
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.MINUTES)) {
if (timeDiffSec > -60)
return compactWords ? "dans moins d1min" : "dans moins dune minute";
if (timeDiffSec > -60*2) // dans 2 min
return compactWords ? "dans 1min" : "dans une minute";
if (timeDiffSec > -3600) // dans moins d1h
return "dans " + (-timeDiffSec/60) + (compactWords ? "min" : " minutes");
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.HOURS)) {
if (timeDiffSec > -3600) // dans moins d1h
return compactWords ? "dans moins d1h" : "dans moins dune heure";
if (timeDiffSec > -3600*2) // dans moins de 2h
return compactWords ? "dans 1h" : "dans une heure";
if (timeDiffSec > -3600*12) // dans moins de 12h
return "dans " + (-timeDiffSec/3600) + (compactWords ? "h" : " heures");
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.DAYS)) {
LocalDateTime nextMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0).plusDays(1);
if (displayDateTime.isBefore(nextMidnight)) // aujourd'hui
return "aujourdhui à " + dayTimeFr(displayTime, dispPrecision);
if (displayDateTime.isBefore(nextMidnight.plusDays(1))) // demain
return "demain à " + dayTimeFr(displayTime, dispPrecision);
if (displayDateTime.isBefore(nextMidnight.plusDays(5))) // dans moins d'1 semaine
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " "
+ dayOfMonthFormatter.format(displayDateTime) + " à "
+ dayTimeFr(displayTime, dispPrecision);
}
}
else {
// present and past
if (timeDiffSec <= 1)
return "maintenant";
if (relPrecision == RelativePrecision.SECONDS) {
if (timeDiffSec < 60) // ya moins d'1 min
return "il y a " + timeDiffSec + (compactWords ? "s" : " secondes");
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.MINUTES)) {
if (timeDiffSec < 60) // ya moins d'1 min
return compactWords ? "il y a moins d1min" : "il y a moins dune minute";
if (timeDiffSec < 60*2) // ya moins de 2 min
return compactWords ? "il y a 1min" : "il y a une minute";
if (timeDiffSec < 3600) // ya moins d'1h
return "il y a " + (timeDiffSec/60) + (compactWords ? "min" : " minutes");
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.HOURS)) {
if (timeDiffSec < 3600) // ya moins d'1h
return "il y a moins dune heure";
if (timeDiffSec < 3600*2) // ya moins de 2h
return "il y a une heure";
if (timeDiffSec < 3600*12) // ya moins de 12h
return "il y a " + (timeDiffSec/3600) + " heures";
}
if (relPrecision.morePreciseOrEqTo(RelativePrecision.DAYS)) {
LocalDateTime lastMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0);
if (!displayDateTime.isBefore(lastMidnight)) // aujourd'hui
return "aujourdhui à " + dayTimeFr(displayTime, dispPrecision);
if (!displayDateTime.isBefore(lastMidnight.minusDays(1))) // hier
return "hier à " + dayTimeFr(displayTime, dispPrecision);
if (!displayDateTime.isBefore(lastMidnight.minusDays(6))) // ya moins d'1 semaine
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " dernier à "
+ dayTimeFr(displayTime, dispPrecision);
}
}
return fullDateFr(displayTime, dispPrecision, true, compactWords);
}
public enum RelativePrecision {
NONE, DAYS, HOURS, MINUTES, SECONDS;
public boolean morePreciseThan(RelativePrecision o) { return ordinal() > o.ordinal(); }
public boolean lessPreciseThan(RelativePrecision o) { return ordinal() < o.ordinal(); }
public boolean morePreciseOrEqTo(RelativePrecision o) { return ordinal() >= o.ordinal(); }
public boolean lessPreciseOrEqTo(RelativePrecision o) { return ordinal() <= o.ordinal(); }
}
public enum DisplayPrecision {
DAYS, HOURS, MINUTES, SECONDS
}
public static String fullDateFr(long displayTime, boolean showSeconds, boolean showWeekday, boolean compactWords) {
return fullDateFr(displayTime, showSeconds ? DisplayPrecision.SECONDS : DisplayPrecision.MINUTES, showWeekday, compactWords);
}
public static String fullDateFr(long displayTime, DisplayPrecision precision, boolean showWeekday, boolean compactWords) {
LocalDateTime displayDateTime = toLocalDateTime(displayTime);
String ret = (showWeekday ? ((compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " ") : "")
+ dayOfMonthFormatter.format(displayDateTime) + " "
+ (compactWords ? cmpMonthFormatter : monthFormatter).format(displayDateTime) + " "
+ yearFormatter.format(displayDateTime);
if (precision == DisplayPrecision.DAYS)
return ret;
return ret + " à " + dayTimeFr(displayTime, precision);
}
public static String dayTimeFr(long displayTime, DisplayPrecision precision) {
DateTimeFormatter tFormatter = switch(precision) {
case HOURS -> HFormatter;
case MINUTES -> HMFormatter;
case SECONDS -> HMSFormatter;
default -> throw new IllegalArgumentException("precision");
};
return tFormatter.format(toLocalDateTime(displayTime));
}
private static LocalDateTime toLocalDateTime(long msTime) {
return Instant.ofEpochMilli(msTime).atZone(TimeZone.getDefault().toZoneId()).toLocalDateTime();
}
public static String durationToLongString(long msDuration, TimeUnit hUnit, TimeUnit lUnit, boolean spaces, boolean fr, boolean leadingZeros) {
if (lUnit.compareTo(hUnit) > 0) {
TimeUnit tmp = lUnit;
lUnit = hUnit;
hUnit = tmp;
}
if (lUnit.compareTo(TimeUnit.MILLISECONDS) < 0)
lUnit = TimeUnit.MILLISECONDS;
if (hUnit.compareTo(TimeUnit.MILLISECONDS) < 0)
hUnit = TimeUnit.MILLISECONDS;
AtomicLong remainingTime = new AtomicLong(msDuration);
AtomicBoolean oneDisplayed = new AtomicBoolean(false);
final TimeUnit fLUnit = lUnit, fHUnit = hUnit;
String ret = Arrays.stream(TimeUnit.values())
.sequential()
.filter(u -> u.compareTo(fLUnit) >= 0 && u.compareTo(fHUnit) <= 0)
.sorted(Comparator.reverseOrder())
.filter(u -> {
if (u.convert(remainingTime.get(), TimeUnit.MILLISECONDS) == 0 && !oneDisplayed.get())
return false;
oneDisplayed.set(true);
return true;
})
.map(u -> {
long v = u.convert(remainingTime.get(), TimeUnit.MILLISECONDS);
remainingTime.addAndGet(TimeUnit.MILLISECONDS.convert(-v, u));
return toString(v, leadingZeros ? timeUnitToLeftPadLength(u) : 1) + timeUnitToSuffix(u, fr);
})
.collect(Collectors.joining(spaces ? " " : ""));
// ensure there is at least something to display (for instance : "0s")
return oneDisplayed.get() ? ret : (toString(0, leadingZeros ? timeUnitToLeftPadLength(lUnit) : 1) + timeUnitToSuffix(lUnit, fr));
}
public static String timeUnitToSuffix(TimeUnit u, boolean fr) {
return switch (u) {
case DAYS -> fr ? "j" : "d";
case HOURS -> "h";
case MINUTES -> "m";
case SECONDS -> "s";
case MILLISECONDS -> "ms";
case MICROSECONDS -> "μs";
case NANOSECONDS -> "ns";
};
}
public static int timeUnitToLeftPadLength(TimeUnit u) {
return switch (u) {
case NANOSECONDS, MICROSECONDS, MILLISECONDS -> 3;
case SECONDS, MINUTES, HOURS -> 2;
case DAYS -> 1;
};
}
public static String toString(long value, int leftPad) {
String valueStr = Long.toString(value);
int padding = leftPad - valueStr.length();
if (padding <= 0)
return valueStr;
return "0".repeat(padding) + valueStr;
}
/**
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false)}
* @param msDuration the duration in ms
* @param milliseconds if the milliseconds are displayed or not
*/
public static String durationToString(long msDuration, boolean milliseconds) {
return durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false);
}
/**
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, true, true, false)}
* @param msDuration the duration in ms
*/
public static String durationToString(long msDuration) {
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, true, true, false);
}
/**
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false)}
* @param msDuration the duration in ms
*/
public static String durationToParsableString(long msDuration) {
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false);
}
/**
* @see <a href="https://github.com/EssentialsX/Essentials/blob/2.x/Essentials/src/main/java/com/earth2me/essentials/utils/DateUtil.java">Essentials DateUtil#parseDuration(String, boolean)</a>
*/
public static long parseDuration(String time, boolean future) throws Exception {
@SuppressWarnings("RegExpSimplifiable")
Pattern timePattern = Pattern.compile("(?:([0-9]+)\\s*y[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*mo[a-z]*[,\\s]*)?"
+ "(?:([0-9]+)\\s*w[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*d[a-z]*[,\\s]*)?"
+ "(?:([0-9]+)\\s*h[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*m[a-z]*[,\\s]*)?"
+ "(?:([0-9]+)\\s*(?:s[a-z]*)?)?", Pattern.CASE_INSENSITIVE);
Matcher m = timePattern.matcher(time);
int years = 0;
int months = 0;
int weeks = 0;
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
boolean found = false;
while (m.find()) {
if (m.group() == null || m.group().isEmpty()) continue;
for (int i = 0; i < m.groupCount(); i++)
if (m.group(i) != null && !m.group(i).isEmpty()) {
found = true;
break;
}
if (found) {
if (m.group(1) != null && !m.group(1).isEmpty()) years = Integer.parseInt(m.group(1));
if (m.group(2) != null && !m.group(2).isEmpty()) months = Integer.parseInt(m.group(2));
if (m.group(3) != null && !m.group(3).isEmpty()) weeks = Integer.parseInt(m.group(3));
if (m.group(4) != null && !m.group(4).isEmpty()) days = Integer.parseInt(m.group(4));
if (m.group(5) != null && !m.group(5).isEmpty()) hours = Integer.parseInt(m.group(5));
if (m.group(6) != null && !m.group(6).isEmpty()) minutes = Integer.parseInt(m.group(6));
if (m.group(7) != null && !m.group(7).isEmpty()) seconds = Integer.parseInt(m.group(7));
break;
}
}
if (!found) throw new Exception("Format de durée invalide");
Calendar c = new GregorianCalendar();
if (years > 0) c.add(Calendar.YEAR, years * (future ? 1 : -1));
if (months > 0) c.add(Calendar.MONTH, months * (future ? 1 : -1));
if (weeks > 0) c.add(Calendar.WEEK_OF_YEAR, weeks * (future ? 1 : -1));
if (days > 0) c.add(Calendar.DAY_OF_MONTH, days * (future ? 1 : -1));
if (hours > 0) c.add(Calendar.HOUR_OF_DAY, hours * (future ? 1 : -1));
if (minutes > 0) c.add(Calendar.MINUTE, minutes * (future ? 1 : -1));
if (seconds > 0) c.add(Calendar.SECOND, seconds * (future ? 1 : -1));
Calendar max = new GregorianCalendar();
max.add(Calendar.YEAR, 10);
if (c.after(max)) return max.getTimeInMillis();
return c.getTimeInMillis();
}
public static final List<String> DURATION_SUFFIXES = List.of("y", "mo", "w", "d", "h", "m", "s");
}