diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/commands/SuggestionsSupplier.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/commands/SuggestionsSupplier.java index 8feaa47..e6631c5 100644 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/commands/SuggestionsSupplier.java +++ b/pandalib-core/src/main/java/fr/pandacube/lib/core/commands/SuggestionsSupplier.java @@ -190,12 +190,12 @@ public interface SuggestionsSupplier { static SuggestionsSupplier suggestDuration() { - final List emptyTokenSuggestions = TimeUtil.DURATION_SUFFIXES.stream().map(p -> "1" + p).collect(Collectors.toList()); + final List emptyTokenSuggestions = DURATION_SUFFIXES.stream().map(p -> "1" + p).collect(Collectors.toList()); return (s, ti, token, args) -> { if (token.isEmpty()) { return emptyTokenSuggestions; } - List remainingSuffixes = new ArrayList<>(TimeUtil.DURATION_SUFFIXES); + List remainingSuffixes = new ArrayList<>(DURATION_SUFFIXES); char[] tokenChars = token.toCharArray(); String accSuffix = ""; for (char c : tokenChars) { @@ -215,6 +215,11 @@ public interface SuggestionsSupplier { }; } + /** + * List of all possible duration unit symbols for suggestions. + */ + public static final List DURATION_SUFFIXES = List.of("y", "mo", "w", "d", "h", "m", "s"); + private static void scanAndRemovePastSuffixes(List suffixes, String foundSuffix) { for (int i = 0; i < suffixes.size(); i++) { diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java index ad6e2de..f1cf405 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java @@ -88,9 +88,9 @@ public class AABBBlock implements Iterable { } 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); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GameWorldUtils.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GameWorldUtils.java index 150eb3e..3e4afa9 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GameWorldUtils.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GameWorldUtils.java @@ -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); diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/LocationUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/LocationUtil.java index b6b8b51..01e5894 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/LocationUtil.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/LocationUtil.java @@ -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); diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/AmountPerTimeLimiter.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/AmountPerTimeLimiter.java index 633d491..cf12f41 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/AmountPerTimeLimiter.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/AmountPerTimeLimiter.java @@ -4,30 +4,44 @@ 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: + *
+ *     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);
+ *         }
+ *     }
+ * 
+ */ public class AmountPerTimeLimiter { private final double maxAmount; private final long duration; private List 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; } - - - - - - private static class Entry { - private final long time; - private double amount; - public Entry(long t, double a) { - time = t; amount = a; - } - } - - + + + /** + * 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() @@ -38,20 +52,45 @@ public class AmountPerTimeLimiter { .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 - 1000) + if (!entries.isEmpty() && entries.get(entries.size()-1).time == currT) entries.get(entries.size()-1).amount += amount; else - entries.add(new Entry(System.currentTimeMillis(), amount)); + 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()); } + + + + + + private static class Entry { + private final long time; + private double amount; + public Entry(long t, double a) { + time = t; amount = a; + } + } } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/BiMap.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/BiMap.java index ef7990d..d4fa491 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/BiMap.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/BiMap.java @@ -9,116 +9,277 @@ 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 the type of the "key" + * @param the type of the "value" + */ public class BiMap implements Iterable> { - protected final Map map; - protected final Map inversedMap; - + /** + * The backend forward map + */ + protected final Map forwardMap; + + /** + * The backend bawkward map + */ + protected final Map backwardMap; + private BiMap 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> mapSupplier) { - map = (Map) mapSupplier.get(); - inversedMap = (Map) mapSupplier.get(); + forwardMap = (Map) mapSupplier.get(); + backwardMap = (Map) 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 source) { this(); putAll(source); } - + /* * Only used for #reversedView() */ private BiMap(BiMap 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) { - if (containsKey(k)) - remove(k); - if (containsValue(v)) - removeValue(v); - map.put(k, v); - inversedMap.put(v, k); - } - - public synchronized void putAll(Map source) { - for (Map.Entry e : source.entrySet()) { - put(e.getKey(), e.getValue()); - } + /** + * 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); + 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 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 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); - return 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); + } } - - public synchronized K removeValue(V v) { - K k = inversedMap.remove(v); - map.remove(k); - return 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; + } + } + + /** + * 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> 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> 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 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 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 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 reversedView() { - if (reversedView == null) - reversedView = new BiMap<>(this); - return reversedView; + synchronized (lock) { + if (reversedView == null) + reversedView = new BiMap<>(this); + return reversedView; + } } - - public synchronized void forEach(BiConsumer c) { - for(Entry 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 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(); + } } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/CronExpression.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/CronExpression.java deleted file mode 100644 index 33a2fbe..0000000 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/CronExpression.java +++ /dev/null @@ -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. - *

- * - * 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" - *

- * A cron expressions consists of 5 or 6 mandatory fields (seconds may be omitted) separated by space.
- * These are: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
CRON fields
FieldAllowable valuesSpecial Characters
Seconds (may be omitted)0-59, - * /
Minutes0-59, - * /
Hours0-23, - * /
Day of month1-31, - * ? / L W
Month1-12 or JAN-DEC (note: english abbreviations), - * /
Day of week1-7 or MON-SUN (note: english abbreviations), - * ? / L #
- * - *

- * '*' Can be used in all fields and means 'for all values'. E.g. "*" in minutes, means 'for all minutes' - *

- * '?' 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. - *

- * '-' Used to specify a time interval. E.g. "10-12" in Hours field means 'for hours 10, 11 and 12' - *

- * ',' Used to specify multiple values for a field. E.g. "MON,WED,FRI" in Day-of-week field means "for - * monday, wednesday and friday" - *

- * '/' 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 - *

- * '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.) - *

- * '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. - *

- * '#' 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. - *

- * Case-sensitive No fields are case-sensitive - *

- * Dependencies between fields 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 names; - - CronFieldType(int from, int to, List 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. - *

- * 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 { - 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 - (?:(?\\*)|(?\\?)|(?L)) # global flag (L, ?, *) - | (?[0-9]{1,2}|[a-z]{3,3}) # or start number or symbol - (?: # start of group 2 - (?L|W) # modifier (L,W) - | -(?[0-9]{1,2}|[a-z]{3,3}) # or end nummer or symbol (in range) - )? # end of group 2 - ) # end of group 1 - (?:(?/|\\#)(?[0-9]{1,7}))? # increment and increment modifier (/ or \\#) - """, - Pattern.CASE_INSENSITIVE | Pattern.COMMENTS); - - final CronFieldType fieldType; - final List 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); - } - } -} \ No newline at end of file diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/DistanceUtil.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/DistanceUtil.java index 92028ec..b28a2d9 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/DistanceUtil.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/DistanceUtil.java @@ -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. + *

+ * 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-9 = 0.000000001m. + */ NM(0.000000001, "nm"), + + /** + * Micrometer unit. One millionth of a meter. 10-6 = 0.000001m. + */ UM(0.000001, "µm"), + + /** + * Millimeter unit. One thousandth of a meter. 10-3 = 0.001m. + */ MM(0.001, "mm"), + + /** + * Centimeter unit. One hundredth of a meter. 10-2 = 0.01m + */ CM(0.01, "cm"), + + /** + * Meter unit. One meter. 100 = 1m. + */ M(1, "m"), + + /** + * Kilometer unit. One thousand meter. 103 = 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; diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/EnumUtil.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/EnumUtil.java index c750d55..1f34a58 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/EnumUtil.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/EnumUtil.java @@ -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 the type of the enum. */ public static > String enumList(Class 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 - * ", " + * List all enum constants which are in the specified enum class. + * It is equivalent to call {@link #enumList(Class, String)} with the second parameter ", ". * * @param enumType the enum class. * @return a string representation of the enum class. + * @param the type of the enum. */ public static > String enumList(Class 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 the type of the enum. */ public static > T searchEnum(Class 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 the type of the enum. */ public static > T randomValue(Class enumType) { - T[] elements = enumType.getEnumConstants(); - - return elements[RandomUtil.rand.nextInt(elements.length)]; + return RandomUtil.arrayElement(enumType.getEnumConstants()); } } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/FileUtils.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/FileUtils.java index 4a4c03b..1ca5a9d 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/FileUtils.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/FileUtils.java @@ -5,19 +5,38 @@ 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()) delete(child); 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); } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/IteratorIterator.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/IteratorIterator.java index b00f63a..ba7be02 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/IteratorIterator.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/IteratorIterator.java @@ -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 the type of the values in the iterators + */ public class IteratorIterator implements Iterator { + /** + * 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 the type of the values in the iterables. + */ public static IteratorIterator ofCollectionOfIterable(Collection> 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 the type of the values in the iterators. + */ public static IteratorIterator ofCollectionOfIterator(Collection> 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 the type of the values in the iterables. + */ @SafeVarargs public static IteratorIterator ofArrayOfIterable(Iterable... 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 the type of the values in the iterators. + */ @SafeVarargs public static IteratorIterator ofArrayOfIterator(Iterator... arr) { return new IteratorIterator<>(Arrays.asList(arr).iterator()); } - + + + + + private final Iterator> iterators; - - private Iterator currentIterator = null; + + private Iterator currentValueIterator = null; + private Iterator nextValueIterator = null; private IteratorIterator(Iterator> 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(); } } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/JArithmeticInterpreter.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/JArithmeticInterpreter.java deleted file mode 100644 index 30fed8b..0000000 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/JArithmeticInterpreter.java +++ /dev/null @@ -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 � - - ecriture formatee d'expression (� 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) == '�')) { - 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); - } // */ - -} diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/Lazy.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/Lazy.java index 432090e..1d5d24e 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/Lazy.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/Lazy.java @@ -17,7 +17,11 @@ public class Lazy implements Supplier { private T cachedValue; private final Supplier 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 s) { supplier = Objects.requireNonNull(s); } @@ -25,21 +29,28 @@ public class Lazy implements Supplier { /** * 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; diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/LazyOrException.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/LazyOrException.java index a4b964d..32ea60b 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/LazyOrException.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/LazyOrException.java @@ -13,12 +13,16 @@ import fr.pandacube.lib.util.ThrowableUtil.SupplierException; * * @param the type of the enclosed value. */ -public class LazyOrException { +public class LazyOrException implements SupplierException { private T cachedValue; private final SupplierException 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 s) { supplier = Objects.requireNonNull(s); } @@ -26,20 +30,28 @@ public class LazyOrException { /** * 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; diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/LevenshteinDistance.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/LevenshteinDistance.java index ca15ba9..09ec1e1 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/LevenshteinDistance.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/LevenshteinDistance.java @@ -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 Levenshtein distance algorithm + * 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: + *

    + *
  • The score of adding a character
  • + *
  • The score of removing a character
  • + *
  • The score of replacing any pair of character
  • + *
+ * + * 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 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 elemDistFn) { - initialList = initList == null ? "" : initList; - elementAdditionScore = addScore; - elementDeletionScore = delScore; - elementDistanceFunction = elemDistFn == null ? ((e1, e2) -> Objects.equals(e1, e2) ? 0 : 1) : elemDistFn; - prev = new int[initialList.length() + 1]; - - curr = new int[initialList.length() + 1]; + /** + * Create a new instance of {@link LevenshteinDistance} that compute the edit-distance between {@code initialString} + * and {@code finalString}. + *

+ * 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 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[this.initialString.length() + 1]; + + curr = new int[this.initialString.length() + 1]; for (int i = 0; i < curr.length; i++) - curr[i] = i * elementDeletionScore; - - add(finList); + curr[i] = i * this.elementDeletionScore; + + 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)}. + *

+ * 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 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}. + *

+ * 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)}. + *

+ * 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]; + } + } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/ListUtil.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/ListUtil.java index bc1ab3d..3298065 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/ListUtil.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/ListUtil.java @@ -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. + *

+ * 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 list, long min, long max) { for (long i = min; i <= max; i++) { list.add(i); diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/Log.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/Log.java index 6ef1119..7196c92 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/Log.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/Log.java @@ -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: + *

+ * getTheLoggerFromSomewhere().info(message);
+ * 
+ * + * Can be simplified by one line to put in the startup code of the application: + *
+ * Log.setLogger(getTheLoggerFromSomewhere());
+ * 
+ * And this code everywhere the application needs to log something: + *
+ * Log.info(message);
+ * 
+ * + * 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); diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/MappedListView.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/MappedListView.java index c256a06..cf5b22e 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/MappedListView.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/MappedListView.java @@ -7,105 +7,147 @@ 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 the source (backend) type * @param the visible (mapped) type */ public class MappedListView extends AbstractList { - + + /** + * The backend list of this {@link MappedListView}. + */ protected final List backend; private final Function getter; private final Function 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 backend, Function getter, Function setter) { this.backend = backend; this.getter = getter; 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 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 iterator() { return new Iterator<>() { @@ -127,11 +169,17 @@ public class MappedListView extends AbstractList { }; } + /** + * {@inheritDoc} + */ @Override public ListIterator listIterator() { return listIterator(0); } + /** + * {@inheritDoc} + */ @Override public ListIterator listIterator(int index) { return new ListIterator<>() { diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/MemoryUtil.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/MemoryUtil.java index f1bde7e..e958a96 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/MemoryUtil.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/MemoryUtil.java @@ -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. + *

+ * This method returns the unit symbol in French. + * @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"}. + *

+ * This method returns the unit symbol in French. + * @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. + *

+ * SI unit: 100 = 1B
+ * Traditional unit: 20 = 1B + */ + B(1, 1, null), + + /** + * Kilobyte unit. + *

+ * SI unit: 103B = 1000B = 1KB (SI unit)
+ * Traditional unit: 210 = 1024B = 1KiB + */ + KB(1024, 1000, "k"), + + /** + * Megabyte unit. + *

+ * SI unit: 106B = 1000000B = 1MB (SI unit)
+ * Traditional unit: 220 = 1048576B = 1MiB + */ + MB(1024 * 1024, 1000_000, "M"), + + /** + * Gigabyte unit. + *

+ * SI unit: 109B = 1000000000B = 1GB (SI unit)
+ * Traditional unit: 230 = 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; + } } } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftVersion.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftVersion.java index 4bab180..24526ed 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftVersion.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftVersion.java @@ -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. + *

+ * 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") + *

+ * 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; @@ -154,47 +198,146 @@ public enum MinecraftVersion { versionMergeDisplay.put(EnumSet.of(v1_19, v1_19_1), List.of("1.19.x")); } - - public final int id; - public final List versionDisplay; - MinecraftVersion(int v, String... d) { - id = v; - versionDisplay = Arrays.asList(d); + /** + * The protocol version number of this Minecraft version. + */ + public final int protocolVersionNumber; + /** + * All Minecraft version supported by this protocol version number. + */ + public final List versionsDisplay; + + 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 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 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 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. + *

+ * 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". + *

+ * 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 getVersionsDisplayList(List 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)); diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftWebUtil.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftWebUtil.java index 0d5afaf..f23e18e 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftWebUtil.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftWebUtil.java @@ -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. + *

+ * Each colored part of the text will be contained in a {@code } 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 } 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"); @@ -75,7 +85,7 @@ public class MinecraftWebUtil { } } else { - builder.append(code_prefix + c); + builder.append(chatcolorPrefix + c); } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/OfflineUUID.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/OfflineUUID.java index fce80c2..2a73d16 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/OfflineUUID.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/OfflineUUID.java @@ -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. + *

+ * You can generate the UUID programatically using {@link #getFromNickName(String)} and + * {@link #getFromNickNames(String[])}. + * + * To use this class as a program, type + *

+ *     java -cp<anyClassPathContainingThisClass> fr.pandacube.lib.util.OfflineUUID [playernames...]
+ * 
+ * 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); @@ -20,26 +44,27 @@ public class OfflineUUID { uuids[i] = getFromNickName(nicknames[i]); return uuids; } - - - - - + + + /** + * 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)); } } } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/RandomUtil.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/RandomUtil.java index 24fcb56..4d108bc 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/RandomUtil.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/RandomUtil.java @@ -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; + return rand.nextInt(minInclu, maxExclu); } + /** + * 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() * (maxExclu - minInclu) + minInclu; + return rand.nextDouble(minInclu, maxExclu); } - - public static T arrayElement(T[] arr) { - return (arr == null || arr.length == 0) ? null : arr[rand.nextInt(arr.length)]; + + /** + * 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 the type of the array elements. + * @see Random#nextInt(int) + */ + public static T arrayElement(T[] array) { + return (array == null || array.length == 0) ? null : array[rand.nextInt(array.length)]; } - - public static T listElement(List arr) { - return (arr == null || arr.isEmpty()) ? null : arr.get(rand.nextInt(arr.size())); + + /** + * 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 the type of the list elements. + * @see Random#nextInt(int) + */ + public static T listElement(List list) { + return (list == null || list.isEmpty()) ? null : list.get(rand.nextInt(list.size())); } - - public static char stringChar(String arr) { - return (arr == null || arr.isEmpty()) ? '\0' : arr.charAt(rand.nextInt(arr.length())); + + /** + * 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 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 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 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 setElement(Set 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++) { @@ -72,22 +123,49 @@ public class RandomUtil { } return n - 1; } - - - - - + + /** + * 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++) { diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/StringUtil.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/StringUtil.java index 020030d..88fe99f 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/StringUtil.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/StringUtil.java @@ -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 c_match dans s + * 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 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 : "a thing, a thing and a thing" + * (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 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); } } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/ThrowableUtil.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/ThrowableUtil.java index 5d241d2..91cc93b 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/ThrowableUtil.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/ThrowableUtil.java @@ -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)) { - t.printStackTrace(ps); - ps.flush(); - } + 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 the type of the returned object */ public static T wrapEx(SupplierException 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 the type of the returned object */ public static T wrapReflectEx(SupplierException 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 { + /** + * 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; } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/Tick.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/Tick.java index 21a54f4..4e8d145 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/Tick.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/Tick.java @@ -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 */ diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/TimeUtil.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/TimeUtil.java index 280fcd1..b7a07cb 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/TimeUtil.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/TimeUtil.java @@ -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")) + *

+ * This method renders the text in French. + * + * @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")) + *

+ * This method renders the text in French. + * + * @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,76 +140,144 @@ 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. + *

+ * This method renders the text in French. + * + * @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. + *

+ * This method renders the text in French. + * + * @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. + *

+ * This method renders the text in French. + * + * @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)); } private static LocalDateTime toLocalDateTime(long msTime) { return Instant.ofEpochMilli(msTime).atZone(TimeZone.getDefault().toZoneId()).toLocalDateTime(); } - - - - - - - - - - + + + /** + * 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"; @@ -229,7 +331,13 @@ public class TimeUtil { case NANOSECONDS -> "ns"; }; } - + + /** + * 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; @@ -237,8 +345,15 @@ public class TimeUtil { case DAYS -> 1; }; } - - 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 Essentials DateUtil#parseDuration(String, boolean) + * Parse a duration string into a time in the past of future, relative to now. + * Source: Essentials DateUtil#parseDuration(String, boolean) + * @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 DURATION_SUFFIXES = List.of("y", "mo", "w", "d", "h", "m", "s"); - - - }