renamed modules dir
This commit is contained in:
19
pandalib-util/pom.xml
Normal file
19
pandalib-util/pom.xml
Normal 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>
|
@@ -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());
|
||||
}
|
||||
}
|
125
pandalib-util/src/main/java/fr/pandacube/lib/util/BiMap.java
Normal file
125
pandalib-util/src/main/java/fr/pandacube/lib/util/BiMap.java
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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: "Each Monday-Friday at 08:00" or "Every last friday of the month at 01:30"
|
||||
* <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"> </th>
|
||||
* <th align="left">Allowable values</th>
|
||||
* <th align="left"> </th>
|
||||
* <th align="left">Special Characters</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td align="left"><code>Seconds (may be omitted)</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>0-59</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>, - * /</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td align="left"><code>Minutes</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>0-59</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>, - * /</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td align="left"><code>Hours</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>0-23</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>, - * /</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td align="left"><code>Day of month</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>1-31</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>, - * ? / L W</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td align="left"><code>Month</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>1-12 or JAN-DEC (note: english abbreviations)</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>, - * /</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td align="left"><code>Day of week</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>1-7 or MON-SUN (note: english abbreviations)</code></td>
|
||||
* <td align="left"> </th>
|
||||
* <td align="left"><code>, - * ? / L #</code></td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <P>
|
||||
* '*' Can be used in all fields and means 'for all values'. E.g. "*" 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. "10-12" in Hours field means 'for hours 10, 11 and 12'
|
||||
* <P>
|
||||
* ',' Used to specify multiple values for a field. E.g. "MON,WED,FRI" in Day-of-week field means "for
|
||||
* monday, wednesday and friday"
|
||||
* <P>
|
||||
* '/' Used to specify increments. E.g. "0/15" in Seconds field means "for seconds 0, 15, 30, ad
|
||||
* 45". And "5/15" in seconds field means "for seconds 5, 20, 35, and 50". 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. "/" character helsp turn some of these values back on. Thus "7/6" 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. "15W" 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: "5#3" means 'third friday in month' (day 5 = friday, #3
|
||||
* - the third). If the day does not exist (e.g. "5#5" - 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 "FRI-MON" is invalid,but "FRI-SUN,MON" 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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)];
|
||||
}
|
||||
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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);
|
||||
} // */
|
||||
|
||||
}
|
48
pandalib-util/src/main/java/fr/pandacube/lib/util/Lazy.java
Normal file
48
pandalib-util/src/main/java/fr/pandacube/lib/util/Lazy.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
71
pandalib-util/src/main/java/fr/pandacube/lib/util/Log.java
Normal file
71
pandalib-util/src/main/java/fr/pandacube/lib/util/Log.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
30
pandalib-util/src/main/java/fr/pandacube/lib/util/Tick.java
Normal file
30
pandalib-util/src/main/java/fr/pandacube/lib/util/Tick.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
339
pandalib-util/src/main/java/fr/pandacube/lib/util/TimeUtil.java
Normal file
339
pandalib-util/src/main/java/fr/pandacube/lib/util/TimeUtil.java
Normal 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 d’1min" : "dans moins d’une minute";
|
||||
if (timeDiffSec > -60*2) // dans 2 min
|
||||
return compactWords ? "dans 1min" : "dans une minute";
|
||||
if (timeDiffSec > -3600) // dans moins d’1h
|
||||
return "dans " + (-timeDiffSec/60) + (compactWords ? "min" : " minutes");
|
||||
}
|
||||
if (relPrecision.morePreciseOrEqTo(RelativePrecision.HOURS)) {
|
||||
if (timeDiffSec > -3600) // dans moins d’1h
|
||||
return compactWords ? "dans moins d’1h" : "dans moins d’une 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 "aujourd’hui à " + 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 d’1min" : "il y a moins d’une 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 d’une 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 "aujourd’hui à " + 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");
|
||||
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user