pandalib-util javadoc and small API changes
This commit is contained in:
parent
5ff9a40f19
commit
f7dc774dbd
@ -190,12 +190,12 @@ public interface SuggestionsSupplier<S> {
|
||||
|
||||
|
||||
static <S> SuggestionsSupplier<S> suggestDuration() {
|
||||
final List<String> emptyTokenSuggestions = TimeUtil.DURATION_SUFFIXES.stream().map(p -> "1" + p).collect(Collectors.toList());
|
||||
final List<String> emptyTokenSuggestions = DURATION_SUFFIXES.stream().map(p -> "1" + p).collect(Collectors.toList());
|
||||
return (s, ti, token, args) -> {
|
||||
if (token.isEmpty()) {
|
||||
return emptyTokenSuggestions;
|
||||
}
|
||||
List<String> remainingSuffixes = new ArrayList<>(TimeUtil.DURATION_SUFFIXES);
|
||||
List<String> remainingSuffixes = new ArrayList<>(DURATION_SUFFIXES);
|
||||
char[] tokenChars = token.toCharArray();
|
||||
String accSuffix = "";
|
||||
for (char c : tokenChars) {
|
||||
@ -215,6 +215,11 @@ public interface SuggestionsSupplier<S> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all possible duration unit symbols for suggestions.
|
||||
*/
|
||||
public static final List<String> DURATION_SUFFIXES = List.of("y", "mo", "w", "d", "h", "m", "s");
|
||||
|
||||
|
||||
private static void scanAndRemovePastSuffixes(List<String> suffixes, String foundSuffix) {
|
||||
for (int i = 0; i < suffixes.size(); i++) {
|
||||
|
@ -88,9 +88,9 @@ public class AABBBlock implements Iterable<BlockVector> {
|
||||
}
|
||||
|
||||
public Vector getRandomPosition() {
|
||||
double x = RandomUtil.nextDoubleBetween(pos1.getX(), pos2.getX());
|
||||
double y = RandomUtil.nextDoubleBetween(pos1.getY(), pos2.getY());
|
||||
double z = RandomUtil.nextDoubleBetween(pos1.getZ(), pos2.getZ());
|
||||
double x = RandomUtil.rand.nextDouble(pos1.getX(), pos2.getX());
|
||||
double y = RandomUtil.rand.nextDouble(pos1.getY(), pos2.getY());
|
||||
double z = RandomUtil.rand.nextDouble(pos1.getZ(), pos2.getZ());
|
||||
return new Vector(x, y, z);
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ public class GameWorldUtils implements Listener {
|
||||
if (!new File(Bukkit.getWorldContainer(), world).isDirectory())
|
||||
throw new IllegalStateException("GameWorld '"+world+"' does not exist");
|
||||
|
||||
String copiedName = world + "_gen" + RandomUtil.nextIntBetween(100000, 999999);
|
||||
String copiedName = world + "_gen" + RandomUtil.rand.nextInt(100000, 999999);
|
||||
|
||||
File srcDir = new File(Bukkit.getWorldContainer(), world);
|
||||
File destDir = new File(Bukkit.getWorldContainer(), copiedName);
|
||||
|
@ -65,9 +65,9 @@ public class LocationUtil {
|
||||
|
||||
// generate a random (x,z) coordinate
|
||||
Location ret = new Location(w,
|
||||
RandomUtil.nextIntBetween(min.getBlockX(), max.getBlockX()) + 0.5,
|
||||
RandomUtil.rand.nextInt(min.getBlockX(), max.getBlockX()) + 0.5,
|
||||
w.getMaxHeight() - 1,
|
||||
RandomUtil.nextIntBetween(min.getBlockZ(), max.getBlockZ()) + 0.5);
|
||||
RandomUtil.rand.nextInt(min.getBlockZ(), max.getBlockZ()) + 0.5);
|
||||
|
||||
// find a secure y value
|
||||
ret = getSecureLocationOrNull(ret);
|
||||
|
@ -4,15 +4,82 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Utility class to track and limit the amount of a specific value for a specified amount of duration.
|
||||
*
|
||||
* An exemple of application is for rolling expense limit of a debit card: you cannot expense more that {@code $X}
|
||||
* during a rolling period of {@code $Y} time.
|
||||
*
|
||||
* Here is an example usage of this class:
|
||||
* <pre>
|
||||
* AmountPerTimeLimiter instance = new AmountPerTimeLimiter(X, Y);
|
||||
* void tryExpense(double amount) {
|
||||
* if (instance.canAdd(amount)) {
|
||||
* // do the action (here, it’s the expense)
|
||||
* instance.add(amount);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* Create a new instance of {@link AmountPerTimeLimiter}
|
||||
* @param maximumAmount the maximum amount possible in the specified interval
|
||||
* @param timeInterval the interval in milliseconds
|
||||
*/
|
||||
public AmountPerTimeLimiter(double maximumAmount, long timeInterval) {
|
||||
maxAmount = maximumAmount;
|
||||
duration = timeInterval;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute and returns the amount considered for the current time interval.
|
||||
* @return the amount considered.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the provided amount into this limiter, at the current system time.
|
||||
* @param amount the amount added.
|
||||
*/
|
||||
public synchronized void add(double amount) {
|
||||
long currT = System.currentTimeMillis();
|
||||
if (!entries.isEmpty() && entries.get(entries.size()-1).time == currT)
|
||||
entries.get(entries.size()-1).amount += amount;
|
||||
else
|
||||
entries.add(new Entry(currT, amount));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the provided amount can be added into the limiter without exceeding the limit.
|
||||
* @param amount the amount to test.
|
||||
* @return if it’s possible to add that amount now.
|
||||
*/
|
||||
public boolean canAdd(double amount) {
|
||||
return getAmountSinceDuration() + amount < maxAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount that can be added right now into the limiter.
|
||||
* @return the maximum amount that can be added.
|
||||
*/
|
||||
public double getRemainingForNow() {
|
||||
return Math.max(0, maxAmount - getAmountSinceDuration());
|
||||
}
|
||||
|
||||
|
||||
@ -26,32 +93,4 @@ public class AmountPerTimeLimiter {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -9,23 +9,52 @@ import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A bi-direction map storing in a synchronized way a {@code forwardMap} that store the key to value mapping, and a
|
||||
* {@code backwardMap} that store the value to key mapping.
|
||||
* All the keys and value are always unique in this bi-directional map.
|
||||
* @param <K> the type of the "key"
|
||||
* @param <V> the type of the "value"
|
||||
*/
|
||||
public class BiMap<K, V> implements Iterable<Entry<K, V>> {
|
||||
|
||||
protected final Map<K, V> map;
|
||||
protected final Map<V, K> inversedMap;
|
||||
/**
|
||||
* The backend forward map
|
||||
*/
|
||||
protected final Map<K, V> forwardMap;
|
||||
|
||||
/**
|
||||
* The backend bawkward map
|
||||
*/
|
||||
protected final Map<V, K> backwardMap;
|
||||
|
||||
private BiMap<V, K> reversedView = null;
|
||||
|
||||
private final Object lock;
|
||||
|
||||
/**
|
||||
* Create a new bi-directional map
|
||||
* @param mapSupplier a {@link Supplier} to create the two backend map.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public BiMap(Supplier<Map<?, ?>> mapSupplier) {
|
||||
map = (Map<K, V>) mapSupplier.get();
|
||||
inversedMap = (Map<V, K>) mapSupplier.get();
|
||||
forwardMap = (Map<K, V>) mapSupplier.get();
|
||||
backwardMap = (Map<V, K>) mapSupplier.get();
|
||||
lock = new Object();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new bi-directional map with {@link HashMap} as the two backend maps.
|
||||
*/
|
||||
public BiMap() {
|
||||
this(HashMap::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new bi-directional map with {@link HashMap} as the two backend maps, and filled with the provided source
|
||||
* {@link Map}.
|
||||
* @param source the source to fill the new {@link BiMap}.
|
||||
*/
|
||||
public BiMap(Map<K, V> source) {
|
||||
this();
|
||||
putAll(source);
|
||||
@ -35,90 +64,222 @@ public class BiMap<K, V> implements Iterable<Entry<K, V>> {
|
||||
* Only used for #reversedView()
|
||||
*/
|
||||
private BiMap(BiMap<V, K> rev) {
|
||||
map = rev.inversedMap;
|
||||
inversedMap = rev.map;
|
||||
forwardMap = rev.backwardMap;
|
||||
backwardMap = rev.forwardMap;
|
||||
lock = rev.lock;
|
||||
reversedView = rev;
|
||||
}
|
||||
|
||||
public synchronized void put(K k, V v) {
|
||||
/**
|
||||
* Associate the provided key and value to each other in this bi-directional map.
|
||||
* Since the {@link BiMap} cannot have duplicate keys or values: if a key is already present, the currently mapped
|
||||
* value will be removed from the map. Also if a value is already present, the currently mapped key will also be
|
||||
* removed.
|
||||
* @param k the key.
|
||||
* @param v the value.
|
||||
*/
|
||||
public void put(K k, V v) {
|
||||
synchronized (lock) {
|
||||
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());
|
||||
forwardMap.put(k, v);
|
||||
backwardMap.put(v, k);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized V get(K k) {
|
||||
return map.get(k);
|
||||
|
||||
/**
|
||||
* Associate the provided key and value to each other in this bi-directional map.
|
||||
* Since the {@link BiMap} cannot have duplicate keys or values: if a key is already present, the currently mapped
|
||||
* value will be removed from the map. Also if a value is already present, the currently mapped key will also be
|
||||
* removed.
|
||||
* @param entry the entry with a key and value.
|
||||
*/
|
||||
public void put(Entry<? extends K, ? extends V> entry) {
|
||||
put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
public synchronized K getKey(V v) {
|
||||
return inversedMap.get(v);
|
||||
/**
|
||||
* Put the content of the provided map into this bi-directional map.
|
||||
* This methods will call the {@link #put(Entry)} method successively in the order of the provided Map’s iterator.
|
||||
* @param source the source map.
|
||||
*/
|
||||
public void putAll(Map<? extends K, ? extends V> source) {
|
||||
synchronized (lock) {
|
||||
source.entrySet().forEach(this::put);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean containsKey(K k) {
|
||||
return map.containsKey(k);
|
||||
/**
|
||||
* Gets the mapped value for the provided key.
|
||||
* @param k the key.
|
||||
* @return the value mapped with the key.
|
||||
*/
|
||||
public V get(K k) {
|
||||
synchronized (lock) {
|
||||
return forwardMap.get(k);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean containsValue(V v) {
|
||||
return inversedMap.containsKey(v);
|
||||
/**
|
||||
* Gets the mapped key for the provided value.
|
||||
* @param v the value.
|
||||
* @return the key mapped with the value.
|
||||
*/
|
||||
public K getKey(V v) {
|
||||
synchronized (lock) {
|
||||
return backwardMap.get(v);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized V remove(K k) {
|
||||
V v = map.remove(k);
|
||||
inversedMap.remove(v);
|
||||
/**
|
||||
* Tells if this {@link BiMap} contains the provided key.
|
||||
* @param k the key to test if it’s present.
|
||||
* @return true if this bimap contains the provided key, false otherwise.
|
||||
*/
|
||||
public boolean containsKey(K k) {
|
||||
synchronized (lock) {
|
||||
return forwardMap.containsKey(k);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if this {@link BiMap} contains the provided value.
|
||||
* @param v the value to test if it’s present.
|
||||
* @return true if this bimap contains the provided value, false otherwise.
|
||||
*/
|
||||
public boolean containsValue(V v) {
|
||||
synchronized (lock) {
|
||||
return backwardMap.containsKey(v);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the mapping of the provided key from this map.
|
||||
* The mapped value is also removed in the internal backward map.
|
||||
* @param k the key whose mapping is to be removed from the map.
|
||||
* @return the value that was mapped with the provided key.
|
||||
*/
|
||||
public V remove(K k) {
|
||||
synchronized (lock) {
|
||||
V v = forwardMap.remove(k);
|
||||
backwardMap.remove(v);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized K removeValue(V v) {
|
||||
K k = inversedMap.remove(v);
|
||||
map.remove(k);
|
||||
/**
|
||||
* Remove the mapping of the provided value from this map.
|
||||
* The mapped key is also removed in the internal forward map.
|
||||
* @param v the value whose mapping is to be removed from the map.
|
||||
* @return the key that was mapped with the provided value.
|
||||
*/
|
||||
public K removeValue(V v) {
|
||||
synchronized (lock) {
|
||||
K k = backwardMap.remove(v);
|
||||
forwardMap.remove(k);
|
||||
return k;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable {@link Set} view of this map.
|
||||
* It’s iteration order will depends on the implementation of the {@code forwardMap}.
|
||||
* @return an unmodifiable {@link Set} view of this map.
|
||||
* @see Map#entrySet()
|
||||
*/
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
return Collections.unmodifiableSet(forwardMap.entrySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator of this map.
|
||||
* It’s iteration order will depends on the implementation of the {@code forwardMap}.
|
||||
* @return an iterator of this map.
|
||||
* @see Map#entrySet()
|
||||
* @see Set#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator() {
|
||||
return Collections.unmodifiableSet(map.entrySet()).iterator();
|
||||
return entrySet().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable {@link Set} view of the keys contained in this map.
|
||||
* It’s iteration order will depends on the implementation of the {@code forwardMap}’s key set.
|
||||
* @return an unmodifiable {@link Set} view of the keys contained in this map.
|
||||
* @see Map#keySet()
|
||||
*/
|
||||
public Set<K> keySet() {
|
||||
return Collections.unmodifiableSet(map.keySet());
|
||||
return Collections.unmodifiableSet(forwardMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable {@link Set} view of the values contained in this map.
|
||||
* It’s iteration order will depends on the implementation of the {@code backwardMap}’s key set.
|
||||
* @return an unmodifiable {@link Set} view of the values contained in this map.
|
||||
* @see Map#keySet()
|
||||
*/
|
||||
public Set<V> valuesSet() {
|
||||
return Collections.unmodifiableSet(inversedMap.keySet());
|
||||
return Collections.unmodifiableSet(backwardMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable {@link Map} view of the {@code forwardMap} of this bi-directional map.
|
||||
* It’s iteration order will depends on the implementation of the {@code forwardMap}.
|
||||
* @return an unmodifiable {@link Map} view of the {@code forwardMap} of this bi-directional map.
|
||||
*/
|
||||
public Map<K, V> asMap() {
|
||||
return Collections.unmodifiableMap(map);
|
||||
return Collections.unmodifiableMap(forwardMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reversed view of this bi-directional map.
|
||||
* Since the returned {@link BiMap} is a view of this {@link BiMap}, any change to either of those will affect both
|
||||
* of them. Also, calling {@code bimap.reversedView().reversedView()} will return the original instance
|
||||
* {@code bimap} since calling this method will cache each map into the respective reversed view.
|
||||
* @return the reversed view of this {@link BiMap}.
|
||||
*/
|
||||
public BiMap<V, K> reversedView() {
|
||||
synchronized (lock) {
|
||||
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());
|
||||
/**
|
||||
* Performs the provided action for each entry of this map, following the iteration order of the internal {@code forwardMap}.
|
||||
* @param action the action to perform on each entry.
|
||||
* @see Map#forEach(BiConsumer)
|
||||
*/
|
||||
public void forEach(BiConsumer<K, V> action) {
|
||||
synchronized (lock) {
|
||||
forwardMap.forEach(action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key-value mapping in this map.
|
||||
* @return the number of key-value mapping in this map.
|
||||
* @see Map#size()
|
||||
*/
|
||||
public int size() {
|
||||
return map.size();
|
||||
synchronized (lock) {
|
||||
return forwardMap.size();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
map.clear();
|
||||
inversedMap.clear();
|
||||
/**
|
||||
* Removes all the mapping from this map.
|
||||
*/
|
||||
public void clear() {
|
||||
synchronized (lock) {
|
||||
forwardMap.clear();
|
||||
backwardMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,629 +0,0 @@
|
||||
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>
|
||||
* <caption>CRON fields</caption>
|
||||
* <tr>
|
||||
* <th>Field</th>
|
||||
* <th>Allowable values</th>
|
||||
* <th>Special Characters</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>Seconds (may be omitted)</code></td>
|
||||
* <td><code>0-59</code></td>
|
||||
* <td><code>, - * /</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>Minutes</code></td>
|
||||
* <td><code>0-59</code></td>
|
||||
* <td><code>, - * /</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>Hours</code></td>
|
||||
* <td><code>0-23</code></td>
|
||||
* <td><code>, - * /</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>Day of month</code></td>
|
||||
* <td><code>1-31</code></td>
|
||||
* <td><code>, - * ? / L W</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>Month</code></td>
|
||||
* <td><code>1-12 or JAN-DEC (note: english abbreviations)</code></td>
|
||||
* <td><code>, - * /</code></td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>Day of week</code></td>
|
||||
* <td><code>1-7 or MON-SUN (note: english abbreviations)</code></td>
|
||||
* <td><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);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,8 +3,19 @@ package fr.pandacube.lib.util;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This class contains various methods to manipulate and display distances.
|
||||
*/
|
||||
public class DistanceUtil {
|
||||
|
||||
/**
|
||||
* Generate a string representation of the provided distance, with a specific decimal precision and one of the
|
||||
* specified metric prefix
|
||||
* @param meterDist the distance to display, in meter
|
||||
* @param precision the number of digit to display after the decimal separator
|
||||
* @param desiredUnits the prefered unit prefix to use for convertion.
|
||||
* @return a string representation of the provided distance
|
||||
*/
|
||||
public static String distanceToString(double meterDist, int precision, DistanceUnit... desiredUnits) {
|
||||
|
||||
Arrays.sort(desiredUnits);
|
||||
@ -28,20 +39,64 @@ public class DistanceUtil {
|
||||
return df.format(dist) + choosenUnit.unitStr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Generate a string representation of the provided distance, with a specific decimal precision, and using either
|
||||
* {@code km} or {@code m} as a unit.
|
||||
* <p>
|
||||
* Calling this method is equivalent to {@code distanceToString(meterDist, precision, DistanceUnit.M, DistanceUnit.KM)}.
|
||||
* @param meterDist the distance to display, in meter
|
||||
* @param precision the number of digit to display after the decimal separator
|
||||
* @return a string representation of the provided distance
|
||||
*/
|
||||
public static String distanceToString(double meterDist, int precision) {
|
||||
return distanceToString(meterDist, precision, DistanceUnit.M, DistanceUnit.KM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of comonly used distance metric unit
|
||||
*/
|
||||
public enum DistanceUnit {
|
||||
|
||||
/**
|
||||
* Nanometer unit. One billionth of a meter. 10<sup>-9</sup> = 0.000000001m.
|
||||
*/
|
||||
NM(0.000000001, "nm"),
|
||||
|
||||
/**
|
||||
* Micrometer unit. One millionth of a meter. 10<sup>-6</sup> = 0.000001m.
|
||||
*/
|
||||
UM(0.000001, "µm"),
|
||||
|
||||
/**
|
||||
* Millimeter unit. One thousandth of a meter. 10<sup>-3</sup> = 0.001m.
|
||||
*/
|
||||
MM(0.001, "mm"),
|
||||
|
||||
/**
|
||||
* Centimeter unit. One hundredth of a meter. 10<sup>-2</sup> = 0.01m
|
||||
*/
|
||||
CM(0.01, "cm"),
|
||||
|
||||
/**
|
||||
* Meter unit. One meter. 10<sup>0</sup> = 1m.
|
||||
*/
|
||||
M(1, "m"),
|
||||
|
||||
/**
|
||||
* Kilometer unit. One thousand meter. 10<sup>3</sup> = 1000m.
|
||||
*/
|
||||
KM(1000, "km");
|
||||
|
||||
private final double multiplicator;
|
||||
private final String unitStr;
|
||||
/**
|
||||
* The value of this unit in meter.
|
||||
*/
|
||||
public final double multiplicator;
|
||||
|
||||
/**
|
||||
* String representation of the unit symbol.
|
||||
*/
|
||||
public final String unitStr;
|
||||
|
||||
DistanceUnit(double mult, String s) {
|
||||
multiplicator = mult;
|
||||
|
@ -3,6 +3,9 @@ package fr.pandacube.lib.util;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This class provides methods for manipulating enums.
|
||||
*/
|
||||
public class EnumUtil {
|
||||
|
||||
/**
|
||||
@ -11,6 +14,7 @@ public class EnumUtil {
|
||||
* @param enumType the enum class.
|
||||
* @param separator a string which will be used as a separator
|
||||
* @return a string representation of the enum class.
|
||||
* @param <T> the type of the enum.
|
||||
*/
|
||||
public static <T extends Enum<T>> String enumList(Class<T> enumType, String separator) {
|
||||
return Arrays.stream(enumType.getEnumConstants())
|
||||
@ -19,67 +23,65 @@ public class EnumUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* 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.
|
||||
* @param <T> the type of the enum.
|
||||
*/
|
||||
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
|
||||
* Search for a specific enum entry in the provided enum type, using the case-insensitive search string.
|
||||
*
|
||||
* @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
|
||||
* @param enumType the class of the enum in which to search
|
||||
* @param search the case-insensitive name of the enum value to return.
|
||||
* @return the element found in the enum, or null if not found.
|
||||
* @param <T> the type of the enum.
|
||||
*/
|
||||
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;
|
||||
for (T el : enumType.getEnumConstants()) {
|
||||
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.
|
||||
* Search for a specific enum entry in the provided enum type, using the case-insensitive search string.
|
||||
* unlike {@link #searchEnum(Class, String)}, this method does not statically check the enum type, in case it is not
|
||||
* known at compilation time.
|
||||
*
|
||||
* @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
|
||||
* For a statically checked enum type, uses {@link #searchEnum(Class, String)} instead.
|
||||
*
|
||||
* @param enumType the class of the enum in which to search
|
||||
* @param search the case-insensitive name of the enum value to return.
|
||||
* @return the element found in the enum, or null if not found or if the provideid type is not an enum.
|
||||
*/
|
||||
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;
|
||||
if (!enumType.isEnum())
|
||||
return null;
|
||||
for (Enum<?> el : (Enum<?>[]) enumType.getEnumConstants()) {
|
||||
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.
|
||||
* Pick a random value from an enum type.
|
||||
*
|
||||
* @param enumType l'enum dans lequel piocher la valeur
|
||||
* @return une des valeurs de l'enum
|
||||
* @param enumType the class of the enum in which to pick the value from
|
||||
* @return one of the enum value, or null if the provided enum is empty.
|
||||
* @param <T> the type of the enum.
|
||||
*/
|
||||
public static <T extends Enum<T>> T randomValue(Class<T> enumType) {
|
||||
T[] elements = enumType.getEnumConstants();
|
||||
|
||||
return elements[RandomUtil.rand.nextInt(elements.length)];
|
||||
return RandomUtil.arrayElement(enumType.getEnumConstants());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,10 +5,16 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
/**
|
||||
* Provides utility methods to manipulate files and directories
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Recursively delete the provided file and all of its content if it is a directory.
|
||||
* @param target the target file or directory.
|
||||
*/
|
||||
public static void delete(File target) {
|
||||
if (target.isDirectory())
|
||||
for (File child : target.listFiles())
|
||||
@ -16,8 +22,21 @@ public class FileUtils {
|
||||
target.delete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recursively copy the provided source file or directory to the provided target.
|
||||
* @param source the source file or directory.
|
||||
* @param target the copy destination.
|
||||
* @throws IOException if an IO error occurs.
|
||||
* @throws IllegalStateException if the target destination already exists and is not a directory.
|
||||
* @throws IllegalArgumentException if at least one of the parameter is null, or if the source doesn’t exists.
|
||||
*/
|
||||
public static void copy(File source, File target) throws IOException {
|
||||
if (source == null || !source.exists()) {
|
||||
throw new IllegalArgumentException("source is null or doesn’t exists: " + source);
|
||||
}
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException("target cannot be null");
|
||||
}
|
||||
if (target.exists() && !target.isDirectory()) {
|
||||
throw new IllegalStateException("target file already exists: " + target);
|
||||
}
|
||||
|
@ -6,69 +6,103 @@ import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* An {@link Iterator} that iterate over all the elements of all the provided iterators.
|
||||
* In other words, this class concatenate the provided iterators.
|
||||
* @param <T> the type of the values in the iterators
|
||||
*/
|
||||
public class IteratorIterator<T> implements Iterator<T> {
|
||||
|
||||
/**
|
||||
* Create an {@link IteratorIterator} with the provided {@link Collection} of {@link Iterable}.
|
||||
* The iterables’ iterators will be concatenated in the order of the collection’s iterator.
|
||||
* @param coll the collection of iterables.
|
||||
* @return a new instance of {@link IteratorIterator} iterating over the elements of the provided iterables.
|
||||
* @param <T> the type of the values in the iterables.
|
||||
*/
|
||||
public static <T> IteratorIterator<T> ofCollectionOfIterable(Collection<Iterable<T>> coll) {
|
||||
return new IteratorIterator<>(coll.stream().map(Iterable::iterator).iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link IteratorIterator} with the provided {@link Collection} of {@link Iterator}.
|
||||
* The iterators will be concatenated in the order of the collection’s iterator.
|
||||
* @param coll the collection of iterators.
|
||||
* @return a new instance of {@link IteratorIterator} iterating over the elements of the provided iterators.
|
||||
* @param <T> the type of the values in the iterators.
|
||||
*/
|
||||
public static <T> IteratorIterator<T> ofCollectionOfIterator(Collection<Iterator<T>> coll) {
|
||||
return new IteratorIterator<>(new ArrayList<>(coll).iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link IteratorIterator} with the provided array of {@link Iterable}.
|
||||
* The iterables’ iterators will be concatenated in the order of the array.
|
||||
* @param arr the array of iterables.
|
||||
* @return a new instance of {@link IteratorIterator} iterating over the elements of the provided iterables.
|
||||
* @param <T> the type of the values in the iterables.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> IteratorIterator<T> ofArrayOfIterable(Iterable<T>... arr) {
|
||||
return new IteratorIterator<>(Arrays.stream(arr).map(Iterable::iterator).iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link IteratorIterator} with the provided array of {@link Iterator}.
|
||||
* The iterators will be concatenated in the order of the array.
|
||||
* @param arr the array of iterators.
|
||||
* @return a new instance of {@link IteratorIterator} iterating over the elements of the provided iterators.
|
||||
* @param <T> the type of the values in the iterators.
|
||||
*/
|
||||
@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 Iterator<T> currentValueIterator = null;
|
||||
private Iterator<T> nextValueIterator = null;
|
||||
|
||||
private IteratorIterator(Iterator<Iterator<T>> iterators) {
|
||||
this.iterators = iterators;
|
||||
}
|
||||
|
||||
private void fixCurrentIterator() {
|
||||
if (currentIterator != null && !currentIterator.hasNext()) {
|
||||
currentIterator = null;
|
||||
private void fixNextIterator() {
|
||||
if (nextValueIterator != null && !nextValueIterator.hasNext()) {
|
||||
nextValueIterator = null;
|
||||
}
|
||||
}
|
||||
private void fixState() {
|
||||
fixCurrentIterator();
|
||||
while (currentIterator == null && iterators.hasNext()) {
|
||||
currentIterator = iterators.next();
|
||||
fixCurrentIterator();
|
||||
fixNextIterator();
|
||||
while (nextValueIterator == null && iterators.hasNext()) {
|
||||
nextValueIterator = iterators.next();
|
||||
fixNextIterator();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
fixState();
|
||||
return currentIterator != null && currentIterator.hasNext();
|
||||
return nextValueIterator != null && nextValueIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (!hasNext())
|
||||
throw new NoSuchElementException("No next value found in iterator.");
|
||||
return currentIterator.next();
|
||||
currentValueIterator = nextValueIterator;
|
||||
return currentValueIterator.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();
|
||||
if (currentValueIterator == null)
|
||||
throw new IllegalStateException();
|
||||
currentValueIterator.remove();
|
||||
}
|
||||
}
|
||||
|
@ -1,736 +0,0 @@
|
||||
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 <EFBFBD>
|
||||
- ecriture formatee d'expression (<EFBFBD> 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);
|
||||
} // */
|
||||
|
||||
}
|
@ -18,6 +18,10 @@ public class Lazy<T> implements Supplier<T> {
|
||||
private final Supplier<T> supplier;
|
||||
private boolean cached = false;
|
||||
|
||||
/**
|
||||
* Create a lazy value loader that will call the provided supplier to get the value.
|
||||
* @param s the supplier from which the value is fetched.
|
||||
*/
|
||||
public Lazy(Supplier<T> s) {
|
||||
supplier = Objects.requireNonNull(s);
|
||||
}
|
||||
@ -25,21 +29,28 @@ public class Lazy<T> implements Supplier<T> {
|
||||
/**
|
||||
* 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).
|
||||
* 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
|
||||
if (!cached)
|
||||
set(supplier.get());
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cached value. The next call to {@link #get()} will get the value from the provider.
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
cached = false;
|
||||
cachedValue = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually set the value to the provided one.
|
||||
* @param value the value to put in the cache.
|
||||
*/
|
||||
public synchronized void set(T value) {
|
||||
cachedValue = value;
|
||||
cached = true;
|
||||
|
@ -13,12 +13,16 @@ import fr.pandacube.lib.util.ThrowableUtil.SupplierException;
|
||||
*
|
||||
* @param <T> the type of the enclosed value.
|
||||
*/
|
||||
public class LazyOrException<T> {
|
||||
public class LazyOrException<T> implements SupplierException<T> {
|
||||
|
||||
private T cachedValue;
|
||||
private final SupplierException<T> supplier;
|
||||
private boolean cached = false;
|
||||
|
||||
/**
|
||||
* Create a lazy value loader that will call the provided supplier to get the value.
|
||||
* @param s the supplier from which the value is fetched.
|
||||
*/
|
||||
public LazyOrException(SupplierException<T> s) {
|
||||
supplier = Objects.requireNonNull(s);
|
||||
}
|
||||
@ -26,20 +30,28 @@ public class LazyOrException<T> {
|
||||
/**
|
||||
* 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).
|
||||
* 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() throws Exception {
|
||||
if (!cached) // check outside synchronized method to reduce useless synchronization if value is already cached
|
||||
if (!cached)
|
||||
set(supplier.get());
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cached value. The next call to {@link #get()} will get the value from the provider.
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
cached = false;
|
||||
cachedValue = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually set the value to the provided one.
|
||||
* @param value the value to put in the cache.
|
||||
*/
|
||||
public synchronized void set(T value) {
|
||||
cachedValue = value;
|
||||
cached = true;
|
||||
|
@ -1,53 +1,144 @@
|
||||
package fr.pandacube.lib.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.ToIntBiFunction;
|
||||
|
||||
/**
|
||||
* Implementation of the <a href="https://en.wikipedia.org/wiki/Levenshtein_distance">Levenshtein distance algorithm</a>
|
||||
* that operate on characters. Its purpose is to compute a "distance" between two strings of characters, that represents
|
||||
* how many edition operation it is needed to perform on the first string ({@code initialString}) to optain the second
|
||||
* one ({@code finalString}).
|
||||
*
|
||||
* All the parameters of the algorithm are configurable:
|
||||
* <ul>
|
||||
* <li>The score of adding a character</li>
|
||||
* <li>The score of removing a character</li>
|
||||
* <li>The score of replacing any pair of character</li>
|
||||
* </ul>
|
||||
*
|
||||
* A simple usage of this class is to call the constructor {@link #LevenshteinDistance(String, String, int, int,
|
||||
* ToIntBiFunction)} (for a full control of the parameters) or {@link #LevenshteinDistance(String, String)} (to keep the
|
||||
* default parameters value); then to call the method {@link #getCurrentDistance()} to compute the Levenshtein distance
|
||||
* between the two strings.
|
||||
*
|
||||
* A more advanced usage offer the possibility to progressively compute a distance from a predefined
|
||||
* {@code initialString} to a {@code finalString} that is feeded progressively using {@link #add(char)} or
|
||||
* {@link #add(String)}. This is useful if the {@code finalString} is an input that is currently being typed by the
|
||||
* user, so the application can progressively update a list of suggested words based on the distance.
|
||||
* For this usage, you can use those constructors to avoid initializing the {@code finalString}:
|
||||
* {@link #LevenshteinDistance(String, int, int, ToIntBiFunction)} or {@link #LevenshteinDistance(String)}.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class LevenshteinDistance {
|
||||
|
||||
private final String initialList;
|
||||
private final String initialString;
|
||||
|
||||
private final int elementAdditionScore;
|
||||
private final int elementDeletionScore;
|
||||
|
||||
private final ToIntBiFunction<Character, Character> elementDistanceFunction;
|
||||
|
||||
private int[] prev, curr;
|
||||
private int[] prev, curr; // dynamic programming interval arrays
|
||||
|
||||
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;
|
||||
/**
|
||||
* Create a new instance of {@link LevenshteinDistance} that compute the edit-distance between {@code initialString}
|
||||
* and {@code finalString}.
|
||||
* <p>
|
||||
* The score of each edition action is provided as parameters.
|
||||
*
|
||||
* @param initialString the initial string. Cannot be null.
|
||||
* @param finalString the final string. Can be null, and may be provided later using {@link #add(String)} or
|
||||
* character after character using {@link #add(char)}.
|
||||
* @param elementAdditionScore the score for adding a character.
|
||||
* @param elementDeletionScore the score for removing a character.
|
||||
* @param elementDistanceFunction a {@link Function} that computes the score for replacing the character provided as
|
||||
* first argument, to the character provided as second argument. If it is null, it
|
||||
* will use a default function that will return 0 if the two characters are equals, 1
|
||||
* otherwise.
|
||||
*/
|
||||
public LevenshteinDistance(String initialString, String finalString, int elementAdditionScore, int elementDeletionScore, ToIntBiFunction<Character, Character> elementDistanceFunction) {
|
||||
this.initialString = Objects.requireNonNull(initialString, "initialList");
|
||||
this.elementAdditionScore = elementAdditionScore;
|
||||
this.elementDeletionScore = elementDeletionScore;
|
||||
this.elementDistanceFunction = elementDistanceFunction == null ? ((e1, e2) -> Objects.equals(e1, e2) ? 0 : 1) : elementDistanceFunction;
|
||||
|
||||
prev = new int[initialList.length() + 1];
|
||||
prev = new int[this.initialString.length() + 1];
|
||||
|
||||
curr = new int[initialList.length() + 1];
|
||||
curr = new int[this.initialString.length() + 1];
|
||||
for (int i = 0; i < curr.length; i++)
|
||||
curr[i] = i * elementDeletionScore;
|
||||
curr[i] = i * this.elementDeletionScore;
|
||||
|
||||
add(finList);
|
||||
if (finalString != null)
|
||||
add(finalString);
|
||||
}
|
||||
|
||||
public LevenshteinDistance() {
|
||||
this(null, null, 1, 1, null);
|
||||
/**
|
||||
* Create a new instance of {@link LevenshteinDistance} that will compute the edit-distance between
|
||||
* {@code initialString} and a final string provided later using {@link #add(String)} or character after character
|
||||
* using {@link #add(char)}.
|
||||
* <p>
|
||||
* The score of each edition action is provided as parameters.
|
||||
*
|
||||
* @param initialString the initial string. Cannot be null.
|
||||
* @param elementAdditionScore the score for adding a character.
|
||||
* @param elementDeletionScore the score for removing a character.
|
||||
* @param elementDistanceFunction a {@link Function} that computes the score for replacing the character provided as
|
||||
* first argument, to the character provided as second argument. If it is null, it
|
||||
* will use a default function that will return 0 if the two characters are equals, 1
|
||||
* otherwise.
|
||||
*/
|
||||
public LevenshteinDistance(String initialString, int elementAdditionScore, int elementDeletionScore, ToIntBiFunction<Character, Character> elementDistanceFunction) {
|
||||
this(initialString, null, elementAdditionScore, elementDeletionScore, elementDistanceFunction);
|
||||
}
|
||||
|
||||
public LevenshteinDistance(String initList) {
|
||||
this(initList, null, 1, 1, null);
|
||||
/**
|
||||
* Create a new instance of {@link LevenshteinDistance} that compute the edit-distance between {@code initialString}
|
||||
* and {@code finalString}.
|
||||
* <p>
|
||||
* All the edition action are valued to 1. To customize the edition action score, use
|
||||
* {@link #LevenshteinDistance(String, String, int, int, ToIntBiFunction)}.
|
||||
*
|
||||
* @param initialString the initial string. Cannot be null.
|
||||
* @param finalString the final string. Can be null, and may be provided later using {@link #add(String)} or
|
||||
* character after character using {@link #add(char)}.
|
||||
*/
|
||||
public LevenshteinDistance(String initialString, String finalString) {
|
||||
this(initialString, finalString, 1, 1, null);
|
||||
}
|
||||
|
||||
public int getCurrentDistance() {
|
||||
return curr[curr.length - 1];
|
||||
/**
|
||||
* Create a new instance of {@link LevenshteinDistance} that will compute the edit-distance between
|
||||
* {@code initialString} and a final string provided later using {@link #add(String)} or character after character
|
||||
* using {@link #add(char)}.
|
||||
* <p>
|
||||
* All the edition action are valued to 1. To customize the edition action score, use
|
||||
* {@link #LevenshteinDistance(String, int, int, ToIntBiFunction)}.
|
||||
*
|
||||
* @param initialString the initial string. Cannot be null.
|
||||
*/
|
||||
public LevenshteinDistance(String initialString) {
|
||||
this(initialString, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the provided string of characters to the {@code finalString}, and update the Levenshtein distance returned by
|
||||
* {@link #getCurrentDistance()}.
|
||||
* @param els the string of character.
|
||||
*/
|
||||
public void add(String els) {
|
||||
for (char el : els.toCharArray())
|
||||
add(el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single character to the {@code finalString}, and update the Levenshtein distance returned by
|
||||
* * {@link #getCurrentDistance()}.
|
||||
* @param el the character.
|
||||
*/
|
||||
public void add(char el) {
|
||||
progress++;
|
||||
// swap score arrays
|
||||
@ -56,13 +147,20 @@ public class LevenshteinDistance {
|
||||
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 S = prev[i - 1] + elementDistanceFunction.applyAsInt(initialString.charAt(i - 1), el);
|
||||
int A = prev[i] + elementAdditionScore;
|
||||
int D = curr[i - 1] + elementDeletionScore;
|
||||
curr[i] = Math.min(S, Math.min(A, D));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the currently calculated Levenshtein distance from the {@code initialString} to the already provided
|
||||
* {@code finalString}.
|
||||
* @return the Levenshtein distance.
|
||||
*/
|
||||
public int getCurrentDistance() {
|
||||
return curr[curr.length - 1];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,9 +2,20 @@ package fr.pandacube.lib.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides utility methods related to lists.
|
||||
*/
|
||||
public class ListUtil {
|
||||
|
||||
|
||||
/**
|
||||
* Utility method to add to the provided list all the values in the provided interval.
|
||||
* <p>
|
||||
* If {@code min > max}, the list is not modified.
|
||||
*
|
||||
* @param list the list to add the values in.
|
||||
* @param min the inclusive min value.
|
||||
* @param max the inclusive max value.
|
||||
*/
|
||||
public static void addLongRangeToList(List<Long> list, long min, long max) {
|
||||
for (long i = min; i <= max; i++) {
|
||||
list.add(i);
|
||||
|
@ -4,65 +4,156 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Utility class to easily log info into a provided logger. This class avoid the needs to fetch the logger everytime it
|
||||
* is needed.
|
||||
*
|
||||
* For instance, this piece of code:
|
||||
* <pre>
|
||||
* getTheLoggerFromSomewhere().info(message);
|
||||
* </pre>
|
||||
*
|
||||
* Can be simplified by one line to put in the startup code of the application:
|
||||
* <pre>
|
||||
* Log.setLogger(getTheLoggerFromSomewhere());
|
||||
* </pre>
|
||||
* And this code everywhere the application needs to log something:
|
||||
* <pre>
|
||||
* Log.info(message);
|
||||
* </pre>
|
||||
*
|
||||
* This the {@link #setLogger(Logger)} method is not called, thi class will use the logger returned by
|
||||
* {@link Logger#getGlobal()}.
|
||||
*/
|
||||
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);
|
||||
/**
|
||||
* Determine if {@link #debug(Throwable)}, {@link #debug(String)} and {@link #debug(String, Throwable)} will actually
|
||||
* log a message or not.
|
||||
* @param debug true to enable debug, false otherwise
|
||||
*/
|
||||
public static void setDebug(boolean debug) {
|
||||
logDebug.set(debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the debug mode is enabled or not.
|
||||
* @return true if debug is enabled, false otherwise.
|
||||
*/
|
||||
public static boolean isDebugEnabled() {
|
||||
return logDebug.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the backend logger of this class.
|
||||
* @return the logger.
|
||||
*/
|
||||
public static Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public static void setLogger(Logger l) {
|
||||
logger = l;
|
||||
/**
|
||||
* Set the backend logger of this class.
|
||||
* @param logger the logger to use.
|
||||
*/
|
||||
public static void setLogger(Logger logger) {
|
||||
Log.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the provided message using logger level {@link Level#INFO}.
|
||||
* @param message the message to log
|
||||
* @see Logger#info(String)
|
||||
*/
|
||||
public static void info(String message) {
|
||||
logger.info(message);
|
||||
}
|
||||
|
||||
public static void warning(String message, Throwable t) {
|
||||
logger.log(Level.WARNING, message, t);
|
||||
/**
|
||||
* Log the provided message and throwable using logger level {@link Level#WARNING}.
|
||||
* @param message the message to log
|
||||
* @param throwable the throwable to log
|
||||
*/
|
||||
public static void warning(String message, Throwable throwable) {
|
||||
logger.log(Level.WARNING, message, throwable);
|
||||
}
|
||||
|
||||
public static void warning(Throwable t) {
|
||||
logger.log(Level.WARNING, "", t);
|
||||
/**
|
||||
* Log the provided throwable using logger level {@link Level#WARNING}.
|
||||
* @param throwable the throwable to log
|
||||
*/
|
||||
public static void warning(Throwable throwable) {
|
||||
logger.log(Level.WARNING, "", throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the provided message using logger level {@link Level#WARNING}.
|
||||
* @param message the message to log
|
||||
* @see Logger#warning(String)
|
||||
*/
|
||||
public static void warning(String message) {
|
||||
logger.warning(message);
|
||||
}
|
||||
|
||||
public static void severe(String message, Throwable t) {
|
||||
logger.log(Level.SEVERE, message, t);
|
||||
/**
|
||||
* Log the provided message and throwable using logger level {@link Level#SEVERE}.
|
||||
* @param message the message to log
|
||||
* @param throwable the throwable to log
|
||||
*/
|
||||
public static void severe(String message, Throwable throwable) {
|
||||
logger.log(Level.SEVERE, message, throwable);
|
||||
}
|
||||
|
||||
public static void severe(Throwable t) {
|
||||
logger.log(Level.SEVERE, "", t);
|
||||
/**
|
||||
* Log the provided throwable using logger level {@link Level#SEVERE}.
|
||||
* @param throwable the throwable to log
|
||||
*/
|
||||
public static void severe(Throwable throwable) {
|
||||
logger.log(Level.SEVERE, "", throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the provided message using logger level {@link Level#SEVERE}.
|
||||
* @param message the message to log
|
||||
* @see Logger#severe(String)
|
||||
*/
|
||||
public static void severe(String message) {
|
||||
logger.severe(message);
|
||||
}
|
||||
|
||||
public static void debug(String message, Throwable t) {
|
||||
/**
|
||||
* Log the provided message and throwable using logger level {@link Level#INFO}, if the debug mode is enabled.
|
||||
* @param message the message to log
|
||||
* @param throwable the throwable to log
|
||||
* @see #isDebugEnabled()
|
||||
* @see #setDebug(boolean)
|
||||
*/
|
||||
public static void debug(String message, Throwable throwable) {
|
||||
if (!logDebug.get()) return;
|
||||
logger.log(Level.INFO, message, t);
|
||||
logger.log(Level.INFO, message, throwable);
|
||||
}
|
||||
|
||||
public static void debug(Throwable t) {
|
||||
/**
|
||||
* Log the provided throwable using logger level {@link Level#INFO}, if the debug mode is enabled.
|
||||
* @param throwable the throwable to log
|
||||
* @see #isDebugEnabled()
|
||||
* @see #setDebug(boolean)
|
||||
*/
|
||||
public static void debug(Throwable throwable) {
|
||||
if (!logDebug.get()) return;
|
||||
logger.log(Level.INFO, "", t);
|
||||
logger.log(Level.INFO, "", throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the provided message using logger level {@link Level#INFO}, if the debug mode is enabled.
|
||||
* @param message the message to log
|
||||
* @see #isDebugEnabled()
|
||||
* @see #setDebug(boolean)
|
||||
* @see Logger#info(String)
|
||||
*/
|
||||
public static void debug(String message) {
|
||||
if (!logDebug.get()) return;
|
||||
logger.info(message);
|
||||
|
@ -7,29 +7,31 @@ 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.
|
||||
* A Wrapper list that provides a mapped view of the backend list. Every modification of this list will modify the bakend
|
||||
* list. 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> {
|
||||
|
||||
/**
|
||||
* The backend list of this {@link MappedListView}.
|
||||
*/
|
||||
protected final List<S> backend;
|
||||
private final Function<S, T> getter;
|
||||
private final Function<T, S> setter;
|
||||
|
||||
/**
|
||||
*
|
||||
* Create a new wrapper for the provided backend list.
|
||||
* @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)
|
||||
* @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 {@link #add(Object)} and
|
||||
* {@link #set(int, Object)} methods)
|
||||
*/
|
||||
public MappedListView(List<S> backend, Function<S, T> getter, Function<T, S> setter) {
|
||||
this.backend = backend;
|
||||
@ -37,75 +39,115 @@ public class MappedListView<S, T> extends AbstractList<T> {
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return backend.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public T get(int index) {
|
||||
return getter.apply(backend.get(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public T set(int index, T element) {
|
||||
return getter.apply(backend.set(index, setter.apply(element)));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean add(T element) {
|
||||
return backend.add(setter.apply(element));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void add(int index, T element) {
|
||||
backend.add(index, setter.apply(element));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public T remove(int index) {
|
||||
return getter.apply(backend.remove(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
backend.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<T> subList(int fromIndex, int toIndex) {
|
||||
return new MappedListView<>(backend.subList(fromIndex, toIndex), getter, setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void removeRange(int fromIndex, int toIndex) {
|
||||
backend.subList(fromIndex, toIndex).clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return backend.equals(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return backend.hashCode();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return backend.indexOf(setter.apply((T) o));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return backend.lastIndexOf(setter.apply((T) o));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new Iterator<>() {
|
||||
@ -127,11 +169,17 @@ public class MappedListView<S, T> extends AbstractList<T> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ListIterator<T> listIterator() {
|
||||
return listIterator(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ListIterator<T> listIterator(int index) {
|
||||
return new ListIterator<>() {
|
||||
|
@ -2,48 +2,28 @@ package fr.pandacube.lib.util;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* This class contains various methods to manipulate and display memory measurements.
|
||||
*/
|
||||
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) {
|
||||
/**
|
||||
* Generate a string representation of the provided memory amount, using the provided memory unit and either to use
|
||||
* SI or traditional units.
|
||||
* <p>
|
||||
* <b>This method returns the unit symbol in French.</b>
|
||||
* @param bytes the memory amount to format.
|
||||
* @param roundTo the memory unit to convert and display the amount to.
|
||||
* @param si true to use SI unit, false to use traditional.
|
||||
* @return a string representation of the provided memory amount.
|
||||
*/
|
||||
public static String humanReadableSize(long bytes, MemoryUnit roundTo, boolean si) {
|
||||
|
||||
boolean neg = octet < 0;
|
||||
boolean neg = bytes < 0;
|
||||
|
||||
long size = Math.abs(octet);
|
||||
long size = Math.abs(bytes);
|
||||
|
||||
MemoryUnit unit = roundTo;
|
||||
for (int ui = MemoryUnit.values().length - 1; ui >= 0; ui--) {
|
||||
@ -58,7 +38,7 @@ public class MemoryUtil {
|
||||
|
||||
String dispValue;
|
||||
if (unit == roundTo) {
|
||||
dispValue = ""+unit.toUnitRound(size, si);
|
||||
dispValue = "" + unit.toUnitRound(size, si);
|
||||
}
|
||||
else {
|
||||
dispValue = format.format(unit.toUnit(size, si));
|
||||
@ -67,8 +47,116 @@ public class MemoryUtil {
|
||||
return (neg ? "-" : "") + dispValue + unit.unit(si);
|
||||
}
|
||||
|
||||
public static String humanReadableSize(long octet) {
|
||||
return humanReadableSize(octet, MemoryUnit.B, false);
|
||||
/**
|
||||
* Generate a string representation of the provided memory amount, displayinh the value in byte (as is) and with the
|
||||
* unit symbol {@code "o"}.
|
||||
* <p>
|
||||
* <b>This method returns the unit symbol in French.</b>
|
||||
* @param bytes the memory amount to format.
|
||||
* @return a string representation of the provided memory amount.
|
||||
*/
|
||||
public static String humanReadableSize(long bytes) {
|
||||
return humanReadableSize(bytes, MemoryUnit.B, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration of comonly used unit of memory prefix.
|
||||
*/
|
||||
public enum MemoryUnit {
|
||||
|
||||
/**
|
||||
* Byte unit.
|
||||
* <p>
|
||||
* SI unit: 10<sup>0</sup> = 1B<br>
|
||||
* Traditional unit: 2<sup>0</sup> = 1B
|
||||
*/
|
||||
B(1, 1, null),
|
||||
|
||||
/**
|
||||
* Kilobyte unit.
|
||||
* <p>
|
||||
* SI unit: 10<sup>3</sup>B = 1000B = 1KB (SI unit)<br>
|
||||
* Traditional unit: 2<sup>10</sup> = 1024B = 1KiB
|
||||
*/
|
||||
KB(1024, 1000, "k"),
|
||||
|
||||
/**
|
||||
* Megabyte unit.
|
||||
* <p>
|
||||
* SI unit: 10<sup>6</sup>B = 1000000B = 1MB (SI unit)<br>
|
||||
* Traditional unit: 2<sup>20</sup> = 1048576B = 1MiB
|
||||
*/
|
||||
MB(1024 * 1024, 1000_000, "M"),
|
||||
|
||||
/**
|
||||
* Gigabyte unit.
|
||||
* <p>
|
||||
* SI unit: 10<sup>9</sup>B = 1000000000B = 1GB (SI unit)<br>
|
||||
* Traditional unit: 2<sup>30</sup> = 1073741824B = 1GiB
|
||||
*/
|
||||
GB(1024 * 1024 * 1024, 1000_000_000, "G");
|
||||
|
||||
/**
|
||||
* The traditional (power of 2) value of this memory unit, in byte.
|
||||
*/
|
||||
public final long valueTrad;
|
||||
|
||||
/**
|
||||
* The SI standard (power of 10) value of this memory unit, in byte.
|
||||
*/
|
||||
public final long valueSI;
|
||||
|
||||
/**
|
||||
* The prefix symbol for this unit.
|
||||
*/
|
||||
public final String unitMultiplier;
|
||||
|
||||
/**
|
||||
* Converts the provided memory amount to this unit, rounded down (using integer division).
|
||||
* @param byteCount the memory amount to convert.
|
||||
* @param si true to use SI unit, false to use traditional.
|
||||
* @return the converted value.
|
||||
*/
|
||||
public long toUnitRound(long byteCount, boolean si) {
|
||||
return byteCount / value(si);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided memory amount to this unit.
|
||||
* @param byteCount the memory amount to convert.
|
||||
* @param si true to use SI unit, false to use traditional.
|
||||
* @return the converted value.
|
||||
*/
|
||||
public double toUnit(long byteCount, boolean si) {
|
||||
return byteCount / (double)value(si);
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of this memory unit, in byte, in either SI or traditional unit.
|
||||
* @param si true to use SI unit, false to use traditional.
|
||||
* @return value of this memory unit, in byte.
|
||||
*/
|
||||
public long value(boolean si) {
|
||||
return si ? valueSI : valueTrad;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full unit symbol of this unit, that is the prefix {@link #unitMultiplier} concatenated with
|
||||
* either "o" for SI unit or "io" to traditional unit.
|
||||
* @param si true to use SI unit, false to use traditional.
|
||||
* @return the full unit symbol of this unit.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,52 +8,96 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Enumeration of all known, post Netty-rewrite (1.7.2+), stable Minecraft Java versions.
|
||||
* <p>
|
||||
* It provides various utility methods to nicely display a set of Minecraft version (for instance "1.13.x",
|
||||
* "1.16-1.16.3", "1.8.x and 1.9", "1.18.1 or 1.18.2")
|
||||
* <p>
|
||||
* Note that this enum uses one value to represent every Minecraft version using the same protocol version number.
|
||||
*/
|
||||
public enum MinecraftVersion {
|
||||
/** Minecraft versions 1.7.2 to 1.7.5, protocol version 4. */
|
||||
v1_7_2_to_1_7_5(4, "1.7.2-1.7.5"),
|
||||
/** Minecraft versions 1.7.6 to 1.7.10, protocol version 5. */
|
||||
v1_7_6_to_1_7_10(5, "1.7.6-1.7.10"),
|
||||
|
||||
/** Minecraft versions 1.8.x, protocol version 47. */
|
||||
v1_8(47, "1.8.x"),
|
||||
|
||||
/** Minecraft version 1.9, protocol version 107. */
|
||||
v1_9(107, "1.9"),
|
||||
/** Minecraft version 1.9.1, protocol version 108. */
|
||||
v1_9_1(108, "1.9.1"),
|
||||
/** Minecraft version 1.9.2, protocol version 109. */
|
||||
v1_9_2(109, "1.9.2"),
|
||||
/** Minecraft versions 1.9.3 and 1.9.4, protocol version 110. */
|
||||
v1_9_3_to_1_9_4(110, "1.9.3", "1.9.4"),
|
||||
|
||||
/** Minecraft versions 1.10.x, protocol version 210. */
|
||||
v1_10(210, "1.10.x"),
|
||||
|
||||
/** Minecraft version 1.11, protocol version 315. */
|
||||
v1_11(315, "1.11"),
|
||||
/** Minecraft versions 1.11.1 and 1.11.2, protocol version 316. */
|
||||
v1_11_1_to_1_11_2(316, "1.11.1", "1.11.2"),
|
||||
|
||||
/** Minecraft version 1.12, protocol version 335. */
|
||||
v1_12(335, "1.12"),
|
||||
/** Minecraft version 1.12.1, protocol version 338. */
|
||||
v1_12_1(338, "1.12.1"),
|
||||
/** Minecraft version 1.12.2, protocol version 340. */
|
||||
v1_12_2(340, "1.12.2"),
|
||||
|
||||
/** Minecraft version 1.13, protocol version 393. */
|
||||
v1_13(393, "1.13"),
|
||||
/** Minecraft version 1.13.1, protocol version 401. */
|
||||
v1_13_1(401, "1.13.1"),
|
||||
/** Minecraft version 1.13.2, protocol version 404. */
|
||||
v1_13_2(404, "1.13.2"),
|
||||
|
||||
/** Minecraft version 1.14, protocol version 477. */
|
||||
v1_14(477, "1.14"),
|
||||
/** Minecraft version 1.14.1, protocol version 480. */
|
||||
v1_14_1(480, "1.14.1"),
|
||||
/** Minecraft version 1.14.2, protocol version 485. */
|
||||
v1_14_2(485, "1.14.2"),
|
||||
/** Minecraft version 1.14.3, protocol version 490. */
|
||||
v1_14_3(490, "1.14.3"),
|
||||
/** Minecraft version 1.14.4, protocol version 498. */
|
||||
v1_14_4(498, "1.14.4"),
|
||||
|
||||
/** Minecraft version 1.15, protocol version 573. */
|
||||
v1_15(573, "1.15"),
|
||||
/** Minecraft version 1.15.1, protocol version 575. */
|
||||
v1_15_1(575, "1.15.1"),
|
||||
/** Minecraft version 1.15.2, protocol version 578. */
|
||||
v1_15_2(578, "1.15.2"),
|
||||
|
||||
/** Minecraft version 1.16, protocol version 735. */
|
||||
v1_16(735, "1.16"),
|
||||
/** Minecraft version 1.16.1, protocol version 736. */
|
||||
v1_16_1(736, "1.16.1"),
|
||||
/** Minecraft version 1.16.2, protocol version 751. */
|
||||
v1_16_2(751, "1.16.2"),
|
||||
/** Minecraft version 1.16.3, protocol version 753. */
|
||||
v1_16_3(753, "1.16.3"),
|
||||
/** Minecraft versions 1.16.4 and 1.16.5, protocol version 754. */
|
||||
v1_16_4_to_1_16_5(754, "1.16.4", "1.16.5"),
|
||||
|
||||
/** Minecraft version 1.17, protocol version 755. */
|
||||
v1_17(755, "1.17"),
|
||||
/** Minecraft version 1.17.1, protocol version 756. */
|
||||
v1_17_1(756, "1.17.1"),
|
||||
|
||||
/** Minecraft versions 1.18 and 1.18.1, protocol version 757. */
|
||||
v1_18_to_1_18_1(757, "1.18", "1.18.1"),
|
||||
/** Minecraft version 1.18.2, protocol version 758. */
|
||||
v1_18_2(758, "1.18.2"),
|
||||
|
||||
/** Minecraft version 1.19, protocol version 759. */
|
||||
v1_19(759, "1.19"),
|
||||
/** Minecraft version 1.19.1, protocol version 759. */
|
||||
v1_19_1(760, "1.19.1");
|
||||
|
||||
// IMPORTANT: don't forget to update the versionMergeDisplay value when adding a new version;
|
||||
@ -156,45 +200,144 @@ public enum MinecraftVersion {
|
||||
}
|
||||
|
||||
|
||||
public final int id;
|
||||
public final List<String> versionDisplay;
|
||||
/**
|
||||
* The protocol version number of this Minecraft version.
|
||||
*/
|
||||
public final int protocolVersionNumber;
|
||||
/**
|
||||
* All Minecraft version supported by this protocol version number.
|
||||
*/
|
||||
public final List<String> versionsDisplay;
|
||||
|
||||
MinecraftVersion(int v, String... d) {
|
||||
id = v;
|
||||
versionDisplay = Arrays.asList(d);
|
||||
MinecraftVersion(int protocolVersionNumber, String... versionsDisplay) {
|
||||
this.protocolVersionNumber = protocolVersionNumber;
|
||||
this.versionsDisplay = Arrays.asList(versionsDisplay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStringAnd();
|
||||
return name() + "{protocol=" + protocolVersionNumber + ", toString(\"and\")=" + toString("and") + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of all the Minecraft version of this enum value, using
|
||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)}.
|
||||
*
|
||||
* @param finalWordSeparator the word separator between the two last versions in the returned string, like "and",
|
||||
* "or" or any other word of any language. The spaces before and after are already
|
||||
* concatenated.
|
||||
* @return a string representation of this {@link MinecraftVersion}.
|
||||
*/
|
||||
public String toString(String finalWordSeparator) {
|
||||
return StringUtil.joinGrammatically(", ", " " + finalWordSeparator + " ", versionsDisplay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of all the Minecraft version of this enum value, using
|
||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the gramatical word "et"
|
||||
* ("and" in french).
|
||||
*
|
||||
* @return a string representation of this {@link MinecraftVersion}.
|
||||
* @deprecated it uses the hardcoded french word "et" as the final word separator.
|
||||
* Use {@link #displayOptimizedListOfVersions(List, String)} with "et" as the last parameter instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String toStringAnd() {
|
||||
return StringUtil.joinGrammatically(", ", " et ", versionDisplay);
|
||||
return toString("et");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of all the Minecraft version of this enum value, using
|
||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the gramatical word "ou"
|
||||
* ("or" in french).
|
||||
*
|
||||
* @return a string representation of this {@link MinecraftVersion}.
|
||||
* @deprecated it uses the hardcoded french word "ou" as the final word separator.
|
||||
* Use {@link #displayOptimizedListOfVersions(List, String)} with "ou" as the last parameter instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String toStringOr() {
|
||||
return StringUtil.joinGrammatically(", ", " ou ", versionDisplay);
|
||||
return toString("ou");
|
||||
}
|
||||
|
||||
public static MinecraftVersion getVersion(int v) {
|
||||
for (MinecraftVersion mcV : values())
|
||||
if (mcV.id == v) return mcV;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the {@link MinecraftVersion} instance associated with the provided protocol version number.
|
||||
*
|
||||
* @param protocolVersionNumber the protocol version number
|
||||
* @return the {@link MinecraftVersion} instance associated with the provided protocol version number, or null if
|
||||
* there is none.
|
||||
*/
|
||||
public static MinecraftVersion getVersion(int protocolVersionNumber) {
|
||||
for (MinecraftVersion mcV : values())
|
||||
if (mcV.protocolVersionNumber == protocolVersionNumber) return mcV;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a string representation of the provided list of version, using
|
||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)}.
|
||||
*
|
||||
* @param versions the minecraft versions to list
|
||||
* @param finalWordSeparator the word separator between the two last versions in the returned string, like "and",
|
||||
* "or" or any other word of any language. The spaces before and after are already
|
||||
* concatenated.
|
||||
* @return a string representation of the provided list of version.
|
||||
*/
|
||||
public static String displayOptimizedListOfVersions(List<MinecraftVersion> versions, String finalWordSeparator) {
|
||||
return StringUtil.joinGrammatically(", ", " " + finalWordSeparator + " ", getVersionsDisplayList(versions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a string representation of the provided list of version, using
|
||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the gramatical word "et"
|
||||
* ("and" in french).
|
||||
*
|
||||
* @param versions the minecraft versions to list
|
||||
* @return a string representation of the provided list of version.
|
||||
* @deprecated it uses the hardcoded french word "et" as the final word separator.
|
||||
* Use {@link #displayOptimizedListOfVersions(List, String)} with "et" as the last parameter instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String displayOptimizedListOfVersionsAnd(List<MinecraftVersion> versions) {
|
||||
return StringUtil.joinGrammatically(", ", " et ", getVersionsDisplayList(versions));
|
||||
return displayOptimizedListOfVersions(versions, "et");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a string representation of the provided list of version, using
|
||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the gramatical word "ou"
|
||||
* ("or" in french).
|
||||
*
|
||||
* @param versions the minecraft versions to list
|
||||
* @return a string representation of the provided list of version.
|
||||
* @deprecated it uses the hardcoded french word "ou" as the final word separator.
|
||||
* Use {@link #displayOptimizedListOfVersions(List, String)} with "ou" as the last parameter instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String displayOptimizedListOfVersionsOr(List<MinecraftVersion> versions) {
|
||||
return StringUtil.joinGrammatically(", ", " ou ", getVersionsDisplayList(versions));
|
||||
return displayOptimizedListOfVersions(versions, "ou");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an optimized list of string representation of Minecraft version, that represent the provided list of
|
||||
* Minecraft version.
|
||||
* <p>
|
||||
* This methods try to merge successive Minecraft version into a single string: for instance, all versions from 1.18
|
||||
* to 1.18.2 are represented by the string "1.18.x"; all version from 1.14.1 to 1.14.4 are represented by the string
|
||||
* "1.14.1-1.14.4".
|
||||
* <p>
|
||||
* All possible merges of {@link MinecraftVersion} are listed in the static initializer of this enum.
|
||||
*
|
||||
* @param vList the {@link List} of {@link MinecraftVersion}
|
||||
* @return an optimized list of string representation of Minecraft version.
|
||||
*/
|
||||
public static List<String> getVersionsDisplayList(List<MinecraftVersion> vList) {
|
||||
if (vList == null)
|
||||
return new ArrayList<>();
|
||||
@ -218,7 +361,7 @@ public enum MinecraftVersion {
|
||||
}
|
||||
|
||||
if (vSubSet.size() == 1) {
|
||||
ret.addAll(values()[i].versionDisplay);
|
||||
ret.addAll(values()[i].versionsDisplay);
|
||||
}
|
||||
else {
|
||||
ret.addAll(versionMergeDisplay.get(vSubSet));
|
||||
|
@ -1,16 +1,26 @@
|
||||
package fr.pandacube.lib.util;
|
||||
|
||||
/**
|
||||
* Provides utility methods around Minecraft and Web stuff.
|
||||
*/
|
||||
public class MinecraftWebUtil {
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Convert a legacy Minecraft color coded String into HTML Format.
|
||||
* Convert a legacy Minecraft color coded String into HTML Format.
|
||||
* <p>
|
||||
* Each colored part of the text will be contained in a {@code <span>} tag with {@code class="cX"} where {@code X}
|
||||
* is the color code from 0 to F in uppercase.
|
||||
* The bold, striked, underlined and italic parts will be contained in a {@code <span>} tag with respectively the
|
||||
* classes {@code cL}, {@code cM}, {@code cN} and {@code cO}.
|
||||
* Some CSS properties are needed to apply the colors to the CSS classes.
|
||||
* @param chatcolorPrefix the prefix used for the color codes
|
||||
* @param legacyText the legacy text to convert to HTML.
|
||||
* @return The text formated in HTML.
|
||||
* @implNote the current implementation does not yet supports the RGB colors.
|
||||
*/
|
||||
// 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)
|
||||
// TODO update to support RGB colors (Bungee and Essentials notation). (see JS implementation at https://www.pandacube.fr/assets/js/global.js )
|
||||
// TODO moves this to pandalib-chat and use Adventure API to help serializing to HTML
|
||||
public static String fromMinecraftColorCodeToHTML(char chatcolorPrefix, String legacyText)
|
||||
{
|
||||
String color_char = "0123456789abcdefr";
|
||||
|
||||
@ -18,12 +28,12 @@ public class MinecraftWebUtil {
|
||||
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);
|
||||
for (int i=0; i<legacyText.length(); i++) {
|
||||
char c = legacyText.charAt(i);
|
||||
|
||||
if (c == code_prefix && (i<ligne.length()-1)) {
|
||||
if (c == chatcolorPrefix && (i<legacyText.length()-1)) {
|
||||
i++;
|
||||
c = ligne.charAt(i);
|
||||
c = legacyText.charAt(i);
|
||||
if (color_char.contains(String.valueOf(Character.toLowerCase(c)))) {
|
||||
if (bold) {
|
||||
builder.append("</span>");
|
||||
@ -75,7 +85,7 @@ public class MinecraftWebUtil {
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder.append(code_prefix + c);
|
||||
builder.append(chatcolorPrefix + c);
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,13 +5,37 @@ import java.util.Objects;
|
||||
import java.util.Scanner;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Utility class and program that generate offline UUIDs for provided player names.
|
||||
* <p>
|
||||
* You can generate the UUID programatically using {@link #getFromNickName(String)} and
|
||||
* {@link #getFromNickNames(String[])}.
|
||||
*
|
||||
* To use this class as a program, type
|
||||
* <pre>
|
||||
* java -cp<anyClassPathContainingThisClass> fr.pandacube.lib.util.OfflineUUID [playernames...]
|
||||
* </pre>
|
||||
* Each argument will be interpreted as a player name. If there is no argument, the program will wait for them in the
|
||||
* input stream.
|
||||
* For each player name, the program will print the player name, a {@code tab} character, the UUID and a line separator.
|
||||
*/
|
||||
public class OfflineUUID {
|
||||
|
||||
/**
|
||||
* Generate the offline {@link UUID} of the provided player name.
|
||||
* @param nickname the player name to optain the offline UUID from.
|
||||
* @return the offline {@link UUID} of the provided player name.
|
||||
*/
|
||||
public static UUID getFromNickName(String nickname) {
|
||||
byte[] from_str = ("OfflinePlayer:" + nickname).getBytes(StandardCharsets.UTF_8);
|
||||
return UUID.nameUUIDFromBytes(from_str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the offline {@link UUID}s of the provided player names.
|
||||
* @param nicknames an array of player name to optain the offline UUIDs from.
|
||||
* @return the offline {@link UUID}s of the provided player name in an array, at the same order as the input.
|
||||
*/
|
||||
public static UUID[] getFromNickNames(String[] nicknames) {
|
||||
Objects.requireNonNull(nicknames);
|
||||
|
||||
@ -22,24 +46,25 @@ public class OfflineUUID {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Main method for this class.
|
||||
* @param args the arguments. One argument is one player name.
|
||||
*/
|
||||
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())
|
||||
System.err.print("Please input a player name: ");
|
||||
if (!s.hasNextLine())
|
||||
break;
|
||||
String line = s.nextLine();
|
||||
System.out.println(getFromNickName(line));
|
||||
System.out.println(line + "\t" + getFromNickName(line));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (String arg : args)
|
||||
System.out.println("" + arg + ":" + getFromNickName(arg));
|
||||
System.out.println(arg + "\t" + getFromNickName(arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,67 +3,118 @@ package fr.pandacube.lib.util;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
/**
|
||||
* Utility class to generate random things.
|
||||
*/
|
||||
public class RandomUtil {
|
||||
|
||||
/**
|
||||
* The unique {@link Random} instance used in this class. Can also be used else where when needed.
|
||||
*/
|
||||
public static final Random rand = new Random();
|
||||
|
||||
/**
|
||||
* Returns a randomly generated integer between {@code minInclu} included and {@code maxExclu} excluded.
|
||||
* @param minInclu the minimum value, included.
|
||||
* @param maxExclu the maximum value, excluded.
|
||||
* @return a random number between {@code minInclu} included and {@code maxExclu} excluded.
|
||||
* @see Random#nextInt(int, int)
|
||||
* @throws IllegalArgumentException if {@code minInclu} is greater than {@code maxExclu}.
|
||||
* @deprecated Use {@link Random#nextInt(int, int)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
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()));
|
||||
return rand.nextInt(minInclu, maxExclu);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Returns a randomly generated double between {@code minInclu} included and {@code maxExclu} excluded.
|
||||
* @param minInclu the minimum value, included.
|
||||
* @param maxExclu the maximum value, excluded.
|
||||
* @return a random number between {@code minInclu} included and {@code maxExclu} excluded.
|
||||
* @see Random#nextDouble(double, double)
|
||||
* @throws IllegalArgumentException if {@code minInclu} is greater than {@code maxExclu}.
|
||||
* @deprecated Use {@link Random#nextDouble(double, double)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static double nextDoubleBetween(double minInclu, double maxExclu) {
|
||||
return rand.nextDouble(minInclu, maxExclu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random element from the provided array.
|
||||
* @param array the array in whith to pick a value randomly.
|
||||
* @return the value randomly picked from the array, or null if the array is null or empty.
|
||||
* @param <T> the type of the array elements.
|
||||
* @see Random#nextInt(int)
|
||||
*/
|
||||
public static <T> T arrayElement(T[] array) {
|
||||
return (array == null || array.length == 0) ? null : array[rand.nextInt(array.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random element from the provided list.
|
||||
* @param list the list in whith to pick a value randomly.
|
||||
* @return the value randomly picked from the list, or null if the array is null or empty.
|
||||
* @param <T> the type of the list elements.
|
||||
* @see Random#nextInt(int)
|
||||
*/
|
||||
public static <T> T listElement(List<T> list) {
|
||||
return (list == null || list.isEmpty()) ? null : list.get(rand.nextInt(list.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random character from the provided string.
|
||||
* @param str the string in whith to pick a character randomly.
|
||||
* @return the character randomly picked from the string, or {@code '\0'} if the string is null or empty.
|
||||
* @see Random#nextInt(int)
|
||||
*/
|
||||
public static char stringChar(String str) {
|
||||
return (str == null || str.isEmpty()) ? '\0' : str.charAt(rand.nextInt(str.length()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random value from the provided set.
|
||||
* @param set the set in which to pick a value randomly.
|
||||
* @return the value randomly picked from the list, or null if the set is null or empty.
|
||||
* @param <T> the type of the set elements.
|
||||
* @implNote The current implementation uses the iterator of the set to pick a random value, since there is no way
|
||||
* to directly pick a value using an index.
|
||||
* @throws IllegalStateException if the set has reduced in size during the execution of this method, making the
|
||||
* iterator reaching the end of the set before getting the value the random generator picked.
|
||||
*/
|
||||
public static <T> T setElement(Set<T> set) {
|
||||
if (set.isEmpty())
|
||||
throw new IllegalArgumentException("set is empty");
|
||||
if (set == null || set.isEmpty())
|
||||
return null;
|
||||
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");
|
||||
throw new IllegalStateException("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)
|
||||
* @param frequencies 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 no provided frequency)
|
||||
* @throws IllegalArgumentException if frequencies is null.
|
||||
*/
|
||||
public static int randomIndexOfFrequencies(double... f) {
|
||||
if (f == null)
|
||||
throw new IllegalArgumentException("f cannot be null");
|
||||
int n = f.length;
|
||||
public static int randomIndexOfFrequencies(double... frequencies) {
|
||||
if (frequencies == null)
|
||||
throw new IllegalArgumentException("frequencies cannot be null");
|
||||
int n = frequencies.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]);
|
||||
if (frequencies[i] < 0)
|
||||
throw new IllegalArgumentException("frequencies[" + i + "] cannot be negative.");
|
||||
fSums[i] = (sum += frequencies[i]);
|
||||
}
|
||||
double r = rand.nextDouble() * sum;
|
||||
for (int i = 0; i < n; i++) {
|
||||
@ -74,20 +125,47 @@ public class RandomUtil {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A set of characters representing all the lowercase letters of the latin alphabet (only in the ASCII table).
|
||||
*/
|
||||
public static final String PASSWORD_CHARSET_LATIN_LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
/**
|
||||
* A set of characters representing all the uppercase letters of the latin alphabet (only in the ASCII table).
|
||||
*/
|
||||
public static final String PASSWORD_CHARSET_LATIN_UPPERCASE = PASSWORD_CHARSET_LATIN_LOWERCASE.toUpperCase();
|
||||
|
||||
/**
|
||||
* A set of characters representing all the number digits, from 0 to 9.
|
||||
*/
|
||||
public static final String PASSWORD_CHARSET_DIGIT = "0123456789";
|
||||
|
||||
/**
|
||||
* A set of characters representing some visible special characters in the ASCII table.
|
||||
*/
|
||||
public static final String PASSWORD_CHARSET_SPECIAL = "@#+*/-;:,.?!='()[]{}&";
|
||||
|
||||
/**
|
||||
* A set of characters representing uppercase and lowercase latin alphabet letters and digits, exclusing some that
|
||||
* can be confusing to read (like {@code iIl1} or {@code oO0}).
|
||||
*/
|
||||
public static final String PASSWORD_CHARSET_NO_ANBIGUITY = "abcdefghkmnpqrstwxyzACDEFGHKLMNPQRSTWXYZ2345679";
|
||||
|
||||
/**
|
||||
* Generate a random password of the provided length, using the characters listed in {@link #PASSWORD_CHARSET_NO_ANBIGUITY}
|
||||
* @param length the length of the generated password.
|
||||
* @return the generated password.
|
||||
*/
|
||||
public static String randomPassword(int length) {
|
||||
return randomPassword(length, PASSWORD_CHARSET_NO_ANBIGUITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random password of the provided length, using the provided characters in a string.
|
||||
* @param length the length of the generated password.
|
||||
* @param charset the characters to use. It’s possible to use of the {@code PASSWORD_*} static strings in this class.
|
||||
* @return the generated password.
|
||||
*/
|
||||
public static String randomPassword(int length, String charset) {
|
||||
char[] pw = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
|
@ -3,7 +3,16 @@ package fr.pandacube.lib.util;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides various methods to manipulate Strings.
|
||||
*/
|
||||
public class StringUtil {
|
||||
|
||||
/**
|
||||
* Format the provided double, omitting the decimal part if the provided double is strictly equals to a long value.
|
||||
* @param d the value to convert to string.
|
||||
* @return a string representation of the double value.
|
||||
*/
|
||||
public static String formatDouble(double d) {
|
||||
if (d == (long) d)
|
||||
return String.format("%d", (long) d);
|
||||
@ -11,34 +20,62 @@ public class StringUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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>
|
||||
* Counts the number of occurence of a speficied character in a string.
|
||||
* @param string the character sequence to search into.
|
||||
* @param character the character to count.
|
||||
* @return the number of occurence of
|
||||
* @deprecated Because it uses snake_case naming convention. Use {@link #countOccurences(CharSequence, char)} instead.
|
||||
*/
|
||||
public static int char_count(CharSequence s, char c_match) {
|
||||
char[] chars = s.toString().toCharArray();
|
||||
@Deprecated(forRemoval = true, since = "2022-07-26")
|
||||
public static int char_count(CharSequence string, char character) {
|
||||
return countOccurences(string, character);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of occurence of a speficied character in a string.
|
||||
* @param string the character sequence to search into.
|
||||
* @param character the character to count.
|
||||
* @return the number of occurence of
|
||||
*/
|
||||
public static int countOccurences(CharSequence string, char character) {
|
||||
int count = 0;
|
||||
for (char c : chars)
|
||||
if (c == c_match) count++;
|
||||
for (char c : string.toString().toCharArray()) {
|
||||
if (c == character) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static String joinGrammatically(CharSequence sep1, CharSequence sepFinal, List<String> strings) {
|
||||
/**
|
||||
* Do like {@link String#join(CharSequence, Iterable)}, but the last separator is different than the others.
|
||||
* It is usedful when enumerating thins in a sentense, for instance : <code>"a thing<u>, </u>a thing<u> and </u>a thing"</code>
|
||||
* (the coma being the usual separator, and {@code " and "} being the final separator).
|
||||
* @param regularSeparator the separator used everywhere except between the two last strings to join.
|
||||
* @param finalSeparator the separator used between the two last strings to join.
|
||||
* @param strings the strings to join.
|
||||
* @return a new string will all the provided {@code strings} joined using the separators.
|
||||
*/
|
||||
public static String joinGrammatically(CharSequence regularSeparator, CharSequence finalSeparator, 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);
|
||||
return switch (size) {
|
||||
case 0 -> "";
|
||||
case 1 -> strings.get(0);
|
||||
default -> String.join(regularSeparator, strings.subList(0, --size)) + finalSeparator + strings.get(size);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static String repeat(char base, int count) {
|
||||
char[] chars = new char[count];
|
||||
/**
|
||||
* Create a {@link String} that repeats the base character n times.
|
||||
* @param base the base character.
|
||||
* @param n the number of repetition.
|
||||
* @return a {@link String} that repeats the base character n times.
|
||||
*/
|
||||
public static String repeat(char base, int n) {
|
||||
char[] chars = new char[n];
|
||||
Arrays.fill(chars, base);
|
||||
return new String(chars);
|
||||
return String.valueOf(chars);
|
||||
}
|
||||
}
|
||||
|
@ -6,16 +6,23 @@ import java.io.PrintStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Utility class to easily manipulate {@link Throwable}s.
|
||||
*/
|
||||
public class ThrowableUtil {
|
||||
|
||||
|
||||
/**
|
||||
* Convert a {@link Throwable} into a {@link String} using the {@link Throwable#printStackTrace(PrintStream)} method,
|
||||
* so the returned string contains the full stack trace.
|
||||
* @param t the {@link Throwable}
|
||||
* @return a {@link String} containing the full stack thace of the provided {@link Throwable}.
|
||||
*/
|
||||
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)) {
|
||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
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;
|
||||
@ -33,6 +40,7 @@ public class ThrowableUtil {
|
||||
* @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.
|
||||
* @param <T> the type of the returned object
|
||||
*/
|
||||
public static <T> T wrapEx(SupplierException<T> supp) {
|
||||
try {
|
||||
@ -58,10 +66,12 @@ public class ThrowableUtil {
|
||||
|
||||
|
||||
/**
|
||||
* Wraps a {@link SupplierException} into a try catch.
|
||||
* Wraps a {@link SupplierException} into a try catch, with special handling of subclasses of
|
||||
* {@link ReflectiveOperationException}.
|
||||
* @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.
|
||||
* @param <T> the type of the returned object
|
||||
*/
|
||||
public static <T> T wrapReflectEx(SupplierException<T> supp) {
|
||||
try {
|
||||
@ -72,7 +82,8 @@ public class ThrowableUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a {@link RunnableException} into a try catch.
|
||||
* Wraps a {@link RunnableException} into a try catch with special handling of subclasses of
|
||||
* {@link ReflectiveOperationException}.
|
||||
* @param run the {@link RunnableException} to run.
|
||||
* @throws RuntimeException if the provided {@link RunnableException} throws a checked exception.
|
||||
*/
|
||||
@ -87,20 +98,29 @@ public class ThrowableUtil {
|
||||
|
||||
|
||||
/**
|
||||
* A supplier that can possibly throw a checked exception
|
||||
* A supplier that can possibly throw a checked exception.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SupplierException<T> {
|
||||
/**
|
||||
* Gets a result.
|
||||
* @return a result.
|
||||
* @throws Exception if implementation failed to run.
|
||||
*/
|
||||
T get() throws Exception;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A runnable that can possibly throw a checked exception
|
||||
* A runnable that can possibly throw a checked exception.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RunnableException {
|
||||
/**
|
||||
* Run any code implemented.
|
||||
* @throws Exception if implementation failed to run.
|
||||
*/
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
package fr.pandacube.lib.util;
|
||||
|
||||
/**
|
||||
* Provides methods related to Minecraft Java server ticks.
|
||||
*/
|
||||
public class Tick {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of tick is the provided duration, in second
|
||||
* Returns the number of tick is the provided number of seconds.
|
||||
* @param seconds the duration in second
|
||||
* @return the same duration as provided, but in Minecraft server ticks
|
||||
*/
|
||||
@ -13,7 +16,7 @@ public class Tick {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of tick is the provided duration, in second
|
||||
* Returns the number of tick is the provided number of minutes.
|
||||
* @param minutes the duration in minutes
|
||||
* @return the same duration as provided, but in Minecraft server ticks
|
||||
*/
|
||||
|
@ -7,7 +7,6 @@ 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;
|
||||
@ -17,38 +16,68 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Utility class providing methods to display human readable time and duration, and parse duration strings.
|
||||
*
|
||||
* The methods that return date and daytime are hardcoded in French.
|
||||
*/
|
||||
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 cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Locale.FRENCH);
|
||||
private static final DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Locale.FRENCH);
|
||||
private static final DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Locale.FRENCH);
|
||||
private static final DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Locale.FRENCH);
|
||||
private static final DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Locale.FRENCH);
|
||||
private static final DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Locale.FRENCH);
|
||||
|
||||
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());
|
||||
private static final DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.FRENCH);
|
||||
private static final DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Locale.FRENCH);
|
||||
private static final DateTimeFormatter HFormatter = DateTimeFormatter.ofPattern("H'h'", Locale.FRENCH);
|
||||
|
||||
|
||||
public static String relativeDateFr(long displayTime, boolean showSeconds, boolean compactWords) {
|
||||
return relativeDateFr(displayTime,
|
||||
/**
|
||||
* Provides a human readable date of the provided time, with ability to adapt the text relatively to the current
|
||||
* time (for instance "il y a 13 minutes" (french for "13 minutes ago"))
|
||||
* <p>
|
||||
* <b>This method renders the text in French.</b>
|
||||
*
|
||||
* @param time the timestamp in milliseconds of the time to diplay.
|
||||
* @param showSeconds if the returned string should includes seconds (true) or not (false). To have more control
|
||||
* over the precision, call {@link #relativeDateFr(long, RelativePrecision, DisplayPrecision,
|
||||
* boolean)}.
|
||||
* @param compactWords true to use compact words, false to use full words.
|
||||
* @return a human readable {@link String} representation of the provided time.
|
||||
*/
|
||||
public static String relativeDateFr(long time, boolean showSeconds, boolean compactWords) {
|
||||
return relativeDateFr(time,
|
||||
showSeconds ? RelativePrecision.SECONDS : RelativePrecision.MINUTES,
|
||||
showSeconds ? DisplayPrecision.SECONDS : DisplayPrecision.MINUTES,
|
||||
compactWords);
|
||||
}
|
||||
|
||||
|
||||
public static String relativeDateFr(long displayTime, RelativePrecision relPrecision, DisplayPrecision dispPrecision, boolean compactWords) {
|
||||
/**
|
||||
* Provides a human readable date of the provided time, with ability to adapt the text relatively to the current
|
||||
* time (for instance "il y a 13 minutes" (french for "13 minutes ago"))
|
||||
* <p>
|
||||
* <b>This method renders the text in French.</b>
|
||||
*
|
||||
* @param time the timestamp in milliseconds of the time to diplay.
|
||||
* @param relPrecision the precision of the relative text.
|
||||
* @param dispPrecision the precision of the full date and time.
|
||||
* @param compactWords true to use compact words, false to use full words.
|
||||
* @return a human readable {@link String} representation of the provided time.
|
||||
*/
|
||||
public static String relativeDateFr(long time, RelativePrecision relPrecision, DisplayPrecision dispPrecision, boolean compactWords) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
|
||||
LocalDateTime displayDateTime = toLocalDateTime(displayTime);
|
||||
LocalDateTime displayDateTime = toLocalDateTime(time);
|
||||
LocalDateTime currentDateTime = toLocalDateTime(currentTime);
|
||||
|
||||
|
||||
long timeDiff = currentTime - displayTime;
|
||||
long timeDiff = currentTime - time;
|
||||
long timeDiffSec = timeDiff / 1000;
|
||||
|
||||
if (timeDiffSec < -1) {
|
||||
@ -57,7 +86,7 @@ public class TimeUtil {
|
||||
if (timeDiffSec > -60)
|
||||
return "dans " + (-timeDiffSec) + (compactWords ? "s" : " secondes");
|
||||
}
|
||||
if (relPrecision.morePreciseOrEqTo(RelativePrecision.MINUTES)) {
|
||||
if (relPrecision.ordinal() >= RelativePrecision.MINUTES.ordinal()) {
|
||||
if (timeDiffSec > -60)
|
||||
return compactWords ? "dans moins d’1min" : "dans moins d’une minute";
|
||||
if (timeDiffSec > -60*2) // dans 2 min
|
||||
@ -65,7 +94,7 @@ public class TimeUtil {
|
||||
if (timeDiffSec > -3600) // dans moins d’1h
|
||||
return "dans " + (-timeDiffSec/60) + (compactWords ? "min" : " minutes");
|
||||
}
|
||||
if (relPrecision.morePreciseOrEqTo(RelativePrecision.HOURS)) {
|
||||
if (relPrecision.ordinal() >= RelativePrecision.HOURS.ordinal()) {
|
||||
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
|
||||
@ -73,16 +102,16 @@ public class TimeUtil {
|
||||
if (timeDiffSec > -3600*12) // dans moins de 12h
|
||||
return "dans " + (-timeDiffSec/3600) + (compactWords ? "h" : " heures");
|
||||
}
|
||||
if (relPrecision.morePreciseOrEqTo(RelativePrecision.DAYS)) {
|
||||
if (relPrecision.ordinal() >= RelativePrecision.DAYS.ordinal()) {
|
||||
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);
|
||||
return "aujourd’hui à " + dayTimeFr(time, dispPrecision);
|
||||
if (displayDateTime.isBefore(nextMidnight.plusDays(1))) // demain
|
||||
return "demain à " + dayTimeFr(displayTime, dispPrecision);
|
||||
return "demain à " + dayTimeFr(time, dispPrecision);
|
||||
if (displayDateTime.isBefore(nextMidnight.plusDays(5))) // dans moins d'1 semaine
|
||||
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " "
|
||||
+ dayOfMonthFormatter.format(displayDateTime) + " à "
|
||||
+ dayTimeFr(displayTime, dispPrecision);
|
||||
+ dayTimeFr(time, dispPrecision);
|
||||
}
|
||||
|
||||
}
|
||||
@ -95,7 +124,7 @@ public class TimeUtil {
|
||||
if (timeDiffSec < 60) // ya moins d'1 min
|
||||
return "il y a " + timeDiffSec + (compactWords ? "s" : " secondes");
|
||||
}
|
||||
if (relPrecision.morePreciseOrEqTo(RelativePrecision.MINUTES)) {
|
||||
if (relPrecision.ordinal() >= RelativePrecision.MINUTES.ordinal()) {
|
||||
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
|
||||
@ -103,7 +132,7 @@ public class TimeUtil {
|
||||
if (timeDiffSec < 3600) // ya moins d'1h
|
||||
return "il y a " + (timeDiffSec/60) + (compactWords ? "min" : " minutes");
|
||||
}
|
||||
if (relPrecision.morePreciseOrEqTo(RelativePrecision.HOURS)) {
|
||||
if (relPrecision.ordinal() >= RelativePrecision.HOURS.ordinal()) {
|
||||
if (timeDiffSec < 3600) // ya moins d'1h
|
||||
return "il y a moins d’une heure";
|
||||
if (timeDiffSec < 3600*2) // ya moins de 2h
|
||||
@ -111,60 +140,126 @@ public class TimeUtil {
|
||||
if (timeDiffSec < 3600*12) // ya moins de 12h
|
||||
return "il y a " + (timeDiffSec/3600) + " heures";
|
||||
}
|
||||
if (relPrecision.morePreciseOrEqTo(RelativePrecision.DAYS)) {
|
||||
if (relPrecision.ordinal() >= RelativePrecision.DAYS.ordinal()) {
|
||||
LocalDateTime lastMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0);
|
||||
if (!displayDateTime.isBefore(lastMidnight)) // aujourd'hui
|
||||
return "aujourd’hui à " + dayTimeFr(displayTime, dispPrecision);
|
||||
return "aujourd’hui à " + dayTimeFr(time, dispPrecision);
|
||||
if (!displayDateTime.isBefore(lastMidnight.minusDays(1))) // hier
|
||||
return "hier à " + dayTimeFr(displayTime, dispPrecision);
|
||||
return "hier à " + dayTimeFr(time, dispPrecision);
|
||||
if (!displayDateTime.isBefore(lastMidnight.minusDays(6))) // ya moins d'1 semaine
|
||||
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " dernier à "
|
||||
+ dayTimeFr(displayTime, dispPrecision);
|
||||
+ dayTimeFr(time, dispPrecision);
|
||||
}
|
||||
|
||||
}
|
||||
return fullDateFr(displayTime, dispPrecision, true, compactWords);
|
||||
return fullDateFr(time, dispPrecision, true, compactWords);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enumaration of different level of precision to display a relative time.
|
||||
*/
|
||||
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(); }
|
||||
/**
|
||||
* No relative display.
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Days precision for relative display.
|
||||
*/
|
||||
DAYS,
|
||||
/**
|
||||
* Hours precision for relative display.
|
||||
*/
|
||||
HOURS,
|
||||
/**
|
||||
* Minutes precision for relative display.
|
||||
*/
|
||||
MINUTES,
|
||||
/**
|
||||
* Seconds precision for relative display.
|
||||
*/
|
||||
SECONDS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumaration of different level of precision to display a date and daytime.
|
||||
*/
|
||||
public enum DisplayPrecision {
|
||||
DAYS, HOURS, MINUTES, SECONDS
|
||||
/**
|
||||
* Display only the date.
|
||||
*/
|
||||
DAYS,
|
||||
/**
|
||||
* Display the date and the hour of the day.
|
||||
*/
|
||||
HOURS,
|
||||
/**
|
||||
* Display the date and the time of the day up to the minute.
|
||||
*/
|
||||
MINUTES,
|
||||
/**
|
||||
* Display the date and the time of the day up to the second.
|
||||
*/
|
||||
SECONDS
|
||||
}
|
||||
|
||||
|
||||
public static String fullDateFr(long displayTime, boolean showSeconds, boolean showWeekday, boolean compactWords) {
|
||||
return fullDateFr(displayTime, showSeconds ? DisplayPrecision.SECONDS : DisplayPrecision.MINUTES, showWeekday, compactWords);
|
||||
/**
|
||||
* Returns a string representation of the date (and eventually day time) of the provided timestamp.
|
||||
* <p>
|
||||
* <b>This method renders the text in French.</b>
|
||||
*
|
||||
* @param timestamp the time to represent in the returned string.
|
||||
* @param showSeconds if the returned string should includes seconds (true) or not (false). To have more control
|
||||
* over the precision, call {@link #fullDateFr(long, DisplayPrecision, boolean, boolean)}.
|
||||
* @param showWeekday true to show the week day, false otherwise.
|
||||
* @param compactWords true to use compact words, false to use full words.
|
||||
* @return a string representation of the date (and eventually day time) of the provided timestamp.
|
||||
*/
|
||||
public static String fullDateFr(long timestamp, boolean showSeconds, boolean showWeekday, boolean compactWords) {
|
||||
return fullDateFr(timestamp, showSeconds ? DisplayPrecision.SECONDS : DisplayPrecision.MINUTES, showWeekday, compactWords);
|
||||
}
|
||||
|
||||
public static String fullDateFr(long displayTime, DisplayPrecision precision, boolean showWeekday, boolean compactWords) {
|
||||
LocalDateTime displayDateTime = toLocalDateTime(displayTime);
|
||||
/**
|
||||
* Returns a string representation of the date (and eventually day time) of the provided timestamp.
|
||||
* <p>
|
||||
* <b>This method renders the text in French.</b>
|
||||
*
|
||||
* @param timestamp the time to represent in the returned string.
|
||||
* @param precision the {@link DisplayPrecision} fo the returned string.
|
||||
* @param showWeekday true to show the week day, false otherwise.
|
||||
* @param compactWords true to use compact words, false to use full words.
|
||||
* @return a string representation of the date (and eventually day time) of the provided timestamp.
|
||||
*/
|
||||
public static String fullDateFr(long timestamp, DisplayPrecision precision, boolean showWeekday, boolean compactWords) {
|
||||
LocalDateTime displayDateTime = toLocalDateTime(timestamp);
|
||||
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);
|
||||
return ret + " à " + dayTimeFr(timestamp, precision);
|
||||
}
|
||||
|
||||
public static String dayTimeFr(long displayTime, DisplayPrecision precision) {
|
||||
/**
|
||||
* Returns a string representation of the time of the day of the provided timestamp.
|
||||
* <p>
|
||||
* <b>This method renders the text in French.</b>
|
||||
*
|
||||
* @param timestamp the time to represent in the returned string.
|
||||
* @param precision the {@link DisplayPrecision} fo the returned string.
|
||||
* @return a string representation of the time of the day of the provided timestamp.
|
||||
*/
|
||||
public static String dayTimeFr(long timestamp, 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));
|
||||
return tFormatter.format(toLocalDateTime(timestamp));
|
||||
}
|
||||
|
||||
|
||||
@ -173,14 +268,16 @@ public class TimeUtil {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Converts the provided duration into a human readable {@link String}.
|
||||
* @param msDuration the duration in millisecond.
|
||||
* @param hUnit the biggest unit of time to display.
|
||||
* @param lUnit the smallest unit of time to display.
|
||||
* @param spaces true to put spaces between time units (e.g.: {@code "1s 500ms"}) or false otherwise (e.g.: {@code "1s500ms"})).
|
||||
* @param fr true to use French unit symbols (it only changes the day symbol from "d" to "j").
|
||||
* @param leadingZeros to use leading zeros when necessary in front of some durations.
|
||||
* @return a {@link String} representation of the duration.
|
||||
*/
|
||||
public static String durationToLongString(long msDuration, TimeUnit hUnit, TimeUnit lUnit, boolean spaces, boolean fr, boolean leadingZeros) {
|
||||
if (lUnit.compareTo(hUnit) > 0) {
|
||||
TimeUnit tmp = lUnit;
|
||||
@ -210,14 +307,19 @@ public class TimeUtil {
|
||||
.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);
|
||||
return toStringWithPaddingZeros(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));
|
||||
return oneDisplayed.get() ? ret : (toStringWithPaddingZeros(0, leadingZeros ? timeUnitToLeftPadLength(lUnit) : 1) + timeUnitToSuffix(lUnit, fr));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides a unit symbol for the provided {@link TimeUnit}.
|
||||
* @param u the {@link TimeUnit}.
|
||||
* @param fr true to use French unit symbols (it only changes the {@link TimeUnit#DAYS} symbol from "d" to "j").
|
||||
* @return a unit symbol for the provided {@link TimeUnit}.
|
||||
*/
|
||||
public static String timeUnitToSuffix(TimeUnit u, boolean fr) {
|
||||
return switch (u) {
|
||||
case DAYS -> fr ? "j" : "d";
|
||||
@ -230,6 +332,12 @@ public class TimeUtil {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate the 0-padded length of a number for the provided {@link TimeUnit}.
|
||||
* Will returns 3 for below-second time units, 2 for seconds, munutes and hours and 1 otherwise.
|
||||
* @param u the {@link TimeUnit}
|
||||
* @return the 0-padded length of a number for the provided {@link TimeUnit}.
|
||||
*/
|
||||
public static int timeUnitToLeftPadLength(TimeUnit u) {
|
||||
return switch (u) {
|
||||
case NANOSECONDS, MICROSECONDS, MILLISECONDS -> 3;
|
||||
@ -238,7 +346,14 @@ public class TimeUtil {
|
||||
};
|
||||
}
|
||||
|
||||
public static String toString(long value, int leftPad) {
|
||||
/**
|
||||
* Converts the provided long to a {@link String} and eventually prepend any {@code "0"} necessary to make the
|
||||
* returned string’s length at least {@code leftPad}.
|
||||
* @param value the value to convert to {@link String}.
|
||||
* @param leftPad the minimal length of the returned String.
|
||||
* @return the string representation of the provided value, with eventual zeros prepended.
|
||||
*/
|
||||
public static String toStringWithPaddingZeros(long value, int leftPad) {
|
||||
String valueStr = Long.toString(value);
|
||||
int padding = leftPad - valueStr.length();
|
||||
if (padding <= 0)
|
||||
@ -255,6 +370,7 @@ public class TimeUtil {
|
||||
* 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
|
||||
* @return a {@link String} representation of the duration.
|
||||
*/
|
||||
public static String durationToString(long msDuration, boolean milliseconds) {
|
||||
return durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false);
|
||||
@ -263,6 +379,7 @@ public class TimeUtil {
|
||||
/**
|
||||
* 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
|
||||
* @return a {@link String} representation of the duration.
|
||||
*/
|
||||
public static String durationToString(long msDuration) {
|
||||
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, true, true, false);
|
||||
@ -271,6 +388,7 @@ public class TimeUtil {
|
||||
/**
|
||||
* 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
|
||||
* @return a {@link String} representation of the duration.
|
||||
*/
|
||||
public static String durationToParsableString(long msDuration) {
|
||||
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false);
|
||||
@ -279,9 +397,14 @@ public class TimeUtil {
|
||||
|
||||
|
||||
/**
|
||||
* @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>
|
||||
* Parse a duration string into a time in the past of future, relative to now.
|
||||
* Source: <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>
|
||||
* @param time the duration to parse.
|
||||
* @param future thur to return the time in the future, false for the time in the past.
|
||||
* @return the computed timestamp in millisecond.
|
||||
* @throws IllegalArgumentException if the format is not valid.
|
||||
*/
|
||||
public static long parseDuration(String time, boolean future) throws Exception {
|
||||
public static long parseDuration(String time, boolean future) {
|
||||
@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]*)?"
|
||||
@ -297,12 +420,14 @@ public class TimeUtil {
|
||||
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() == 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));
|
||||
@ -314,7 +439,8 @@ public class TimeUtil {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) throw new Exception("Format de durée invalide");
|
||||
if (!found)
|
||||
throw new IllegalArgumentException("Invalid duration format");
|
||||
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));
|
||||
@ -325,15 +451,8 @@ public class TimeUtil {
|
||||
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();
|
||||
return c.after(max) ? max.getTimeInMillis() : c.getTimeInMillis();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static final List<String> DURATION_SUFFIXES = List.of("y", "mo", "w", "d", "h", "m", "s");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user