| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package org.openstreetmap.josm.tools;
|
|---|
| 3 |
|
|---|
| 4 | import static java.util.function.Predicate.not;
|
|---|
| 5 | import static org.openstreetmap.josm.tools.I18n.marktr;
|
|---|
| 6 | import static org.openstreetmap.josm.tools.I18n.tr;
|
|---|
| 7 | import static org.openstreetmap.josm.tools.I18n.trn;
|
|---|
| 8 |
|
|---|
| 9 | import java.awt.Font;
|
|---|
| 10 | import java.awt.font.FontRenderContext;
|
|---|
| 11 | import java.awt.font.GlyphVector;
|
|---|
| 12 | import java.io.Closeable;
|
|---|
| 13 | import java.io.File;
|
|---|
| 14 | import java.io.FileNotFoundException;
|
|---|
| 15 | import java.io.IOException;
|
|---|
| 16 | import java.io.InputStream;
|
|---|
| 17 | import java.net.MalformedURLException;
|
|---|
| 18 | import java.net.URI;
|
|---|
| 19 | import java.net.URISyntaxException;
|
|---|
| 20 | import java.net.URL;
|
|---|
| 21 | import java.net.URLDecoder;
|
|---|
| 22 | import java.net.URLEncoder;
|
|---|
| 23 | import java.nio.charset.StandardCharsets;
|
|---|
| 24 | import java.nio.file.Files;
|
|---|
| 25 | import java.nio.file.InvalidPathException;
|
|---|
| 26 | import java.nio.file.Path;
|
|---|
| 27 | import java.nio.file.Paths;
|
|---|
| 28 | import java.nio.file.StandardCopyOption;
|
|---|
| 29 | import java.nio.file.attribute.BasicFileAttributes;
|
|---|
| 30 | import java.nio.file.attribute.FileTime;
|
|---|
| 31 | import java.security.MessageDigest;
|
|---|
| 32 | import java.security.NoSuchAlgorithmException;
|
|---|
| 33 | import java.text.Bidi;
|
|---|
| 34 | import java.text.MessageFormat;
|
|---|
| 35 | import java.text.Normalizer;
|
|---|
| 36 | import java.util.AbstractCollection;
|
|---|
| 37 | import java.util.AbstractList;
|
|---|
| 38 | import java.util.ArrayList;
|
|---|
| 39 | import java.util.Arrays;
|
|---|
| 40 | import java.util.Collection;
|
|---|
| 41 | import java.util.Collections;
|
|---|
| 42 | import java.util.Iterator;
|
|---|
| 43 | import java.util.List;
|
|---|
| 44 | import java.util.Locale;
|
|---|
| 45 | import java.util.Map;
|
|---|
| 46 | import java.util.Objects;
|
|---|
| 47 | import java.util.Optional;
|
|---|
| 48 | import java.util.concurrent.ExecutionException;
|
|---|
| 49 | import java.util.concurrent.Executor;
|
|---|
| 50 | import java.util.concurrent.ForkJoinPool;
|
|---|
| 51 | import java.util.concurrent.ForkJoinWorkerThread;
|
|---|
| 52 | import java.util.concurrent.ThreadFactory;
|
|---|
| 53 | import java.util.concurrent.TimeUnit;
|
|---|
| 54 | import java.util.concurrent.atomic.AtomicLong;
|
|---|
| 55 | import java.util.function.Consumer;
|
|---|
| 56 | import java.util.function.Function;
|
|---|
| 57 | import java.util.function.Predicate;
|
|---|
| 58 | import java.util.regex.Matcher;
|
|---|
| 59 | import java.util.regex.Pattern;
|
|---|
| 60 | import java.util.stream.Collectors;
|
|---|
| 61 | import java.util.stream.IntStream;
|
|---|
| 62 | import java.util.stream.Stream;
|
|---|
| 63 | import java.util.zip.ZipFile;
|
|---|
| 64 |
|
|---|
| 65 | import org.openstreetmap.josm.spi.preferences.Config;
|
|---|
| 66 |
|
|---|
| 67 | /**
|
|---|
| 68 | * Basic utils, that can be useful in different parts of the program.
|
|---|
| 69 | */
|
|---|
| 70 | public final class Utils {
|
|---|
| 71 |
|
|---|
| 72 | /** Pattern matching white spaces */
|
|---|
| 73 | public static final Pattern WHITE_SPACES_PATTERN = Pattern.compile("\\s+", Pattern.UNICODE_CHARACTER_CLASS);
|
|---|
| 74 |
|
|---|
| 75 | private static final long MILLIS_OF_SECOND = TimeUnit.SECONDS.toMillis(1);
|
|---|
| 76 | private static final long MILLIS_OF_MINUTE = TimeUnit.MINUTES.toMillis(1);
|
|---|
| 77 | private static final long MILLIS_OF_HOUR = TimeUnit.HOURS.toMillis(1);
|
|---|
| 78 | private static final long MILLIS_OF_DAY = TimeUnit.DAYS.toMillis(1);
|
|---|
| 79 | private static final int[][] EMPTY_INT_INT_ARRAY = new int[0][];
|
|---|
| 80 |
|
|---|
| 81 | /**
|
|---|
| 82 | * A list of all characters allowed in URLs
|
|---|
| 83 | */
|
|---|
| 84 | public static final String URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=%";
|
|---|
| 85 |
|
|---|
| 86 | private static final Pattern REMOVE_DIACRITICS = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
|
|---|
| 87 |
|
|---|
| 88 | private static final Pattern PATTERN_LENGTH = Pattern.compile("^(-?\\d+(?:\\.\\d+)?)(cm|mi|mm|m|ft|km|nmi|in|'|\")?$");
|
|---|
| 89 | private static final Pattern PATTERN_LENGTH2 = Pattern.compile("^(-?)(\\d+(?:\\.\\d+)?)(ft|')(\\d+(?:\\.\\d+)?)(in|\")?$");
|
|---|
| 90 |
|
|---|
| 91 | private static final String DEFAULT_STRIP = "\uFEFF\u200B";
|
|---|
| 92 |
|
|---|
| 93 | private static final String[] SIZE_UNITS = {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
|
|---|
| 94 |
|
|---|
| 95 | // Constants backported from Java 9, see https://bugs.openjdk.java.net/browse/JDK-4477961
|
|---|
| 96 | private static final double TO_DEGREES = 180.0 / Math.PI;
|
|---|
| 97 | private static final double TO_RADIANS = Math.PI / 180.0;
|
|---|
| 98 |
|
|---|
| 99 | private Utils() {
|
|---|
| 100 | // Hide default constructor for utils classes
|
|---|
| 101 | }
|
|---|
| 102 |
|
|---|
| 103 | /**
|
|---|
| 104 | * Returns the first element from {@code items} which is non-null, or null if all elements are null.
|
|---|
| 105 | * @param <T> type of items
|
|---|
| 106 | * @param items the items to look for
|
|---|
| 107 | * @return first non-null item if there is one
|
|---|
| 108 | */
|
|---|
| 109 | @SafeVarargs
|
|---|
| 110 | public static <T> T firstNonNull(T... items) {
|
|---|
| 111 | return Arrays.stream(items).filter(Objects::nonNull)
|
|---|
| 112 | .findFirst().orElse(null);
|
|---|
| 113 | }
|
|---|
| 114 |
|
|---|
| 115 | /**
|
|---|
| 116 | * Filter a collection by (sub)class.
|
|---|
| 117 | * This is an efficient read-only implementation.
|
|---|
| 118 | * @param <S> Super type of items
|
|---|
| 119 | * @param <T> type of items
|
|---|
| 120 | * @param collection the collection
|
|---|
| 121 | * @param clazz the (sub)class
|
|---|
| 122 | * @return a read-only filtered collection
|
|---|
| 123 | */
|
|---|
| 124 | public static <S, T extends S> SubclassFilteredCollection<S, T> filteredCollection(Collection<S> collection, final Class<T> clazz) {
|
|---|
| 125 | CheckParameterUtil.ensureParameterNotNull(clazz, "clazz");
|
|---|
| 126 | return new SubclassFilteredCollection<>(collection, clazz::isInstance);
|
|---|
| 127 | }
|
|---|
| 128 |
|
|---|
| 129 | /**
|
|---|
| 130 | * Find the index of the first item that matches the predicate.
|
|---|
| 131 | * @param <T> The iterable type
|
|---|
| 132 | * @param collection The iterable to iterate over.
|
|---|
| 133 | * @param predicate The predicate to search for.
|
|---|
| 134 | * @return The index of the first item or -1 if none was found.
|
|---|
| 135 | */
|
|---|
| 136 | public static <T> int indexOf(Iterable<? extends T> collection, Predicate<? super T> predicate) {
|
|---|
| 137 | int i = 0;
|
|---|
| 138 | for (T item : collection) {
|
|---|
| 139 | if (predicate.test(item))
|
|---|
| 140 | return i;
|
|---|
| 141 | i++;
|
|---|
| 142 | }
|
|---|
| 143 | return -1;
|
|---|
| 144 | }
|
|---|
| 145 |
|
|---|
| 146 | /**
|
|---|
| 147 | * Ensures a logical condition is met. Otherwise throws an assertion error.
|
|---|
| 148 | * @param condition the condition to be met
|
|---|
| 149 | * @param message Formatted error message to raise if condition is not met
|
|---|
| 150 | * @param data Message parameters, optional
|
|---|
| 151 | * @throws AssertionError if the condition is not met
|
|---|
| 152 | */
|
|---|
| 153 | public static void ensure(boolean condition, String message, Object... data) {
|
|---|
| 154 | if (!condition)
|
|---|
| 155 | throw new AssertionError(
|
|---|
| 156 | MessageFormat.format(message, data)
|
|---|
| 157 | );
|
|---|
| 158 | }
|
|---|
| 159 |
|
|---|
| 160 | /**
|
|---|
| 161 | * Returns the modulo in the range [0, n) for the given dividend and divisor.
|
|---|
| 162 | * @param a the dividend
|
|---|
| 163 | * @param n the divisor
|
|---|
| 164 | * @return the modulo, which is the remainder of the Euclidean division of a by n, in the range [0, n)
|
|---|
| 165 | * @throws IllegalArgumentException if n is less than or equal to 0
|
|---|
| 166 | */
|
|---|
| 167 | public static int mod(int a, int n) {
|
|---|
| 168 | if (n <= 0)
|
|---|
| 169 | throw new IllegalArgumentException("n must be <= 0 but is " + n);
|
|---|
| 170 | int res = a % n;
|
|---|
| 171 | if (res < 0) {
|
|---|
| 172 | res += n;
|
|---|
| 173 | }
|
|---|
| 174 | return res;
|
|---|
| 175 | }
|
|---|
| 176 |
|
|---|
| 177 | /**
|
|---|
| 178 | * Converts the given iterable collection as an unordered HTML list.
|
|---|
| 179 | * @param values The iterable collection
|
|---|
| 180 | * @return An unordered HTML list
|
|---|
| 181 | */
|
|---|
| 182 | public static String joinAsHtmlUnorderedList(Iterable<?> values) {
|
|---|
| 183 | return StreamUtils.toStream(values).map(Object::toString).collect(StreamUtils.toHtmlList());
|
|---|
| 184 | }
|
|---|
| 185 |
|
|---|
| 186 | /**
|
|---|
| 187 | * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
|
|---|
| 188 | * @param <T> type of items
|
|---|
| 189 | * @param array The array to copy
|
|---|
| 190 | * @return A copy of the original array, or {@code null} if {@code array} is null
|
|---|
| 191 | * @since 6221
|
|---|
| 192 | */
|
|---|
| 193 | public static <T> T[] copyArray(T[] array) {
|
|---|
| 194 | if (array != null) {
|
|---|
| 195 | return Arrays.copyOf(array, array.length);
|
|---|
| 196 | }
|
|---|
| 197 | return array;
|
|---|
| 198 | }
|
|---|
| 199 |
|
|---|
| 200 | /**
|
|---|
| 201 | * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
|
|---|
| 202 | * @param array The array to copy
|
|---|
| 203 | * @return A copy of the original array, or {@code null} if {@code array} is null
|
|---|
| 204 | * @since 6222
|
|---|
| 205 | */
|
|---|
| 206 | public static char[] copyArray(char... array) {
|
|---|
| 207 | if (array != null) {
|
|---|
| 208 | return Arrays.copyOf(array, array.length);
|
|---|
| 209 | }
|
|---|
| 210 | return array;
|
|---|
| 211 | }
|
|---|
| 212 |
|
|---|
| 213 | /**
|
|---|
| 214 | * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
|
|---|
| 215 | * @param array The array to copy
|
|---|
| 216 | * @return A copy of the original array, or {@code null} if {@code array} is null
|
|---|
| 217 | * @since 7436
|
|---|
| 218 | */
|
|---|
| 219 | public static int[] copyArray(int... array) {
|
|---|
| 220 | if (array != null) {
|
|---|
| 221 | return Arrays.copyOf(array, array.length);
|
|---|
| 222 | }
|
|---|
| 223 | return array;
|
|---|
| 224 | }
|
|---|
| 225 |
|
|---|
| 226 | /**
|
|---|
| 227 | * Copies the given array. Unlike {@link Arrays#copyOf}, this method is null-safe.
|
|---|
| 228 | * @param array The array to copy
|
|---|
| 229 | * @return A copy of the original array, or {@code null} if {@code array} is null
|
|---|
| 230 | * @since 11879
|
|---|
| 231 | */
|
|---|
| 232 | public static byte[] copyArray(byte... array) {
|
|---|
| 233 | if (array != null) {
|
|---|
| 234 | return Arrays.copyOf(array, array.length);
|
|---|
| 235 | }
|
|---|
| 236 | return array;
|
|---|
| 237 | }
|
|---|
| 238 |
|
|---|
| 239 | /**
|
|---|
| 240 | * Simple file copy function that will overwrite the target file.
|
|---|
| 241 | * @param in The source file
|
|---|
| 242 | * @param out The destination file
|
|---|
| 243 | * @return the path to the target file
|
|---|
| 244 | * @throws IOException if any I/O error occurs
|
|---|
| 245 | * @throws IllegalArgumentException if {@code in} or {@code out} is {@code null}
|
|---|
| 246 | * @throws InvalidPathException if a Path object cannot be constructed from the abstract path
|
|---|
| 247 | * @since 7003
|
|---|
| 248 | */
|
|---|
| 249 | public static Path copyFile(File in, File out) throws IOException {
|
|---|
| 250 | CheckParameterUtil.ensureParameterNotNull(in, "in");
|
|---|
| 251 | CheckParameterUtil.ensureParameterNotNull(out, "out");
|
|---|
| 252 | return Files.copy(in.toPath(), out.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
|---|
| 253 | }
|
|---|
| 254 |
|
|---|
| 255 | /**
|
|---|
| 256 | * Recursive directory copy function
|
|---|
| 257 | * @param in The source directory
|
|---|
| 258 | * @param out The destination directory
|
|---|
| 259 | * @throws IOException if any I/O error occurs
|
|---|
| 260 | * @throws IllegalArgumentException if {@code in} or {@code out} is {@code null}
|
|---|
| 261 | * @since 7835
|
|---|
| 262 | */
|
|---|
| 263 | public static void copyDirectory(File in, File out) throws IOException {
|
|---|
| 264 | CheckParameterUtil.ensureParameterNotNull(in, "in");
|
|---|
| 265 | CheckParameterUtil.ensureParameterNotNull(out, "out");
|
|---|
| 266 | if (!out.exists() && !out.mkdirs()) {
|
|---|
| 267 | Logging.warn("Unable to create directory "+out.getPath());
|
|---|
| 268 | }
|
|---|
| 269 | File[] files = in.listFiles();
|
|---|
| 270 | if (files != null) {
|
|---|
| 271 | for (File f : files) {
|
|---|
| 272 | File target = new File(out, f.getName());
|
|---|
| 273 | if (f.isDirectory()) {
|
|---|
| 274 | copyDirectory(f, target);
|
|---|
| 275 | } else {
|
|---|
| 276 | copyFile(f, target);
|
|---|
| 277 | }
|
|---|
| 278 | }
|
|---|
| 279 | }
|
|---|
| 280 | }
|
|---|
| 281 |
|
|---|
| 282 | /**
|
|---|
| 283 | * Deletes a directory recursively.
|
|---|
| 284 | * @param path The directory to delete
|
|---|
| 285 | * @return <code>true</code> if and only if the file or directory is
|
|---|
| 286 | * successfully deleted; <code>false</code> otherwise
|
|---|
| 287 | */
|
|---|
| 288 | public static boolean deleteDirectory(File path) {
|
|---|
| 289 | if (path.exists()) {
|
|---|
| 290 | File[] files = path.listFiles();
|
|---|
| 291 | if (files != null) {
|
|---|
| 292 | for (File file : files) {
|
|---|
| 293 | if (file.isDirectory()) {
|
|---|
| 294 | deleteDirectory(file);
|
|---|
| 295 | } else {
|
|---|
| 296 | deleteFile(file);
|
|---|
| 297 | }
|
|---|
| 298 | }
|
|---|
| 299 | }
|
|---|
| 300 | }
|
|---|
| 301 | return path.delete();
|
|---|
| 302 | }
|
|---|
| 303 |
|
|---|
| 304 | /**
|
|---|
| 305 | * Deletes a file and log a default warning if the file exists but the deletion fails.
|
|---|
| 306 | * @param file file to delete
|
|---|
| 307 | * @return {@code true} if and only if the file does not exist or is successfully deleted; {@code false} otherwise
|
|---|
| 308 | * @since 10569
|
|---|
| 309 | */
|
|---|
| 310 | public static boolean deleteFileIfExists(File file) {
|
|---|
| 311 | return !file.exists() || deleteFile(file);
|
|---|
| 312 | }
|
|---|
| 313 |
|
|---|
| 314 | /**
|
|---|
| 315 | * Deletes a file and log a default warning if the deletion fails.
|
|---|
| 316 | * @param file file to delete
|
|---|
| 317 | * @return {@code true} if and only if the file is successfully deleted; {@code false} otherwise
|
|---|
| 318 | * @since 9296
|
|---|
| 319 | */
|
|---|
| 320 | public static boolean deleteFile(File file) {
|
|---|
| 321 | return deleteFile(file, marktr("Unable to delete file {0}"));
|
|---|
| 322 | }
|
|---|
| 323 |
|
|---|
| 324 | /**
|
|---|
| 325 | * Deletes a file and log a configurable warning if the deletion fails.
|
|---|
| 326 | * @param file file to delete
|
|---|
| 327 | * @param warnMsg warning message. It will be translated with {@code tr()}
|
|---|
| 328 | * and must contain a single parameter <code>{0}</code> for the file path
|
|---|
| 329 | * @return {@code true} if and only if the file is successfully deleted; {@code false} otherwise
|
|---|
| 330 | * @since 9296
|
|---|
| 331 | */
|
|---|
| 332 | public static boolean deleteFile(File file, String warnMsg) {
|
|---|
| 333 | boolean result = file.delete();
|
|---|
| 334 | if (!result) {
|
|---|
| 335 | Logging.warn(tr(warnMsg, file.getPath()));
|
|---|
| 336 | }
|
|---|
| 337 | return result;
|
|---|
| 338 | }
|
|---|
| 339 |
|
|---|
| 340 | /**
|
|---|
| 341 | * Creates a directory and log a default warning if the creation fails.
|
|---|
| 342 | * @param dir directory to create
|
|---|
| 343 | * @return {@code true} if and only if the directory is successfully created; {@code false} otherwise
|
|---|
| 344 | * @since 9645
|
|---|
| 345 | */
|
|---|
| 346 | public static boolean mkDirs(File dir) {
|
|---|
| 347 | return mkDirs(dir, marktr("Unable to create directory {0}"));
|
|---|
| 348 | }
|
|---|
| 349 |
|
|---|
| 350 | /**
|
|---|
| 351 | * Creates a directory and log a configurable warning if the creation fails.
|
|---|
| 352 | * @param dir directory to create
|
|---|
| 353 | * @param warnMsg warning message. It will be translated with {@code tr()}
|
|---|
| 354 | * and must contain a single parameter <code>{0}</code> for the directory path
|
|---|
| 355 | * @return {@code true} if and only if the directory is successfully created; {@code false} otherwise
|
|---|
| 356 | * @since 9645
|
|---|
| 357 | */
|
|---|
| 358 | public static boolean mkDirs(File dir, String warnMsg) {
|
|---|
| 359 | boolean result = dir.mkdirs();
|
|---|
| 360 | if (!result) {
|
|---|
| 361 | Logging.warn(tr(warnMsg, dir.getPath()));
|
|---|
| 362 | }
|
|---|
| 363 | return result;
|
|---|
| 364 | }
|
|---|
| 365 |
|
|---|
| 366 | /**
|
|---|
| 367 | * <p>Utility method for closing a {@link java.io.Closeable} object.</p>
|
|---|
| 368 | *
|
|---|
| 369 | * @param c the closeable object. May be null.
|
|---|
| 370 | */
|
|---|
| 371 | public static void close(Closeable c) {
|
|---|
| 372 | if (c == null) return;
|
|---|
| 373 | try {
|
|---|
| 374 | c.close();
|
|---|
| 375 | } catch (IOException e) {
|
|---|
| 376 | Logging.warn(e);
|
|---|
| 377 | }
|
|---|
| 378 | }
|
|---|
| 379 |
|
|---|
| 380 | /**
|
|---|
| 381 | * <p>Utility method for closing a {@link java.util.zip.ZipFile}.</p>
|
|---|
| 382 | *
|
|---|
| 383 | * @param zip the zip file. May be null.
|
|---|
| 384 | */
|
|---|
| 385 | public static void close(ZipFile zip) {
|
|---|
| 386 | close((Closeable) zip);
|
|---|
| 387 | }
|
|---|
| 388 |
|
|---|
| 389 | /**
|
|---|
| 390 | * Converts the given file to its URL.
|
|---|
| 391 | * @param f The file to get URL from
|
|---|
| 392 | * @return The URL of the given file, or {@code null} if not possible.
|
|---|
| 393 | * @since 6615
|
|---|
| 394 | */
|
|---|
| 395 | public static URL fileToURL(File f) {
|
|---|
| 396 | if (f != null) {
|
|---|
| 397 | try {
|
|---|
| 398 | return f.toURI().toURL();
|
|---|
| 399 | } catch (MalformedURLException ex) {
|
|---|
| 400 | Logging.error("Unable to convert filename " + f.getAbsolutePath() + " to URL");
|
|---|
| 401 | }
|
|---|
| 402 | }
|
|---|
| 403 | return null;
|
|---|
| 404 | }
|
|---|
| 405 |
|
|---|
| 406 | /**
|
|---|
| 407 | * Converts the given URL to its URI.
|
|---|
| 408 | * @param url the URL to get URI from
|
|---|
| 409 | * @return the URI of given URL
|
|---|
| 410 | * @throws URISyntaxException if the URL cannot be converted to an URI
|
|---|
| 411 | * @throws MalformedURLException if no protocol is specified, or an unknown protocol is found, or {@code spec} is {@code null}.
|
|---|
| 412 | * @since 15543
|
|---|
| 413 | */
|
|---|
| 414 | public static URI urlToURI(String url) throws URISyntaxException, MalformedURLException {
|
|---|
| 415 | return urlToURI(new URL(url));
|
|---|
| 416 | }
|
|---|
| 417 |
|
|---|
| 418 | /**
|
|---|
| 419 | * Converts the given URL to its URI.
|
|---|
| 420 | * @param url the URL to get URI from
|
|---|
| 421 | * @return the URI of given URL
|
|---|
| 422 | * @throws URISyntaxException if the URL cannot be converted to an URI
|
|---|
| 423 | * @since 15543
|
|---|
| 424 | */
|
|---|
| 425 | public static URI urlToURI(URL url) throws URISyntaxException {
|
|---|
| 426 | try {
|
|---|
| 427 | return url.toURI();
|
|---|
| 428 | } catch (URISyntaxException e) {
|
|---|
| 429 | Logging.trace(e);
|
|---|
| 430 | return new URI(
|
|---|
| 431 | url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
|
|---|
| 432 | }
|
|---|
| 433 | }
|
|---|
| 434 |
|
|---|
| 435 | private static final double EPSILON = 1e-11;
|
|---|
| 436 |
|
|---|
| 437 | /**
|
|---|
| 438 | * Determines if the two given double values are equal (their delta being smaller than a fixed epsilon)
|
|---|
| 439 | * @param a The first double value to compare
|
|---|
| 440 | * @param b The second double value to compare
|
|---|
| 441 | * @return {@code true} if {@code abs(a - b) <= 1e-11}, {@code false} otherwise
|
|---|
| 442 | */
|
|---|
| 443 | public static boolean equalsEpsilon(double a, double b) {
|
|---|
| 444 | return Math.abs(a - b) <= EPSILON;
|
|---|
| 445 | }
|
|---|
| 446 |
|
|---|
| 447 | /**
|
|---|
| 448 | * Calculate MD5 hash of a string and output in hexadecimal format.
|
|---|
| 449 | * @param data arbitrary String
|
|---|
| 450 | * @return MD5 hash of data, string of length 32 with characters in range [0-9a-f]
|
|---|
| 451 | */
|
|---|
| 452 | public static String md5Hex(String data) {
|
|---|
| 453 | MessageDigest md;
|
|---|
| 454 | try {
|
|---|
| 455 | md = MessageDigest.getInstance("MD5");
|
|---|
| 456 | } catch (NoSuchAlgorithmException e) {
|
|---|
| 457 | throw new JosmRuntimeException(e);
|
|---|
| 458 | }
|
|---|
| 459 | byte[] byteData = data.getBytes(StandardCharsets.UTF_8);
|
|---|
| 460 | byte[] byteDigest = md.digest(byteData);
|
|---|
| 461 | return toHexString(byteDigest);
|
|---|
| 462 | }
|
|---|
| 463 |
|
|---|
| 464 | private static final char[] HEX_ARRAY = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
|---|
| 465 |
|
|---|
| 466 | /**
|
|---|
| 467 | * Converts a byte array to a string of hexadecimal characters.
|
|---|
| 468 | * Preserves leading zeros, so the size of the output string is always twice
|
|---|
| 469 | * the number of input bytes.
|
|---|
| 470 | * @param bytes the byte array
|
|---|
| 471 | * @return hexadecimal representation
|
|---|
| 472 | */
|
|---|
| 473 | public static String toHexString(byte[] bytes) {
|
|---|
| 474 |
|
|---|
| 475 | if (bytes == null) {
|
|---|
| 476 | return "";
|
|---|
| 477 | }
|
|---|
| 478 |
|
|---|
| 479 | final int len = bytes.length;
|
|---|
| 480 | if (len == 0) {
|
|---|
| 481 | return "";
|
|---|
| 482 | }
|
|---|
| 483 |
|
|---|
| 484 | char[] hexChars = new char[len * 2];
|
|---|
| 485 | int j = 0;
|
|---|
| 486 | for (final int v : bytes) {
|
|---|
| 487 | hexChars[j++] = HEX_ARRAY[(v & 0xf0) >> 4];
|
|---|
| 488 | hexChars[j++] = HEX_ARRAY[v & 0xf];
|
|---|
| 489 | }
|
|---|
| 490 | return new String(hexChars);
|
|---|
| 491 | }
|
|---|
| 492 |
|
|---|
| 493 | /**
|
|---|
| 494 | * Topological sort.
|
|---|
| 495 | * @param <T> type of items
|
|---|
| 496 | *
|
|---|
| 497 | * @param dependencies contains mappings (key → value). In the final list of sorted objects, the key will come
|
|---|
| 498 | * after the value. (In other words, the key depends on the value(s).)
|
|---|
| 499 | * There must not be cyclic dependencies.
|
|---|
| 500 | * @return the list of sorted objects
|
|---|
| 501 | */
|
|---|
| 502 | public static <T> List<T> topologicalSort(final MultiMap<T, T> dependencies) {
|
|---|
| 503 | MultiMap<T, T> deps = new MultiMap<>();
|
|---|
| 504 | for (T key : dependencies.keySet()) {
|
|---|
| 505 | deps.putVoid(key);
|
|---|
| 506 | for (T val : dependencies.get(key)) {
|
|---|
| 507 | deps.putVoid(val);
|
|---|
| 508 | deps.put(key, val);
|
|---|
| 509 | }
|
|---|
| 510 | }
|
|---|
| 511 |
|
|---|
| 512 | int size = deps.size();
|
|---|
| 513 | List<T> sorted = new ArrayList<>();
|
|---|
| 514 | for (int i = 0; i < size; ++i) {
|
|---|
| 515 | T parentless = deps.keySet().stream()
|
|---|
| 516 | .filter(key -> deps.get(key).isEmpty())
|
|---|
| 517 | .findFirst().orElse(null);
|
|---|
| 518 | if (parentless == null) throw new JosmRuntimeException("parentless");
|
|---|
| 519 | sorted.add(parentless);
|
|---|
| 520 | deps.remove(parentless);
|
|---|
| 521 | for (T key : deps.keySet()) {
|
|---|
| 522 | deps.remove(key, parentless);
|
|---|
| 523 | }
|
|---|
| 524 | }
|
|---|
| 525 | if (sorted.size() != size) throw new JosmRuntimeException("Wrong size");
|
|---|
| 526 | return sorted;
|
|---|
| 527 | }
|
|---|
| 528 |
|
|---|
| 529 | /**
|
|---|
| 530 | * Replaces some HTML reserved characters (<, > and &) by their equivalent entity (&lt;, &gt; and &amp;);
|
|---|
| 531 | * @param s The unescaped string
|
|---|
| 532 | * @return The escaped string
|
|---|
| 533 | */
|
|---|
| 534 | public static String escapeReservedCharactersHTML(String s) {
|
|---|
| 535 | return s == null ? "" : s.replace("&", "&").replace("<", "<").replace(">", ">");
|
|---|
| 536 | }
|
|---|
| 537 |
|
|---|
| 538 | /**
|
|---|
| 539 | * Transforms the collection {@code c} into an unmodifiable collection and
|
|---|
| 540 | * applies the {@link Function} {@code f} on each element upon access.
|
|---|
| 541 | * @param <A> class of input collection
|
|---|
| 542 | * @param <B> class of transformed collection
|
|---|
| 543 | * @param c a collection
|
|---|
| 544 | * @param f a function that transforms objects of {@code A} to objects of {@code B}
|
|---|
| 545 | * @return the transformed unmodifiable collection
|
|---|
| 546 | */
|
|---|
| 547 | public static <A, B> Collection<B> transform(final Collection<? extends A> c, final Function<A, B> f) {
|
|---|
| 548 | return new AbstractCollection<>() {
|
|---|
| 549 |
|
|---|
| 550 | @Override
|
|---|
| 551 | public int size() {
|
|---|
| 552 | return c.size();
|
|---|
| 553 | }
|
|---|
| 554 |
|
|---|
| 555 | @Override
|
|---|
| 556 | public Iterator<B> iterator() {
|
|---|
| 557 | return new Iterator<>() {
|
|---|
| 558 |
|
|---|
| 559 | private final Iterator<? extends A> it = c.iterator();
|
|---|
| 560 |
|
|---|
| 561 | @Override
|
|---|
| 562 | public boolean hasNext() {
|
|---|
| 563 | return it.hasNext();
|
|---|
| 564 | }
|
|---|
| 565 |
|
|---|
| 566 | @Override
|
|---|
| 567 | public B next() {
|
|---|
| 568 | return f.apply(it.next());
|
|---|
| 569 | }
|
|---|
| 570 |
|
|---|
| 571 | @Override
|
|---|
| 572 | public void remove() {
|
|---|
| 573 | throw new UnsupportedOperationException();
|
|---|
| 574 | }
|
|---|
| 575 | };
|
|---|
| 576 | }
|
|---|
| 577 | };
|
|---|
| 578 | }
|
|---|
| 579 |
|
|---|
| 580 | /**
|
|---|
| 581 | * Transforms the list {@code l} into an unmodifiable list and
|
|---|
| 582 | * applies the {@link Function} {@code f} on each element upon access.
|
|---|
| 583 | * @param <A> class of input collection
|
|---|
| 584 | * @param <B> class of transformed collection
|
|---|
| 585 | * @param l a collection
|
|---|
| 586 | * @param f a function that transforms objects of {@code A} to objects of {@code B}
|
|---|
| 587 | * @return the transformed unmodifiable list
|
|---|
| 588 | */
|
|---|
| 589 | public static <A, B> List<B> transform(final List<? extends A> l, final Function<A, B> f) {
|
|---|
| 590 | return new AbstractList<>() {
|
|---|
| 591 |
|
|---|
| 592 | @Override
|
|---|
| 593 | public int size() {
|
|---|
| 594 | return l.size();
|
|---|
| 595 | }
|
|---|
| 596 |
|
|---|
| 597 | @Override
|
|---|
| 598 | public B get(int index) {
|
|---|
| 599 | return f.apply(l.get(index));
|
|---|
| 600 | }
|
|---|
| 601 | };
|
|---|
| 602 | }
|
|---|
| 603 |
|
|---|
| 604 | /**
|
|---|
| 605 | * Returns an unmodifiable list for the given collection.
|
|---|
| 606 | * Makes use of {@link Collections#emptySet()} and {@link Collections#singleton} and {@link Arrays#asList} to save memory.
|
|---|
| 607 | * @param collection the collection for which an unmodifiable collection is to be returned
|
|---|
| 608 | * @param <T> the class of the objects in the array
|
|---|
| 609 | * @return an unmodifiable list
|
|---|
| 610 | * @see <a href="https://dzone.com/articles/preventing-your-java-collections-from-wasting-memo">
|
|---|
| 611 | * How to Prevent Your Java Collections From Wasting Memory</a>
|
|---|
| 612 | */
|
|---|
| 613 | @SuppressWarnings("unchecked")
|
|---|
| 614 | public static <T> List<T> toUnmodifiableList(Collection<T> collection) {
|
|---|
| 615 | // Note: Windows does a `null` check on startup on these lists. See #23717.
|
|---|
| 616 | // Only change this once that is fixed.
|
|---|
| 617 | // Java 9: use List.of(...)
|
|---|
| 618 | if (isEmpty(collection)) {
|
|---|
| 619 | return Collections.emptyList();
|
|---|
| 620 | } else if (collection.size() == 1) {
|
|---|
| 621 | return Collections.singletonList(collection.iterator().next());
|
|---|
| 622 | } else {
|
|---|
| 623 | return (List<T>) Arrays.asList(collection.toArray());
|
|---|
| 624 | }
|
|---|
| 625 | }
|
|---|
| 626 |
|
|---|
| 627 | /**
|
|---|
| 628 | * Returns an unmodifiable map for the given map.
|
|---|
| 629 | * Makes use of {@link Collections#emptyMap} and {@link Collections#singletonMap} and {@code Map#ofEntries} to save memory.
|
|---|
| 630 | *
|
|---|
| 631 | * @param map the map for which an unmodifiable map is to be returned
|
|---|
| 632 | * @param <K> the type of keys maintained by this map
|
|---|
| 633 | * @param <V> the type of mapped values
|
|---|
| 634 | * @return an unmodifiable map
|
|---|
| 635 | * @see <a href="https://dzone.com/articles/preventing-your-java-collections-from-wasting-memo">
|
|---|
| 636 | * How to Prevent Your Java Collections From Wasting Memory</a>
|
|---|
| 637 | */
|
|---|
| 638 | @SuppressWarnings({"unchecked", "squid:S1696"})
|
|---|
| 639 | public static <K, V> Map<K, V> toUnmodifiableMap(Map<K, V> map) {
|
|---|
| 640 | if (isEmpty(map)) {
|
|---|
| 641 | return Collections.emptyMap();
|
|---|
| 642 | } else if (map.size() == 1) {
|
|---|
| 643 | final Map.Entry<K, V> entry = map.entrySet().iterator().next();
|
|---|
| 644 | return Collections.singletonMap(entry.getKey(), entry.getValue());
|
|---|
| 645 | }
|
|---|
| 646 | // see #23748: If the map contains `null`, then Map.ofEntries will throw an NPE.
|
|---|
| 647 | // We also cannot check the map for `null`, since that may _also_ throw an NPE.
|
|---|
| 648 | try {
|
|---|
| 649 | return Map.ofEntries(map.entrySet().toArray(new Map.Entry[0]));
|
|---|
| 650 | } catch (NullPointerException e) {
|
|---|
| 651 | Logging.trace(e);
|
|---|
| 652 | }
|
|---|
| 653 | return Collections.unmodifiableMap(map);
|
|---|
| 654 | }
|
|---|
| 655 |
|
|---|
| 656 | /**
|
|---|
| 657 | * Determines if a collection is null or empty.
|
|---|
| 658 | * @param collection collection
|
|---|
| 659 | * @return {@code true} if collection is null or empty
|
|---|
| 660 | * @since 18207
|
|---|
| 661 | */
|
|---|
| 662 | public static boolean isEmpty(Collection<?> collection) {
|
|---|
| 663 | return collection == null || collection.isEmpty();
|
|---|
| 664 | }
|
|---|
| 665 |
|
|---|
| 666 | /**
|
|---|
| 667 | * Determines if a map is null or empty.
|
|---|
| 668 | * @param map map
|
|---|
| 669 | * @return {@code true} if map is null or empty
|
|---|
| 670 | * @since 18207
|
|---|
| 671 | */
|
|---|
| 672 | public static boolean isEmpty(Map<?, ?> map) {
|
|---|
| 673 | return map == null || map.isEmpty();
|
|---|
| 674 | }
|
|---|
| 675 |
|
|---|
| 676 | /**
|
|---|
| 677 | * Determines if a multimap is null or empty.
|
|---|
| 678 | * @param map map
|
|---|
| 679 | * @return {@code true} if map is null or empty
|
|---|
| 680 | * @since 18208
|
|---|
| 681 | */
|
|---|
| 682 | public static boolean isEmpty(MultiMap<?, ?> map) {
|
|---|
| 683 | return map == null || map.isEmpty();
|
|---|
| 684 | }
|
|---|
| 685 |
|
|---|
| 686 | /**
|
|---|
| 687 | * Determines if a string is null or empty.
|
|---|
| 688 | * @param string string
|
|---|
| 689 | * @return {@code true} if string is null or empty
|
|---|
| 690 | * @since 18207
|
|---|
| 691 | */
|
|---|
| 692 | public static boolean isEmpty(String string) {
|
|---|
| 693 | return string == null || string.isEmpty();
|
|---|
| 694 | }
|
|---|
| 695 |
|
|---|
| 696 | /**
|
|---|
| 697 | * Returns the first not empty string in the given candidates, otherwise the default string.
|
|---|
| 698 | * @param defaultString default string returned if all candidates would be empty if stripped
|
|---|
| 699 | * @param candidates string candidates to consider
|
|---|
| 700 | * @return the first not empty string in the given candidates, otherwise the default string
|
|---|
| 701 | * @since 15646
|
|---|
| 702 | */
|
|---|
| 703 | public static String firstNotEmptyString(String defaultString, String... candidates) {
|
|---|
| 704 | return Arrays.stream(candidates)
|
|---|
| 705 | .filter(not(Utils::isStripEmpty))
|
|---|
| 706 | .findFirst().orElse(defaultString);
|
|---|
| 707 | }
|
|---|
| 708 |
|
|---|
| 709 | /**
|
|---|
| 710 | * Determines if the given String would be empty if stripped.
|
|---|
| 711 | * This is an efficient alternative to {@code strip(s).isEmpty()} that avoids to create useless String object.
|
|---|
| 712 | * @param str The string to test
|
|---|
| 713 | * @return {@code true} if the stripped version of {@code s} would be empty.
|
|---|
| 714 | * @since 11435
|
|---|
| 715 | */
|
|---|
| 716 | public static boolean isStripEmpty(String str) {
|
|---|
| 717 | if (str != null && !str.isBlank()) {
|
|---|
| 718 | for (int i = 0; i < str.length(); i++) {
|
|---|
| 719 | if (!isStrippedChar(str.charAt(i), null)) {
|
|---|
| 720 | return false;
|
|---|
| 721 | }
|
|---|
| 722 | }
|
|---|
| 723 | }
|
|---|
| 724 | return true;
|
|---|
| 725 | }
|
|---|
| 726 |
|
|---|
| 727 | /**
|
|---|
| 728 | * An alternative to {@link String#trim()} to effectively remove all leading
|
|---|
| 729 | * and trailing white characters, including Unicode ones.
|
|---|
| 730 | * @param str The string to strip
|
|---|
| 731 | * @return <code>str</code>, without leading and trailing characters, according to
|
|---|
| 732 | * {@link Character#isWhitespace(char)} and {@link Character#isSpaceChar(char)}.
|
|---|
| 733 | * @see <a href="http://closingbraces.net/2008/11/11/javastringtrim/">Java String.trim has a strange idea of whitespace</a>
|
|---|
| 734 | * @see <a href="https://bugs.openjdk.java.net/browse/JDK-4080617">JDK bug 4080617</a>
|
|---|
| 735 | * @see <a href="https://bugs.openjdk.java.net/browse/JDK-7190385">JDK bug 7190385</a>
|
|---|
| 736 | * @since 5772
|
|---|
| 737 | */
|
|---|
| 738 | public static String strip(final String str) {
|
|---|
| 739 | return strip(str, DEFAULT_STRIP);
|
|---|
| 740 | }
|
|---|
| 741 |
|
|---|
| 742 | /**
|
|---|
| 743 | * An alternative to {@link String#trim()} to effectively remove all leading
|
|---|
| 744 | * and trailing white characters, including Unicode ones.
|
|---|
| 745 | * @param str The string to strip
|
|---|
| 746 | * @param skipChars additional characters to skip
|
|---|
| 747 | * @return <code>str</code>, without leading and trailing characters, according to
|
|---|
| 748 | * {@link Character#isWhitespace(char)}, {@link Character#isSpaceChar(char)} and skipChars.
|
|---|
| 749 | * @since 8435
|
|---|
| 750 | */
|
|---|
| 751 | public static String strip(final String str, final String skipChars) {
|
|---|
| 752 | if (isEmpty(str)) {
|
|---|
| 753 | return str;
|
|---|
| 754 | }
|
|---|
| 755 |
|
|---|
| 756 | int start = 0;
|
|---|
| 757 | int end = str.length();
|
|---|
| 758 | boolean leadingSkipChar = true;
|
|---|
| 759 | while (leadingSkipChar && start < end) {
|
|---|
| 760 | leadingSkipChar = isStrippedChar(str.charAt(start), skipChars);
|
|---|
| 761 | if (leadingSkipChar) {
|
|---|
| 762 | start++;
|
|---|
| 763 | }
|
|---|
| 764 | }
|
|---|
| 765 | boolean trailingSkipChar = true;
|
|---|
| 766 | while (trailingSkipChar && end > start) {
|
|---|
| 767 | trailingSkipChar = isStrippedChar(str.charAt(end - 1), skipChars);
|
|---|
| 768 | if (trailingSkipChar) {
|
|---|
| 769 | end--;
|
|---|
| 770 | }
|
|---|
| 771 | }
|
|---|
| 772 |
|
|---|
| 773 | return str.substring(start, end);
|
|---|
| 774 | }
|
|---|
| 775 |
|
|---|
| 776 | private static boolean isStrippedChar(char c, final String skipChars) {
|
|---|
| 777 | return Character.isWhitespace(c) || Character.isSpaceChar(c)
|
|---|
| 778 | || DEFAULT_STRIP.indexOf(c) >= 0
|
|---|
| 779 | || (skipChars != null && skipChars.indexOf(c) >= 0);
|
|---|
| 780 | }
|
|---|
| 781 |
|
|---|
| 782 | /**
|
|---|
| 783 | * Removes leading, trailing, and multiple inner whitespaces from the given string, to be used as a key or value.
|
|---|
| 784 | * @param s The string
|
|---|
| 785 | * @return The string without leading, trailing or multiple inner whitespaces
|
|---|
| 786 | * @since 13597
|
|---|
| 787 | */
|
|---|
| 788 | public static String removeWhiteSpaces(String s) {
|
|---|
| 789 | return removeWhiteSpaces(WHITE_SPACES_PATTERN, s);
|
|---|
| 790 | }
|
|---|
| 791 |
|
|---|
| 792 | /**
|
|---|
| 793 | * Removes leading, trailing, and multiple inner whitespaces from the given string, to be used as a key or value.
|
|---|
| 794 | * @param s The string
|
|---|
| 795 | * @param whitespaces The regex for whitespaces to remove outside the leading and trailing whitespaces (see {@link #strip(String)})
|
|---|
| 796 | * @return The string without leading, trailing or multiple inner whitespaces
|
|---|
| 797 | * @since 19261
|
|---|
| 798 | */
|
|---|
| 799 | public static String removeWhiteSpaces(Pattern whitespaces, String s) {
|
|---|
| 800 | if (isEmpty(s)) {
|
|---|
| 801 | return s;
|
|---|
| 802 | }
|
|---|
| 803 | return whitespaces.matcher(strip(s)).replaceAll(" ");
|
|---|
| 804 | }
|
|---|
| 805 |
|
|---|
| 806 | /**
|
|---|
| 807 | * Runs an external command and returns the standard output.
|
|---|
| 808 | * <p>
|
|---|
| 809 | * The program is expected to execute fast, as this call waits 10 seconds at most.
|
|---|
| 810 | *
|
|---|
| 811 | * @param command the command with arguments
|
|---|
| 812 | * @return the output
|
|---|
| 813 | * @throws IOException when there was an error, e.g. command does not exist
|
|---|
| 814 | * @throws ExecutionException when the return code is != 0. The output is can be retrieved in the exception message
|
|---|
| 815 | * @throws InterruptedException if the current thread is {@linkplain Thread#interrupt() interrupted} by another thread while waiting
|
|---|
| 816 | */
|
|---|
| 817 | public static String execOutput(List<String> command) throws IOException, ExecutionException, InterruptedException {
|
|---|
| 818 | return execOutput(command, 10, TimeUnit.SECONDS);
|
|---|
| 819 | }
|
|---|
| 820 |
|
|---|
| 821 | /**
|
|---|
| 822 | * Runs an external command and returns the standard output. Waits at most the specified time.
|
|---|
| 823 | *
|
|---|
| 824 | * @param command the command with arguments
|
|---|
| 825 | * @param timeout the maximum time to wait
|
|---|
| 826 | * @param unit the time unit of the {@code timeout} argument. Must not be null
|
|---|
| 827 | * @return the output
|
|---|
| 828 | * @throws IOException when there was an error, e.g. command does not exist
|
|---|
| 829 | * @throws ExecutionException when the return code is != 0. The output is can be retrieved in the exception message
|
|---|
| 830 | * @throws InterruptedException if the current thread is {@linkplain Thread#interrupt() interrupted} by another thread while waiting
|
|---|
| 831 | * @since 13467
|
|---|
| 832 | */
|
|---|
| 833 | public static String execOutput(List<String> command, long timeout, TimeUnit unit)
|
|---|
| 834 | throws IOException, ExecutionException, InterruptedException {
|
|---|
| 835 | if (Logging.isDebugEnabled()) {
|
|---|
| 836 | Logging.debug(String.join(" ", command));
|
|---|
| 837 | }
|
|---|
| 838 | Path out = Files.createTempFile("josm_exec_" + command.get(0) + "_", ".txt");
|
|---|
| 839 | try {
|
|---|
| 840 | Process p = new ProcessBuilder(command).redirectErrorStream(true).redirectOutput(out.toFile()).start();
|
|---|
| 841 | if (!p.waitFor(timeout, unit) || p.exitValue() != 0) {
|
|---|
| 842 | throw new ExecutionException(command.toString(), null);
|
|---|
| 843 | }
|
|---|
| 844 | return String.join("\n", Files.readAllLines(out)).trim();
|
|---|
| 845 | } finally {
|
|---|
| 846 | try {
|
|---|
| 847 | Files.delete(out);
|
|---|
| 848 | } catch (IOException e) {
|
|---|
| 849 | Logging.warn(e);
|
|---|
| 850 | }
|
|---|
| 851 | }
|
|---|
| 852 | }
|
|---|
| 853 |
|
|---|
| 854 | /**
|
|---|
| 855 | * Returns the JOSM temp directory.
|
|---|
| 856 | * @return The JOSM temp directory ({@code <java.io.tmpdir>/JOSM}), or {@code null} if {@code java.io.tmpdir} is not defined
|
|---|
| 857 | * @since 6245
|
|---|
| 858 | */
|
|---|
| 859 | public static File getJosmTempDir() {
|
|---|
| 860 | String tmpDir = getSystemProperty("java.io.tmpdir");
|
|---|
| 861 | if (tmpDir == null) {
|
|---|
| 862 | return null;
|
|---|
| 863 | }
|
|---|
| 864 | final File josmTmpDir = new File(tmpDir, "JOSM");
|
|---|
| 865 | if (!josmTmpDir.exists() && !josmTmpDir.mkdirs()) {
|
|---|
| 866 | Logging.warn("Unable to create temp directory " + josmTmpDir);
|
|---|
| 867 | }
|
|---|
| 868 | return josmTmpDir;
|
|---|
| 869 | }
|
|---|
| 870 |
|
|---|
| 871 | /**
|
|---|
| 872 | * Returns a simple human readable (hours, minutes, seconds) string for a given duration in milliseconds.
|
|---|
| 873 | * @param elapsedTime The duration in milliseconds
|
|---|
| 874 | * @return A human readable string for the given duration
|
|---|
| 875 | * @throws IllegalArgumentException if elapsedTime is < 0
|
|---|
| 876 | * @since 6354
|
|---|
| 877 | */
|
|---|
| 878 | public static String getDurationString(long elapsedTime) {
|
|---|
| 879 | if (elapsedTime < 0) {
|
|---|
| 880 | throw new IllegalArgumentException("elapsedTime must be >= 0");
|
|---|
| 881 | }
|
|---|
| 882 | // Is it less than 1 second ?
|
|---|
| 883 | if (elapsedTime < MILLIS_OF_SECOND) {
|
|---|
| 884 | return String.format("%d %s", elapsedTime, tr("ms"));
|
|---|
| 885 | }
|
|---|
| 886 | // Is it less than 1 minute ?
|
|---|
| 887 | if (elapsedTime < MILLIS_OF_MINUTE) {
|
|---|
| 888 | return String.format("%.1f %s", elapsedTime / (double) MILLIS_OF_SECOND, tr("s"));
|
|---|
| 889 | }
|
|---|
| 890 | // Is it less than 1 hour ?
|
|---|
| 891 | if (elapsedTime < MILLIS_OF_HOUR) {
|
|---|
| 892 | final long min = elapsedTime / MILLIS_OF_MINUTE;
|
|---|
| 893 | return String.format("%d %s %d %s", min, tr("min"), (elapsedTime - min * MILLIS_OF_MINUTE) / MILLIS_OF_SECOND, tr("s"));
|
|---|
| 894 | }
|
|---|
| 895 | // Is it less than 1 day ?
|
|---|
| 896 | if (elapsedTime < MILLIS_OF_DAY) {
|
|---|
| 897 | final long hour = elapsedTime / MILLIS_OF_HOUR;
|
|---|
| 898 | return String.format("%d %s %d %s", hour, tr("h"), (elapsedTime - hour * MILLIS_OF_HOUR) / MILLIS_OF_MINUTE, tr("min"));
|
|---|
| 899 | }
|
|---|
| 900 | long days = elapsedTime / MILLIS_OF_DAY;
|
|---|
| 901 | return String.format("%d %s %d %s", days, trn("day", "days", days), (elapsedTime - days * MILLIS_OF_DAY) / MILLIS_OF_HOUR, tr("h"));
|
|---|
| 902 | }
|
|---|
| 903 |
|
|---|
| 904 | /**
|
|---|
| 905 | * Returns a human readable representation (B, kB, MB, ...) for the given number of byes.
|
|---|
| 906 | * @param bytes the number of bytes
|
|---|
| 907 | * @param locale the locale used for formatting
|
|---|
| 908 | * @return a human readable representation
|
|---|
| 909 | * @since 9274
|
|---|
| 910 | */
|
|---|
| 911 | public static String getSizeString(long bytes, Locale locale) {
|
|---|
| 912 | if (bytes < 0) {
|
|---|
| 913 | throw new IllegalArgumentException("bytes must be >= 0");
|
|---|
| 914 | }
|
|---|
| 915 | int unitIndex = 0;
|
|---|
| 916 | double value = bytes;
|
|---|
| 917 | while (value >= 1024 && unitIndex < SIZE_UNITS.length) {
|
|---|
| 918 | value /= 1024;
|
|---|
| 919 | unitIndex++;
|
|---|
| 920 | }
|
|---|
| 921 | if (value > 100 || unitIndex == 0) {
|
|---|
| 922 | return String.format(locale, "%.0f %s", value, SIZE_UNITS[unitIndex]);
|
|---|
| 923 | } else if (value > 10) {
|
|---|
| 924 | return String.format(locale, "%.1f %s", value, SIZE_UNITS[unitIndex]);
|
|---|
| 925 | } else {
|
|---|
| 926 | return String.format(locale, "%.2f %s", value, SIZE_UNITS[unitIndex]);
|
|---|
| 927 | }
|
|---|
| 928 | }
|
|---|
| 929 |
|
|---|
| 930 | /**
|
|---|
| 931 | * Returns a human readable representation of a list of positions.
|
|---|
| 932 | * <p>
|
|---|
| 933 | * For instance, {@code [1,5,2,6,7} yields "1-2,5-7
|
|---|
| 934 | * @param positionList a list of positions
|
|---|
| 935 | * @return a human readable representation
|
|---|
| 936 | */
|
|---|
| 937 | public static String getPositionListString(List<Integer> positionList) {
|
|---|
| 938 | Collections.sort(positionList);
|
|---|
| 939 | final StringBuilder sb = new StringBuilder(32);
|
|---|
| 940 | sb.append(positionList.get(0));
|
|---|
| 941 | int cnt = 0;
|
|---|
| 942 | int last = positionList.get(0);
|
|---|
| 943 | for (int i = 1; i < positionList.size(); ++i) {
|
|---|
| 944 | int cur = positionList.get(i);
|
|---|
| 945 | if (cur == last + 1) {
|
|---|
| 946 | ++cnt;
|
|---|
| 947 | } else if (cnt == 0) {
|
|---|
| 948 | sb.append(',').append(cur);
|
|---|
| 949 | } else {
|
|---|
| 950 | sb.append('-').append(last)
|
|---|
| 951 | .append(',').append(cur);
|
|---|
| 952 | cnt = 0;
|
|---|
| 953 | }
|
|---|
| 954 | last = cur;
|
|---|
| 955 | }
|
|---|
| 956 | if (cnt >= 1) {
|
|---|
| 957 | sb.append('-').append(last);
|
|---|
| 958 | }
|
|---|
| 959 | return sb.toString();
|
|---|
| 960 | }
|
|---|
| 961 |
|
|---|
| 962 | /**
|
|---|
| 963 | * Returns a list of capture groups if {@link Matcher#matches()}, or {@code null}.
|
|---|
| 964 | * The first element (index 0) is the complete match.
|
|---|
| 965 | * Further elements correspond to the parts in parentheses of the regular expression.
|
|---|
| 966 | * @param m the matcher
|
|---|
| 967 | * @return a list of capture groups if {@link Matcher#matches()}, or {@code null}.
|
|---|
| 968 | */
|
|---|
| 969 | public static List<String> getMatches(final Matcher m) {
|
|---|
| 970 | if (m.matches()) {
|
|---|
| 971 | return IntStream.rangeClosed(0, m.groupCount())
|
|---|
| 972 | .mapToObj(m::group)
|
|---|
| 973 | .collect(Collectors.toList());
|
|---|
| 974 | } else {
|
|---|
| 975 | return null;
|
|---|
| 976 | }
|
|---|
| 977 | }
|
|---|
| 978 |
|
|---|
| 979 | /**
|
|---|
| 980 | * Cast an object safely.
|
|---|
| 981 | * @param <T> the target type
|
|---|
| 982 | * @param o the object to cast
|
|---|
| 983 | * @param klass the target class (same as T)
|
|---|
| 984 | * @return null if <code>o</code> is null or the type <code>o</code> is not
|
|---|
| 985 | * a subclass of <code>klass</code>. The casted value otherwise.
|
|---|
| 986 | */
|
|---|
| 987 | public static <T> T cast(Object o, Class<T> klass) {
|
|---|
| 988 | if (klass.isInstance(o)) {
|
|---|
| 989 | return klass.cast(o);
|
|---|
| 990 | }
|
|---|
| 991 | return null;
|
|---|
| 992 | }
|
|---|
| 993 |
|
|---|
| 994 | /**
|
|---|
| 995 | * Returns the root cause of a throwable object.
|
|---|
| 996 | * @param t The object to get root cause for
|
|---|
| 997 | * @return the root cause of {@code t}
|
|---|
| 998 | * @since 6639
|
|---|
| 999 | */
|
|---|
| 1000 | public static Throwable getRootCause(Throwable t) {
|
|---|
| 1001 | Throwable result = t;
|
|---|
| 1002 | if (result != null) {
|
|---|
| 1003 | Throwable cause = result.getCause();
|
|---|
| 1004 | while (cause != null && !cause.equals(result)) {
|
|---|
| 1005 | result = cause;
|
|---|
| 1006 | cause = result.getCause();
|
|---|
| 1007 | }
|
|---|
| 1008 | }
|
|---|
| 1009 | return result;
|
|---|
| 1010 | }
|
|---|
| 1011 |
|
|---|
| 1012 | /**
|
|---|
| 1013 | * Adds the given item at the end of a new copy of given array.
|
|---|
| 1014 | * @param <T> type of items
|
|---|
| 1015 | * @param array The source array
|
|---|
| 1016 | * @param item The item to add
|
|---|
| 1017 | * @return An extended copy of {@code array} containing {@code item} as additional last element
|
|---|
| 1018 | * @since 6717
|
|---|
| 1019 | */
|
|---|
| 1020 | public static <T> T[] addInArrayCopy(T[] array, T item) {
|
|---|
| 1021 | T[] biggerCopy = Arrays.copyOf(array, array.length + 1);
|
|---|
| 1022 | biggerCopy[array.length] = item;
|
|---|
| 1023 | return biggerCopy;
|
|---|
| 1024 | }
|
|---|
| 1025 |
|
|---|
| 1026 | /**
|
|---|
| 1027 | * If the string {@code s} is longer than {@code maxLength}, the string is cut and "..." is appended.
|
|---|
| 1028 | * @param s String to shorten
|
|---|
| 1029 | * @param maxLength maximum number of characters to keep (not including the "...")
|
|---|
| 1030 | * @return the shortened string
|
|---|
| 1031 | * @throws IllegalArgumentException if maxLength is less than the length of "..."
|
|---|
| 1032 | */
|
|---|
| 1033 | public static String shortenString(String s, int maxLength) {
|
|---|
| 1034 | final String ellipses = "...";
|
|---|
| 1035 | CheckParameterUtil.ensureThat(maxLength >= ellipses.length(), "maxLength is shorter than " + ellipses.length());
|
|---|
| 1036 | if (s != null && s.length() > maxLength) {
|
|---|
| 1037 | return s.substring(0, maxLength - ellipses.length()) + ellipses;
|
|---|
| 1038 | } else {
|
|---|
| 1039 | return s;
|
|---|
| 1040 | }
|
|---|
| 1041 | }
|
|---|
| 1042 |
|
|---|
| 1043 | /**
|
|---|
| 1044 | * If the string {@code s} is longer than {@code maxLines} lines, the string is cut and a "..." line is appended.
|
|---|
| 1045 | * @param s String to shorten
|
|---|
| 1046 | * @param maxLines maximum number of lines to keep (including including the "..." line)
|
|---|
| 1047 | * @return the shortened string
|
|---|
| 1048 | */
|
|---|
| 1049 | public static String restrictStringLines(String s, int maxLines) {
|
|---|
| 1050 | if (s == null) {
|
|---|
| 1051 | return null;
|
|---|
| 1052 | } else {
|
|---|
| 1053 | return String.join("\n", limit(Arrays.asList(s.split("\\n", -1)), maxLines, "..."));
|
|---|
| 1054 | }
|
|---|
| 1055 | }
|
|---|
| 1056 |
|
|---|
| 1057 | /**
|
|---|
| 1058 | * If the collection {@code elements} is larger than {@code maxElements} elements,
|
|---|
| 1059 | * the collection is shortened and the {@code overflowIndicator} is appended.
|
|---|
| 1060 | * @param <T> type of elements
|
|---|
| 1061 | * @param elements collection to shorten
|
|---|
| 1062 | * @param maxElements maximum number of elements to keep (including the {@code overflowIndicator})
|
|---|
| 1063 | * @param overflowIndicator the element used to indicate that the collection has been shortened
|
|---|
| 1064 | * @return the shortened collection
|
|---|
| 1065 | */
|
|---|
| 1066 | public static <T> Collection<T> limit(Collection<T> elements, int maxElements, T overflowIndicator) {
|
|---|
| 1067 | if (elements == null) {
|
|---|
| 1068 | return null;
|
|---|
| 1069 | } else {
|
|---|
| 1070 | if (elements.size() > maxElements) {
|
|---|
| 1071 | final Collection<T> r = new ArrayList<>(maxElements);
|
|---|
| 1072 | final Iterator<T> it = elements.iterator();
|
|---|
| 1073 | while (r.size() < maxElements - 1) {
|
|---|
| 1074 | r.add(it.next());
|
|---|
| 1075 | }
|
|---|
| 1076 | r.add(overflowIndicator);
|
|---|
| 1077 | return r;
|
|---|
| 1078 | } else {
|
|---|
| 1079 | return elements;
|
|---|
| 1080 | }
|
|---|
| 1081 | }
|
|---|
| 1082 | }
|
|---|
| 1083 |
|
|---|
| 1084 | /**
|
|---|
| 1085 | * Fixes URL with illegal characters in the query (and fragment) part by
|
|---|
| 1086 | * percent encoding those characters.
|
|---|
| 1087 | * <p>
|
|---|
| 1088 | * special characters like & and # are not encoded
|
|---|
| 1089 | *
|
|---|
| 1090 | * @param url the URL that should be fixed
|
|---|
| 1091 | * @return the repaired URL
|
|---|
| 1092 | */
|
|---|
| 1093 | public static String fixURLQuery(String url) {
|
|---|
| 1094 | if (url == null || url.indexOf('?') == -1)
|
|---|
| 1095 | return url;
|
|---|
| 1096 |
|
|---|
| 1097 | final String query = url.substring(url.indexOf('?') + 1);
|
|---|
| 1098 |
|
|---|
| 1099 | final StringBuilder sb = new StringBuilder(url.substring(0, url.indexOf('?') + 1));
|
|---|
| 1100 |
|
|---|
| 1101 | for (int i = 0; i < query.length(); i++) {
|
|---|
| 1102 | final String c = query.substring(i, i + 1);
|
|---|
| 1103 | if (URL_CHARS.contains(c)) {
|
|---|
| 1104 | sb.append(c);
|
|---|
| 1105 | } else {
|
|---|
| 1106 | sb.append(encodeUrl(c));
|
|---|
| 1107 | }
|
|---|
| 1108 | }
|
|---|
| 1109 | return sb.toString();
|
|---|
| 1110 | }
|
|---|
| 1111 |
|
|---|
| 1112 | /**
|
|---|
| 1113 | * Translates a string into <code>application/x-www-form-urlencoded</code>
|
|---|
| 1114 | * format. This method uses UTF-8 encoding scheme to obtain the bytes for unsafe
|
|---|
| 1115 | * characters.
|
|---|
| 1116 | *
|
|---|
| 1117 | * @param s <code>String</code> to be translated.
|
|---|
| 1118 | * @return the translated <code>String</code>.
|
|---|
| 1119 | * @see #decodeUrl(String)
|
|---|
| 1120 | * @since 8304
|
|---|
| 1121 | */
|
|---|
| 1122 | public static String encodeUrl(String s) {
|
|---|
| 1123 | return URLEncoder.encode(s, StandardCharsets.UTF_8);
|
|---|
| 1124 | }
|
|---|
| 1125 |
|
|---|
| 1126 | /**
|
|---|
| 1127 | * Decodes a <code>application/x-www-form-urlencoded</code> string.
|
|---|
| 1128 | * UTF-8 encoding is used to determine
|
|---|
| 1129 | * what characters are represented by any consecutive sequences of the
|
|---|
| 1130 | * form "<code>%<i>xy</i></code>".
|
|---|
| 1131 | *
|
|---|
| 1132 | * @param s the <code>String</code> to decode
|
|---|
| 1133 | * @return the newly decoded <code>String</code>
|
|---|
| 1134 | * @see #encodeUrl(String)
|
|---|
| 1135 | * @since 8304
|
|---|
| 1136 | */
|
|---|
| 1137 | public static String decodeUrl(String s) {
|
|---|
| 1138 | return URLDecoder.decode(s, StandardCharsets.UTF_8);
|
|---|
| 1139 | }
|
|---|
| 1140 |
|
|---|
| 1141 | /**
|
|---|
| 1142 | * Determines if the given URL denotes a file on a local filesystem.
|
|---|
| 1143 | * @param url The URL to test
|
|---|
| 1144 | * @return {@code true} if the url points to a local file
|
|---|
| 1145 | * @since 7356
|
|---|
| 1146 | */
|
|---|
| 1147 | public static boolean isLocalUrl(String url) {
|
|---|
| 1148 | return url != null && !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("resource://");
|
|---|
| 1149 | }
|
|---|
| 1150 |
|
|---|
| 1151 | /**
|
|---|
| 1152 | * Determines if the given URL is valid.
|
|---|
| 1153 | * @param url The URL to test
|
|---|
| 1154 | * @return {@code true} if the url is valid
|
|---|
| 1155 | * @since 10294
|
|---|
| 1156 | */
|
|---|
| 1157 | public static boolean isValidUrl(String url) {
|
|---|
| 1158 | if (url != null) {
|
|---|
| 1159 | try {
|
|---|
| 1160 | new URL(url);
|
|---|
| 1161 | return true;
|
|---|
| 1162 | } catch (MalformedURLException e) {
|
|---|
| 1163 | Logging.trace(e);
|
|---|
| 1164 | }
|
|---|
| 1165 | }
|
|---|
| 1166 | return false;
|
|---|
| 1167 | }
|
|---|
| 1168 |
|
|---|
| 1169 | /**
|
|---|
| 1170 | * Creates a new {@link ThreadFactory} which creates threads with names according to {@code nameFormat}.
|
|---|
| 1171 | * @param nameFormat a {@link String#format(String, Object...)} compatible name format; its first argument is a unique thread index
|
|---|
| 1172 | * @param threadPriority the priority of the created threads, see {@link Thread#setPriority(int)}
|
|---|
| 1173 | * @return a new {@link ThreadFactory}
|
|---|
| 1174 | */
|
|---|
| 1175 | @SuppressWarnings("ThreadPriorityCheck")
|
|---|
| 1176 | public static ThreadFactory newThreadFactory(final String nameFormat, final int threadPriority) {
|
|---|
| 1177 | return new ThreadFactory() {
|
|---|
| 1178 | final AtomicLong count = new AtomicLong(0);
|
|---|
| 1179 | @Override
|
|---|
| 1180 | public Thread newThread(final Runnable runnable) {
|
|---|
| 1181 | final Thread thread = new Thread(runnable, String.format(Locale.ENGLISH, nameFormat, count.getAndIncrement()));
|
|---|
| 1182 | thread.setPriority(threadPriority);
|
|---|
| 1183 | return thread;
|
|---|
| 1184 | }
|
|---|
| 1185 | };
|
|---|
| 1186 | }
|
|---|
| 1187 |
|
|---|
| 1188 | /**
|
|---|
| 1189 | * Compute <a href="https://en.wikipedia.org/wiki/Levenshtein_distance">Levenshtein distance</a>
|
|---|
| 1190 | *
|
|---|
| 1191 | * @param s First word
|
|---|
| 1192 | * @param t Second word
|
|---|
| 1193 | * @return The distance between words
|
|---|
| 1194 | * @since 14371
|
|---|
| 1195 | */
|
|---|
| 1196 | public static int getLevenshteinDistance(String s, String t) {
|
|---|
| 1197 | int[][] d; // matrix
|
|---|
| 1198 | int n; // length of s
|
|---|
| 1199 | int m; // length of t
|
|---|
| 1200 | int i; // iterates through s
|
|---|
| 1201 | int j; // iterates through t
|
|---|
| 1202 | char si; // ith character of s
|
|---|
| 1203 | char tj; // jth character of t
|
|---|
| 1204 | int cost; // cost
|
|---|
| 1205 |
|
|---|
| 1206 | // Step 1
|
|---|
| 1207 | n = s.length();
|
|---|
| 1208 | m = t.length();
|
|---|
| 1209 | if (n == 0)
|
|---|
| 1210 | return m;
|
|---|
| 1211 | if (m == 0)
|
|---|
| 1212 | return n;
|
|---|
| 1213 | d = new int[n+1][m+1];
|
|---|
| 1214 |
|
|---|
| 1215 | // Step 2
|
|---|
| 1216 | for (i = 0; i <= n; i++) {
|
|---|
| 1217 | d[i][0] = i;
|
|---|
| 1218 | }
|
|---|
| 1219 | for (j = 0; j <= m; j++) {
|
|---|
| 1220 | d[0][j] = j;
|
|---|
| 1221 | }
|
|---|
| 1222 |
|
|---|
| 1223 | // Step 3
|
|---|
| 1224 | for (i = 1; i <= n; i++) {
|
|---|
| 1225 |
|
|---|
| 1226 | si = s.charAt(i - 1);
|
|---|
| 1227 |
|
|---|
| 1228 | // Step 4
|
|---|
| 1229 | for (j = 1; j <= m; j++) {
|
|---|
| 1230 |
|
|---|
| 1231 | tj = t.charAt(j - 1);
|
|---|
| 1232 |
|
|---|
| 1233 | // Step 5
|
|---|
| 1234 | if (si == tj) {
|
|---|
| 1235 | cost = 0;
|
|---|
| 1236 | } else {
|
|---|
| 1237 | cost = 1;
|
|---|
| 1238 | }
|
|---|
| 1239 |
|
|---|
| 1240 | // Step 6
|
|---|
| 1241 | d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), d[i - 1][j - 1] + cost);
|
|---|
| 1242 | }
|
|---|
| 1243 | }
|
|---|
| 1244 |
|
|---|
| 1245 | // Step 7
|
|---|
| 1246 | return d[n][m];
|
|---|
| 1247 | }
|
|---|
| 1248 |
|
|---|
| 1249 | /**
|
|---|
| 1250 | * Check if two strings are similar, but not identical, i.e., have a Levenshtein distance of 1 or 2.
|
|---|
| 1251 | * @param string1 first string to compare
|
|---|
| 1252 | * @param string2 second string to compare
|
|---|
| 1253 | * @return true if the normalized strings are different but only a "little bit"
|
|---|
| 1254 | * @see #getLevenshteinDistance
|
|---|
| 1255 | * @since 14371
|
|---|
| 1256 | */
|
|---|
| 1257 | public static boolean isSimilar(String string1, String string2) {
|
|---|
| 1258 | // check plain strings
|
|---|
| 1259 | int distance = getLevenshteinDistance(string1, string2);
|
|---|
| 1260 |
|
|---|
| 1261 | // check if only the case differs, so we don't consider large distance as different strings
|
|---|
| 1262 | if (distance > 2 && string1.length() == string2.length()) {
|
|---|
| 1263 | return deAccent(string1).equalsIgnoreCase(deAccent(string2));
|
|---|
| 1264 | } else {
|
|---|
| 1265 | return distance > 0 && distance <= 2;
|
|---|
| 1266 | }
|
|---|
| 1267 | }
|
|---|
| 1268 |
|
|---|
| 1269 | /**
|
|---|
| 1270 | * Calculates the <a href="https://en.wikipedia.org/wiki/Standard_deviation">standard deviation</a> of population.
|
|---|
| 1271 | * @param values an array of values
|
|---|
| 1272 | * @return standard deviation of the given array, or -1.0 if the array has less than two values
|
|---|
| 1273 | * @see #getStandardDeviation(double[], double)
|
|---|
| 1274 | * @since 18553
|
|---|
| 1275 | */
|
|---|
| 1276 | public static double getStandardDeviation(double[] values) {
|
|---|
| 1277 | return getStandardDeviation(values, Double.NaN);
|
|---|
| 1278 | }
|
|---|
| 1279 |
|
|---|
| 1280 | /**
|
|---|
| 1281 | * Calculates the <a href="https://en.wikipedia.org/wiki/Standard_deviation">standard deviation</a> of population with the given
|
|---|
| 1282 | * mean value.
|
|---|
| 1283 | * @param values an array of values
|
|---|
| 1284 | * @param mean precalculated average value of the array
|
|---|
| 1285 | * @return standard deviation of the given array, or -1.0 if the array has less than two values
|
|---|
| 1286 | * @see #getStandardDeviation(double[])
|
|---|
| 1287 | * @since 18553
|
|---|
| 1288 | */
|
|---|
| 1289 | public static double getStandardDeviation(double[] values, double mean) {
|
|---|
| 1290 | if (values.length < 2) {
|
|---|
| 1291 | return -1.0;
|
|---|
| 1292 | }
|
|---|
| 1293 |
|
|---|
| 1294 | double standardDeviation = 0;
|
|---|
| 1295 |
|
|---|
| 1296 | if (Double.isNaN(mean)) {
|
|---|
| 1297 | mean = Arrays.stream(values).average().orElse(0);
|
|---|
| 1298 | }
|
|---|
| 1299 |
|
|---|
| 1300 | for (double length : values) {
|
|---|
| 1301 | standardDeviation += Math.pow(length - mean, 2);
|
|---|
| 1302 | }
|
|---|
| 1303 |
|
|---|
| 1304 | return Math.sqrt(standardDeviation / values.length);
|
|---|
| 1305 | }
|
|---|
| 1306 |
|
|---|
| 1307 | /**
|
|---|
| 1308 | * Group a list of integers, mostly useful to avoid calling many selection change events
|
|---|
| 1309 | * for a logical interval.
|
|---|
| 1310 | * <br>
|
|---|
| 1311 | * Example: {@code groupIntegers(1, 2, 3, 5, 6, 7, 8, 9)} becomes {@code [[1, 3], [5, 9]]}
|
|---|
| 1312 | * @param integers The integers to group
|
|---|
| 1313 | * @return The integers grouped into logical blocks, [lower, higher] (inclusive)
|
|---|
| 1314 | * @since 18556
|
|---|
| 1315 | */
|
|---|
| 1316 | public static int[][] groupIntegers(int... integers) {
|
|---|
| 1317 | if (integers.length == 0) {
|
|---|
| 1318 | return EMPTY_INT_INT_ARRAY;
|
|---|
| 1319 | }
|
|---|
| 1320 | List<int[]> groups = new ArrayList<>();
|
|---|
| 1321 | int[] current = {Integer.MIN_VALUE, Integer.MIN_VALUE};
|
|---|
| 1322 | groups.add(current);
|
|---|
| 1323 | for (int row : integers) {
|
|---|
| 1324 | if (current[0] == Integer.MIN_VALUE) {
|
|---|
| 1325 | current[0] = row;
|
|---|
| 1326 | current[1] = row;
|
|---|
| 1327 | continue;
|
|---|
| 1328 | }
|
|---|
| 1329 | if (current[1] == row - 1) {
|
|---|
| 1330 | current[1] = row;
|
|---|
| 1331 | } else {
|
|---|
| 1332 | current = new int[] {row, row};
|
|---|
| 1333 | groups.add(current);
|
|---|
| 1334 | }
|
|---|
| 1335 | }
|
|---|
| 1336 | return groups.toArray(EMPTY_INT_INT_ARRAY);
|
|---|
| 1337 | }
|
|---|
| 1338 |
|
|---|
| 1339 | /**
|
|---|
| 1340 | * A ForkJoinWorkerThread that will always inherit caller permissions,
|
|---|
| 1341 | * unlike JDK's InnocuousForkJoinWorkerThread, used if a security manager exists.
|
|---|
| 1342 | */
|
|---|
| 1343 | static final class JosmForkJoinWorkerThread extends ForkJoinWorkerThread {
|
|---|
| 1344 | JosmForkJoinWorkerThread(ForkJoinPool pool) {
|
|---|
| 1345 | super(pool);
|
|---|
| 1346 | }
|
|---|
| 1347 | }
|
|---|
| 1348 |
|
|---|
| 1349 | /**
|
|---|
| 1350 | * Returns a {@link ForkJoinPool} with the parallelism given by the preference key.
|
|---|
| 1351 | * @param pref The preference key to determine parallelism
|
|---|
| 1352 | * @param nameFormat see {@link #newThreadFactory(String, int)}
|
|---|
| 1353 | * @param threadPriority see {@link #newThreadFactory(String, int)}
|
|---|
| 1354 | * @return a {@link ForkJoinPool}
|
|---|
| 1355 | */
|
|---|
| 1356 | @SuppressWarnings("ThreadPriorityCheck")
|
|---|
| 1357 | public static ForkJoinPool newForkJoinPool(String pref, final String nameFormat, final int threadPriority) {
|
|---|
| 1358 | final int noThreads = Config.getPref().getInt(pref, Runtime.getRuntime().availableProcessors());
|
|---|
| 1359 | return new ForkJoinPool(noThreads, new ForkJoinPool.ForkJoinWorkerThreadFactory() {
|
|---|
| 1360 | final AtomicLong count = new AtomicLong(0);
|
|---|
| 1361 | @Override
|
|---|
| 1362 | public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
|
|---|
| 1363 | // Do not use JDK default thread factory !
|
|---|
| 1364 | // If JOSM is started with Java Web Start, a security manager is installed and the factory
|
|---|
| 1365 | // creates threads without any permission, forbidding them to load a class instantiating
|
|---|
| 1366 | // another ForkJoinPool such as MultipolygonBuilder (see bug #15722)
|
|---|
| 1367 | final ForkJoinWorkerThread thread = new JosmForkJoinWorkerThread(pool);
|
|---|
| 1368 | thread.setName(String.format(Locale.ENGLISH, nameFormat, count.getAndIncrement()));
|
|---|
| 1369 | thread.setPriority(threadPriority);
|
|---|
| 1370 | return thread;
|
|---|
| 1371 | }
|
|---|
| 1372 | }, null, true);
|
|---|
| 1373 | }
|
|---|
| 1374 |
|
|---|
| 1375 | /**
|
|---|
| 1376 | * Returns an executor which executes commands in the calling thread
|
|---|
| 1377 | * @return an executor
|
|---|
| 1378 | */
|
|---|
| 1379 | public static Executor newDirectExecutor() {
|
|---|
| 1380 | return Runnable::run;
|
|---|
| 1381 | }
|
|---|
| 1382 |
|
|---|
| 1383 | /**
|
|---|
| 1384 | * Gets the value of the specified environment variable.
|
|---|
| 1385 | * An environment variable is a system-dependent external named value.
|
|---|
| 1386 | * @param name name the name of the environment variable
|
|---|
| 1387 | * @return the string value of the variable;
|
|---|
| 1388 | * {@code null} if the variable is not defined in the system environment or if a security exception occurs.
|
|---|
| 1389 | * @see System#getenv(String)
|
|---|
| 1390 | * @since 13647
|
|---|
| 1391 | */
|
|---|
| 1392 | public static String getSystemEnv(String name) {
|
|---|
| 1393 | try {
|
|---|
| 1394 | return System.getenv(name);
|
|---|
| 1395 | } catch (SecurityException e) {
|
|---|
| 1396 | Logging.log(Logging.LEVEL_ERROR, "Unable to get system env", e);
|
|---|
| 1397 | return null;
|
|---|
| 1398 | }
|
|---|
| 1399 | }
|
|---|
| 1400 |
|
|---|
| 1401 | /**
|
|---|
| 1402 | * Gets the system property indicated by the specified key.
|
|---|
| 1403 | * @param key the name of the system property.
|
|---|
| 1404 | * @return the string value of the system property;
|
|---|
| 1405 | * {@code null} if there is no property with that key or if a security exception occurs.
|
|---|
| 1406 | * @see System#getProperty(String)
|
|---|
| 1407 | * @since 13647
|
|---|
| 1408 | */
|
|---|
| 1409 | public static String getSystemProperty(String key) {
|
|---|
| 1410 | try {
|
|---|
| 1411 | return System.getProperty(key);
|
|---|
| 1412 | } catch (SecurityException e) {
|
|---|
| 1413 | Logging.log(Logging.LEVEL_ERROR, "Unable to get system property", e);
|
|---|
| 1414 | return null;
|
|---|
| 1415 | }
|
|---|
| 1416 | }
|
|---|
| 1417 |
|
|---|
| 1418 | /**
|
|---|
| 1419 | * Updates a given system property.
|
|---|
| 1420 | * @param key The property key
|
|---|
| 1421 | * @param value The property value
|
|---|
| 1422 | * @return the previous value of the system property, or {@code null} if it did not have one.
|
|---|
| 1423 | * @since 7894
|
|---|
| 1424 | */
|
|---|
| 1425 | public static String updateSystemProperty(String key, String value) {
|
|---|
| 1426 | if (value != null) {
|
|---|
| 1427 | try {
|
|---|
| 1428 | String old = System.setProperty(key, value);
|
|---|
| 1429 | if (Logging.isDebugEnabled() && !value.equals(old)) {
|
|---|
| 1430 | if (!key.toLowerCase(Locale.ENGLISH).contains("password")) {
|
|---|
| 1431 | Logging.debug("System property '" + key + "' set to '" + value + "'. Old value was '" + old + '\'');
|
|---|
| 1432 | } else {
|
|---|
| 1433 | Logging.debug("System property '" + key + "' changed.");
|
|---|
| 1434 | }
|
|---|
| 1435 | }
|
|---|
| 1436 | return old;
|
|---|
| 1437 | } catch (SecurityException e) {
|
|---|
| 1438 | // Don't call Logging class, it may not be fully initialized yet
|
|---|
| 1439 | System.err.println("Unable to update system property: " + e.getMessage());
|
|---|
| 1440 | }
|
|---|
| 1441 | }
|
|---|
| 1442 | return null;
|
|---|
| 1443 | }
|
|---|
| 1444 |
|
|---|
| 1445 | /**
|
|---|
| 1446 | * Determines if the filename has one of the given extensions, in a robust manner.
|
|---|
| 1447 | * The comparison is case and locale insensitive.
|
|---|
| 1448 | * @param filename The file name
|
|---|
| 1449 | * @param extensions The list of extensions to look for (without dot)
|
|---|
| 1450 | * @return {@code true} if the filename has one of the given extensions
|
|---|
| 1451 | * @since 8404
|
|---|
| 1452 | */
|
|---|
| 1453 | public static boolean hasExtension(String filename, String... extensions) {
|
|---|
| 1454 | String name = filename.toLowerCase(Locale.ENGLISH).replace("?format=raw", "");
|
|---|
| 1455 | return Arrays.stream(extensions)
|
|---|
| 1456 | .anyMatch(ext -> name.endsWith('.' + ext.toLowerCase(Locale.ENGLISH)));
|
|---|
| 1457 | }
|
|---|
| 1458 |
|
|---|
| 1459 | /**
|
|---|
| 1460 | * Determines if the file's name has one of the given extensions, in a robust manner.
|
|---|
| 1461 | * The comparison is case and locale insensitive.
|
|---|
| 1462 | * @param file The file
|
|---|
| 1463 | * @param extensions The list of extensions to look for (without dot)
|
|---|
| 1464 | * @return {@code true} if the file's name has one of the given extensions
|
|---|
| 1465 | * @since 8404
|
|---|
| 1466 | */
|
|---|
| 1467 | public static boolean hasExtension(File file, String... extensions) {
|
|---|
| 1468 | return hasExtension(file.getName(), extensions);
|
|---|
| 1469 | }
|
|---|
| 1470 |
|
|---|
| 1471 | /**
|
|---|
| 1472 | * Returns the initial capacity to pass to the HashMap / HashSet constructor
|
|---|
| 1473 | * when it is initialized with a known number of entries.
|
|---|
| 1474 | * <p>
|
|---|
| 1475 | * When a HashMap is filled with entries, the underlying array is copied over
|
|---|
| 1476 | * to a larger one multiple times. To avoid this process when the number of
|
|---|
| 1477 | * entries is known in advance, the initial capacity of the array can be
|
|---|
| 1478 | * given to the HashMap constructor. This method returns a suitable value
|
|---|
| 1479 | * that avoids rehashing but doesn't waste memory.
|
|---|
| 1480 | * @param nEntries the number of entries expected
|
|---|
| 1481 | * @param loadFactor the load factor
|
|---|
| 1482 | * @return the initial capacity for the HashMap constructor
|
|---|
| 1483 | */
|
|---|
| 1484 | public static int hashMapInitialCapacity(int nEntries, double loadFactor) {
|
|---|
| 1485 | return (int) Math.ceil(nEntries / loadFactor);
|
|---|
| 1486 | }
|
|---|
| 1487 |
|
|---|
| 1488 | /**
|
|---|
| 1489 | * Returns the initial capacity to pass to the HashMap / HashSet constructor
|
|---|
| 1490 | * when it is initialized with a known number of entries.
|
|---|
| 1491 | * <p>
|
|---|
| 1492 | * When a HashMap is filled with entries, the underlying array is copied over
|
|---|
| 1493 | * to a larger one multiple times. To avoid this process when the number of
|
|---|
| 1494 | * entries is known in advance, the initial capacity of the array can be
|
|---|
| 1495 | * given to the HashMap constructor. This method returns a suitable value
|
|---|
| 1496 | * that avoids rehashing but doesn't waste memory.
|
|---|
| 1497 | * <p>
|
|---|
| 1498 | * Assumes default load factor (0.75).
|
|---|
| 1499 | * @param nEntries the number of entries expected
|
|---|
| 1500 | * @return the initial capacity for the HashMap constructor
|
|---|
| 1501 | */
|
|---|
| 1502 | public static int hashMapInitialCapacity(int nEntries) {
|
|---|
| 1503 | return hashMapInitialCapacity(nEntries, 0.75d);
|
|---|
| 1504 | }
|
|---|
| 1505 |
|
|---|
| 1506 | /**
|
|---|
| 1507 | * Utility class to save a string along with its rendering direction
|
|---|
| 1508 | * (left-to-right or right-to-left).
|
|---|
| 1509 | */
|
|---|
| 1510 | private static class DirectionString {
|
|---|
| 1511 | public final int direction;
|
|---|
| 1512 | public final String str;
|
|---|
| 1513 |
|
|---|
| 1514 | DirectionString(int direction, String str) {
|
|---|
| 1515 | this.direction = direction;
|
|---|
| 1516 | this.str = str;
|
|---|
| 1517 | }
|
|---|
| 1518 | }
|
|---|
| 1519 |
|
|---|
| 1520 | /**
|
|---|
| 1521 | * Convert a string to a list of {@link GlyphVector}s. The string may contain
|
|---|
| 1522 | * bi-directional text. The result will be in correct visual order.
|
|---|
| 1523 | * Each element of the resulting list corresponds to one section of the
|
|---|
| 1524 | * string with consistent writing direction (left-to-right or right-to-left).
|
|---|
| 1525 | *
|
|---|
| 1526 | * @param string the string to render
|
|---|
| 1527 | * @param font the font
|
|---|
| 1528 | * @param frc a FontRenderContext object
|
|---|
| 1529 | * @return a list of GlyphVectors
|
|---|
| 1530 | */
|
|---|
| 1531 | public static List<GlyphVector> getGlyphVectorsBidi(String string, Font font, FontRenderContext frc) {
|
|---|
| 1532 | final List<GlyphVector> gvs = new ArrayList<>();
|
|---|
| 1533 | final Bidi bidi = new Bidi(string, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT);
|
|---|
| 1534 | final byte[] levels = new byte[bidi.getRunCount()];
|
|---|
| 1535 | final DirectionString[] dirStrings = new DirectionString[levels.length];
|
|---|
| 1536 | for (int i = 0; i < levels.length; ++i) {
|
|---|
| 1537 | levels[i] = (byte) bidi.getRunLevel(i);
|
|---|
| 1538 | final String substr = string.substring(bidi.getRunStart(i), bidi.getRunLimit(i));
|
|---|
| 1539 | final int dir = levels[i] % 2 == 0 ? Bidi.DIRECTION_LEFT_TO_RIGHT : Bidi.DIRECTION_RIGHT_TO_LEFT;
|
|---|
| 1540 | dirStrings[i] = new DirectionString(dir, substr);
|
|---|
| 1541 | }
|
|---|
| 1542 | Bidi.reorderVisually(levels, 0, dirStrings, 0, levels.length);
|
|---|
| 1543 | for (DirectionString dirString : dirStrings) {
|
|---|
| 1544 | final char[] chars = dirString.str.toCharArray();
|
|---|
| 1545 | gvs.add(font.layoutGlyphVector(frc, chars, 0, chars.length, dirString.direction));
|
|---|
| 1546 | }
|
|---|
| 1547 | return gvs;
|
|---|
| 1548 | }
|
|---|
| 1549 |
|
|---|
| 1550 | /**
|
|---|
| 1551 | * Removes diacritics (accents) from string.
|
|---|
| 1552 | * @param str string
|
|---|
| 1553 | * @return {@code str} without any diacritic (accent)
|
|---|
| 1554 | * @since 13836 (moved from SimilarNamedWays)
|
|---|
| 1555 | */
|
|---|
| 1556 | public static String deAccent(String str) {
|
|---|
| 1557 | // https://stackoverflow.com/a/1215117/2257172
|
|---|
| 1558 | return REMOVE_DIACRITICS.matcher(Normalizer.normalize(str, Normalizer.Form.NFD)).replaceAll("");
|
|---|
| 1559 | }
|
|---|
| 1560 |
|
|---|
| 1561 | /**
|
|---|
| 1562 | * Clamp a value to the given range
|
|---|
| 1563 | * @param val The value
|
|---|
| 1564 | * @param min minimum value
|
|---|
| 1565 | * @param max maximum value
|
|---|
| 1566 | * @return the value
|
|---|
| 1567 | * @throws IllegalArgumentException if {@code min > max}
|
|---|
| 1568 | * @since 10805
|
|---|
| 1569 | */
|
|---|
| 1570 | public static double clamp(double val, double min, double max) {
|
|---|
| 1571 | // Switch to Math.clamp when we move to Java 21
|
|---|
| 1572 | if (min > max) {
|
|---|
| 1573 | throw new IllegalArgumentException(MessageFormat.format("Parameter min ({0}) cannot be greater than max ({1})", min, max));
|
|---|
| 1574 | } else if (val < min) {
|
|---|
| 1575 | return min;
|
|---|
| 1576 | }
|
|---|
| 1577 | return Math.min(val, max);
|
|---|
| 1578 | }
|
|---|
| 1579 |
|
|---|
| 1580 | /**
|
|---|
| 1581 | * Clamp a integer value to the given range
|
|---|
| 1582 | * @param val The value
|
|---|
| 1583 | * @param min minimum value
|
|---|
| 1584 | * @param max maximum value
|
|---|
| 1585 | * @return the value
|
|---|
| 1586 | * @throws IllegalArgumentException if {@code min > max}
|
|---|
| 1587 | * @since 11055
|
|---|
| 1588 | */
|
|---|
| 1589 | public static int clamp(int val, int min, int max) {
|
|---|
| 1590 | if (min > max) {
|
|---|
| 1591 | throw new IllegalArgumentException(MessageFormat.format("Parameter min ({0}) cannot be greater than max ({1})", min, max));
|
|---|
| 1592 | } else if (val < min) {
|
|---|
| 1593 | return min;
|
|---|
| 1594 | } else if (val > max) {
|
|---|
| 1595 | return max;
|
|---|
| 1596 | } else {
|
|---|
| 1597 | return val;
|
|---|
| 1598 | }
|
|---|
| 1599 | }
|
|---|
| 1600 |
|
|---|
| 1601 | /**
|
|---|
| 1602 | * Convert angle from radians to degrees.
|
|---|
| 1603 | * <p>
|
|---|
| 1604 | * Replacement for {@link Math#toDegrees(double)} to match the Java 9
|
|---|
| 1605 | * version of that method. (Can be removed when JOSM support for Java 8 ends.)
|
|---|
| 1606 | * Only relevant in relation to ProjectionRegressionTest.
|
|---|
| 1607 | * @param angleRad an angle in radians
|
|---|
| 1608 | * @return the same angle in degrees
|
|---|
| 1609 | * @see <a href="https://josm.openstreetmap.de/ticket/11889">#11889</a>
|
|---|
| 1610 | * @since 12013
|
|---|
| 1611 | */
|
|---|
| 1612 | public static double toDegrees(double angleRad) {
|
|---|
| 1613 | return angleRad * TO_DEGREES;
|
|---|
| 1614 | }
|
|---|
| 1615 |
|
|---|
| 1616 | /**
|
|---|
| 1617 | * Convert angle from degrees to radians.
|
|---|
| 1618 | * <p>
|
|---|
| 1619 | * Replacement for {@link Math#toRadians(double)} to match the Java 9
|
|---|
| 1620 | * version of that method. (Can be removed when JOSM support for Java 8 ends.)
|
|---|
| 1621 | * Only relevant in relation to ProjectionRegressionTest.
|
|---|
| 1622 | * @param angleDeg an angle in degrees
|
|---|
| 1623 | * @return the same angle in radians
|
|---|
| 1624 | * @see <a href="https://josm.openstreetmap.de/ticket/11889">#11889</a>
|
|---|
| 1625 | * @since 12013
|
|---|
| 1626 | */
|
|---|
| 1627 | public static double toRadians(double angleDeg) {
|
|---|
| 1628 | return angleDeg * TO_RADIANS;
|
|---|
| 1629 | }
|
|---|
| 1630 |
|
|---|
| 1631 | /**
|
|---|
| 1632 | * Returns the Java version as an int value.
|
|---|
| 1633 | * @return the Java version as an int value (8, 9, 10, etc.)
|
|---|
| 1634 | * @since 12130
|
|---|
| 1635 | */
|
|---|
| 1636 | public static int getJavaVersion() {
|
|---|
| 1637 | // Switch to Runtime.version() once we move past Java 8
|
|---|
| 1638 | String version = Objects.requireNonNull(getSystemProperty("java.version"));
|
|---|
| 1639 | if (version.startsWith("1.")) {
|
|---|
| 1640 | version = version.substring(2);
|
|---|
| 1641 | }
|
|---|
| 1642 | // Allow these formats:
|
|---|
| 1643 | // 1.8.0_72-ea
|
|---|
| 1644 | // 9-ea
|
|---|
| 1645 | // 9
|
|---|
| 1646 | // 9.0.1
|
|---|
| 1647 | int dotPos = version.indexOf('.');
|
|---|
| 1648 | int dashPos = version.indexOf('-');
|
|---|
| 1649 | return Integer.parseInt(version.substring(0,
|
|---|
| 1650 | dotPos > -1 ? dotPos : dashPos > -1 ? dashPos : version.length()));
|
|---|
| 1651 | }
|
|---|
| 1652 |
|
|---|
| 1653 | /**
|
|---|
| 1654 | * Returns the Java update as an int value.
|
|---|
| 1655 | * @return the Java update as an int value (121, 131, etc.)
|
|---|
| 1656 | * @since 12217
|
|---|
| 1657 | */
|
|---|
| 1658 | public static int getJavaUpdate() {
|
|---|
| 1659 | // Switch to Runtime.version() once we move past Java 8
|
|---|
| 1660 | String version = Objects.requireNonNull(getSystemProperty("java.version"));
|
|---|
| 1661 | if (version.startsWith("1.")) {
|
|---|
| 1662 | version = version.substring(2);
|
|---|
| 1663 | }
|
|---|
| 1664 | // Allow these formats:
|
|---|
| 1665 | // 1.8.0_72-ea
|
|---|
| 1666 | // 9-ea
|
|---|
| 1667 | // 9
|
|---|
| 1668 | // 9.0.1
|
|---|
| 1669 | // 17.0.4.1+1-LTS
|
|---|
| 1670 | // $MAJOR.$MINOR.$SECURITY.$PATCH
|
|---|
| 1671 | int undePos = version.indexOf('_');
|
|---|
| 1672 | int dashPos = version.indexOf('-');
|
|---|
| 1673 | if (undePos > -1) {
|
|---|
| 1674 | return Integer.parseInt(version.substring(undePos + 1,
|
|---|
| 1675 | dashPos > -1 ? dashPos : version.length()));
|
|---|
| 1676 | }
|
|---|
| 1677 | int firstDotPos = version.indexOf('.');
|
|---|
| 1678 | int secondDotPos = version.indexOf('.', firstDotPos + 1);
|
|---|
| 1679 | if (firstDotPos == secondDotPos) {
|
|---|
| 1680 | return 0;
|
|---|
| 1681 | }
|
|---|
| 1682 | return firstDotPos > -1 ? Integer.parseInt(version.substring(firstDotPos + 1,
|
|---|
| 1683 | secondDotPos > -1 ? secondDotPos : version.length())) : 0;
|
|---|
| 1684 | }
|
|---|
| 1685 |
|
|---|
| 1686 | /**
|
|---|
| 1687 | * Returns the Java build number as an int value.
|
|---|
| 1688 | * @return the Java build number as an int value (0, 1, etc.)
|
|---|
| 1689 | * @since 12217
|
|---|
| 1690 | */
|
|---|
| 1691 | public static int getJavaBuild() {
|
|---|
| 1692 | // Switch to Runtime.version() once we move past Java 8
|
|---|
| 1693 | String version = Objects.requireNonNull(getSystemProperty("java.runtime.version"));
|
|---|
| 1694 | int bPos = version.indexOf('b');
|
|---|
| 1695 | int pPos = version.indexOf('+');
|
|---|
| 1696 | try {
|
|---|
| 1697 | return Integer.parseInt(version.substring(bPos > -1 ? bPos + 1 : pPos + 1));
|
|---|
| 1698 | } catch (NumberFormatException e) {
|
|---|
| 1699 | Logging.trace(e);
|
|---|
| 1700 | return 0;
|
|---|
| 1701 | }
|
|---|
| 1702 | }
|
|---|
| 1703 |
|
|---|
| 1704 | /**
|
|---|
| 1705 | * Returns the latest version of Java, from Oracle website.
|
|---|
| 1706 | * @return the latest version of Java, from Oracle website
|
|---|
| 1707 | * @since 12219
|
|---|
| 1708 | */
|
|---|
| 1709 | public static String getJavaLatestVersion() {
|
|---|
| 1710 | try {
|
|---|
| 1711 | String[] versions = HttpClient.create(
|
|---|
| 1712 | new URL(Config.getPref().get(
|
|---|
| 1713 | "java.baseline.version.url",
|
|---|
| 1714 | Config.getUrls().getJOSMWebsite() + "/remote/oracle-java-update-baseline.version")))
|
|---|
| 1715 | .connect().fetchContent().split("\n", -1);
|
|---|
| 1716 | // OpenWebStart currently only has Java 21
|
|---|
| 1717 | if (getJavaVersion() <= 21) {
|
|---|
| 1718 | for (String version : versions) {
|
|---|
| 1719 | if (version.startsWith("21")) { // Use current Java LTS
|
|---|
| 1720 | return version;
|
|---|
| 1721 | }
|
|---|
| 1722 | }
|
|---|
| 1723 | }
|
|---|
| 1724 | return versions[0];
|
|---|
| 1725 | } catch (IOException e) {
|
|---|
| 1726 | Logging.error(e);
|
|---|
| 1727 | }
|
|---|
| 1728 | return null;
|
|---|
| 1729 | }
|
|---|
| 1730 |
|
|---|
| 1731 | /**
|
|---|
| 1732 | * Determines if a class can be found for the given name.
|
|---|
| 1733 | * @param className class name to find
|
|---|
| 1734 | * @return {@code true} if the class can be found, {@code false} otherwise
|
|---|
| 1735 | * @since 17692
|
|---|
| 1736 | */
|
|---|
| 1737 | public static boolean isClassFound(String className) {
|
|---|
| 1738 | try {
|
|---|
| 1739 | return Class.forName(className) != null;
|
|---|
| 1740 | } catch (ClassNotFoundException e) {
|
|---|
| 1741 | return false;
|
|---|
| 1742 | }
|
|---|
| 1743 | }
|
|---|
| 1744 |
|
|---|
| 1745 | /**
|
|---|
| 1746 | * Determines whether JOSM has been started via Web Start (JNLP).
|
|---|
| 1747 | * @return true if JOSM has been started via Web Start (JNLP)
|
|---|
| 1748 | * @since 17679
|
|---|
| 1749 | */
|
|---|
| 1750 | public static boolean isRunningWebStart() {
|
|---|
| 1751 | // See http://stackoverflow.com/a/16200769/2257172
|
|---|
| 1752 | return isClassFound("javax.jnlp.ServiceManager");
|
|---|
| 1753 | }
|
|---|
| 1754 |
|
|---|
| 1755 | /**
|
|---|
| 1756 | * Determines whether JOSM has been started via Open Web Start (IcedTea-Web).
|
|---|
| 1757 | * @return true if JOSM has been started via Open Web Start (IcedTea-Web)
|
|---|
| 1758 | * @since 17679
|
|---|
| 1759 | */
|
|---|
| 1760 | public static boolean isRunningOpenWebStart() {
|
|---|
| 1761 | // To be kept in sync if package name changes to org.eclipse.adoptium or something
|
|---|
| 1762 | return isRunningWebStart() && isClassFound("net.adoptopenjdk.icedteaweb.client.commandline.CommandLine");
|
|---|
| 1763 | }
|
|---|
| 1764 |
|
|---|
| 1765 | /**
|
|---|
| 1766 | * Get a function that converts an object to a singleton stream of a certain
|
|---|
| 1767 | * class (or null if the object cannot be cast to that class).
|
|---|
| 1768 | * <p>
|
|---|
| 1769 | * Can be useful in relation with streams, but be aware of the performance
|
|---|
| 1770 | * implications of creating a stream for each element.
|
|---|
| 1771 | * @param <T> type of the objects to convert
|
|---|
| 1772 | * @param <U> type of the elements in the resulting stream
|
|---|
| 1773 | * @param klass the class U
|
|---|
| 1774 | * @return function converting an object to a singleton stream or null
|
|---|
| 1775 | * @since 12594
|
|---|
| 1776 | */
|
|---|
| 1777 | public static <T, U> Function<T, Stream<U>> castToStream(Class<U> klass) {
|
|---|
| 1778 | return x -> klass.isInstance(x) ? Stream.of(klass.cast(x)) : null;
|
|---|
| 1779 | }
|
|---|
| 1780 |
|
|---|
| 1781 | /**
|
|---|
| 1782 | * Helper method to replace the "<code>instanceof</code>-check and cast" pattern.
|
|---|
| 1783 | * Checks if an object is instance of class T and performs an action if that
|
|---|
| 1784 | * is the case.
|
|---|
| 1785 | * Syntactic sugar to avoid typing the class name two times, when one time
|
|---|
| 1786 | * would suffice.
|
|---|
| 1787 | * @param <T> the type for the instanceof check and cast
|
|---|
| 1788 | * @param o the object to check and cast
|
|---|
| 1789 | * @param klass the class T
|
|---|
| 1790 | * @param consumer action to take when o is and instance of T
|
|---|
| 1791 | * @since 12604
|
|---|
| 1792 | */
|
|---|
| 1793 | public static <T> void instanceOfThen(Object o, Class<T> klass, Consumer<? super T> consumer) {
|
|---|
| 1794 | if (klass.isInstance(o)) {
|
|---|
| 1795 | consumer.accept(klass.cast(o));
|
|---|
| 1796 | }
|
|---|
| 1797 | }
|
|---|
| 1798 |
|
|---|
| 1799 | /**
|
|---|
| 1800 | * Helper method to replace the "<code>instanceof</code>-check and cast" pattern.
|
|---|
| 1801 | *
|
|---|
| 1802 | * @param <T> the type for the instanceof check and cast
|
|---|
| 1803 | * @param o the object to check and cast
|
|---|
| 1804 | * @param klass the class T
|
|---|
| 1805 | * @return {@link Optional} containing the result of the cast, if it is possible, an empty
|
|---|
| 1806 | * Optional otherwise
|
|---|
| 1807 | */
|
|---|
| 1808 | public static <T> Optional<T> instanceOfAndCast(Object o, Class<T> klass) {
|
|---|
| 1809 | if (klass.isInstance(o))
|
|---|
| 1810 | return Optional.of(klass.cast(o));
|
|---|
| 1811 | return Optional.empty();
|
|---|
| 1812 | }
|
|---|
| 1813 |
|
|---|
| 1814 | /**
|
|---|
| 1815 | * Convenient method to open an URL stream, using JOSM HTTP client if needed.
|
|---|
| 1816 | * @param url URL for reading from
|
|---|
| 1817 | * @return an input stream for reading from the URL
|
|---|
| 1818 | * @throws IOException if any I/O error occurs
|
|---|
| 1819 | * @since 13356
|
|---|
| 1820 | */
|
|---|
| 1821 | public static InputStream openStream(URL url) throws IOException {
|
|---|
| 1822 | switch (url.getProtocol()) {
|
|---|
| 1823 | case "http":
|
|---|
| 1824 | case "https":
|
|---|
| 1825 | return HttpClient.create(url).connect().getContent();
|
|---|
| 1826 | case "jar":
|
|---|
| 1827 | try {
|
|---|
| 1828 | return url.openStream();
|
|---|
| 1829 | } catch (FileNotFoundException | InvalidPathException e) {
|
|---|
| 1830 | final URL betterUrl = betterJarUrl(url);
|
|---|
| 1831 | if (betterUrl != null) {
|
|---|
| 1832 | try {
|
|---|
| 1833 | return betterUrl.openStream();
|
|---|
| 1834 | } catch (RuntimeException | IOException ex) {
|
|---|
| 1835 | Logging.warn(ex);
|
|---|
| 1836 | }
|
|---|
| 1837 | }
|
|---|
| 1838 | throw e;
|
|---|
| 1839 | }
|
|---|
| 1840 | case "file":
|
|---|
| 1841 | default:
|
|---|
| 1842 | return url.openStream();
|
|---|
| 1843 | }
|
|---|
| 1844 | }
|
|---|
| 1845 |
|
|---|
| 1846 | /**
|
|---|
| 1847 | * Tries to build a better JAR URL if we find it concerned by a JDK bug.
|
|---|
| 1848 | * @param jarUrl jar URL to test
|
|---|
| 1849 | * @return potentially a better URL that won't provoke a JDK bug, or null
|
|---|
| 1850 | * @throws IOException if an I/O error occurs
|
|---|
| 1851 | * @since 14404
|
|---|
| 1852 | */
|
|---|
| 1853 | public static URL betterJarUrl(URL jarUrl) throws IOException {
|
|---|
| 1854 | return betterJarUrl(jarUrl, null);
|
|---|
| 1855 | }
|
|---|
| 1856 |
|
|---|
| 1857 | /**
|
|---|
| 1858 | * Tries to build a better JAR URL if we find it concerned by a JDK bug.
|
|---|
| 1859 | * @param jarUrl jar URL to test
|
|---|
| 1860 | * @param defaultUrl default URL to return
|
|---|
| 1861 | * @return potentially a better URL that won't provoke a JDK bug, or {@code defaultUrl}
|
|---|
| 1862 | * @throws IOException if an I/O error occurs
|
|---|
| 1863 | * @since 14480
|
|---|
| 1864 | */
|
|---|
| 1865 | public static URL betterJarUrl(URL jarUrl, URL defaultUrl) throws IOException {
|
|---|
| 1866 | // Workaround to https://bugs.openjdk.java.net/browse/JDK-4523159
|
|---|
| 1867 | String urlPath = jarUrl.getPath().replace("%20", " ");
|
|---|
| 1868 | if (urlPath.startsWith("file:/") && urlPath.split("!", -1).length > 2) {
|
|---|
| 1869 | // Locate jar file
|
|---|
| 1870 | int index = urlPath.lastIndexOf("!/");
|
|---|
| 1871 | final Path jarFile = Paths.get(urlPath.substring("file:/".length(), index));
|
|---|
| 1872 | Path filename = jarFile.getFileName();
|
|---|
| 1873 | FileTime jarTime = Files.readAttributes(jarFile, BasicFileAttributes.class).lastModifiedTime();
|
|---|
| 1874 | // Copy it to temp directory (hopefully free of exclamation mark) if needed (missing or older jar)
|
|---|
| 1875 | final Path jarCopy = Paths.get(getSystemProperty("java.io.tmpdir")).resolve(filename);
|
|---|
| 1876 | if (!jarCopy.toFile().exists() ||
|
|---|
| 1877 | Files.readAttributes(jarCopy, BasicFileAttributes.class).lastModifiedTime().compareTo(jarTime) < 0) {
|
|---|
| 1878 | Files.copy(jarFile, jarCopy, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
|
|---|
| 1879 | }
|
|---|
| 1880 | // Return URL using the copy
|
|---|
| 1881 | return new URL(jarUrl.getProtocol() + ':' + jarCopy.toUri().toURL().toExternalForm() + urlPath.substring(index));
|
|---|
| 1882 | }
|
|---|
| 1883 | return defaultUrl;
|
|---|
| 1884 | }
|
|---|
| 1885 |
|
|---|
| 1886 | /**
|
|---|
| 1887 | * Finds a resource with a given name, with robustness to known JDK bugs.
|
|---|
| 1888 | * @param klass class on which {@link ClassLoader#getResourceAsStream} will be called
|
|---|
| 1889 | * @param path name of the desired resource
|
|---|
| 1890 | * @return A {@link java.io.InputStream} object or {@code null} if no resource with this name is found
|
|---|
| 1891 | * @since 14480
|
|---|
| 1892 | */
|
|---|
| 1893 | public static InputStream getResourceAsStream(Class<?> klass, String path) {
|
|---|
| 1894 | return getResourceAsStream(klass.getClassLoader(), path);
|
|---|
| 1895 | }
|
|---|
| 1896 |
|
|---|
| 1897 | /**
|
|---|
| 1898 | * Finds a resource with a given name, with robustness to known JDK bugs.
|
|---|
| 1899 | * @param cl classloader on which {@link ClassLoader#getResourceAsStream} will be called
|
|---|
| 1900 | * @param path name of the desired resource
|
|---|
| 1901 | * @return A {@link java.io.InputStream} object or {@code null} if no resource with this name is found
|
|---|
| 1902 | * @since 15416
|
|---|
| 1903 | */
|
|---|
| 1904 | public static InputStream getResourceAsStream(ClassLoader cl, String path) {
|
|---|
| 1905 | try {
|
|---|
| 1906 | if (path != null && path.startsWith("/")) {
|
|---|
| 1907 | path = path.substring(1); // See Class#resolveName
|
|---|
| 1908 | }
|
|---|
| 1909 | return cl.getResourceAsStream(path);
|
|---|
| 1910 | } catch (InvalidPathException e) {
|
|---|
| 1911 | Logging.error("Cannot open {0}: {1}", path, e.getMessage());
|
|---|
| 1912 | Logging.trace(e);
|
|---|
| 1913 | try {
|
|---|
| 1914 | final URL betterUrl = betterJarUrl(cl.getResource(path));
|
|---|
| 1915 | if (betterUrl != null) {
|
|---|
| 1916 | return betterUrl.openStream();
|
|---|
| 1917 | }
|
|---|
| 1918 | } catch (IOException ex) {
|
|---|
| 1919 | Logging.error(ex);
|
|---|
| 1920 | }
|
|---|
| 1921 | return null;
|
|---|
| 1922 | }
|
|---|
| 1923 | }
|
|---|
| 1924 |
|
|---|
| 1925 | /**
|
|---|
| 1926 | * Strips all HTML characters and return the result.
|
|---|
| 1927 | *
|
|---|
| 1928 | * @param rawString The raw HTML string
|
|---|
| 1929 | * @return the plain text from the HTML string
|
|---|
| 1930 | * @since 15760
|
|---|
| 1931 | */
|
|---|
| 1932 | public static String stripHtml(String rawString) {
|
|---|
| 1933 | // remove HTML tags
|
|---|
| 1934 | rawString = rawString.replaceAll("<[^>]+>", " ");
|
|---|
| 1935 | // consolidate multiple spaces between a word to a single space
|
|---|
| 1936 | rawString = rawString.replaceAll("(?U)\\b\\s{2,}\\b", " ");
|
|---|
| 1937 | // remove extra whitespaces
|
|---|
| 1938 | return rawString.trim();
|
|---|
| 1939 | }
|
|---|
| 1940 |
|
|---|
| 1941 | /**
|
|---|
| 1942 | * Intern a string
|
|---|
| 1943 | * @param string The string to intern
|
|---|
| 1944 | * @return The interned string
|
|---|
| 1945 | * @since 16545
|
|---|
| 1946 | */
|
|---|
| 1947 | public static String intern(String string) {
|
|---|
| 1948 | return string == null ? null : string.intern();
|
|---|
| 1949 | }
|
|---|
| 1950 |
|
|---|
| 1951 | /**
|
|---|
| 1952 | * Convert a length unit to meters
|
|---|
| 1953 | * @param s arbitrary string representing a length
|
|---|
| 1954 | * @return the length converted to meters
|
|---|
| 1955 | * @throws IllegalArgumentException if input is no valid length
|
|---|
| 1956 | * @since 19089
|
|---|
| 1957 | */
|
|---|
| 1958 | public static Double unitToMeter(String s) throws IllegalArgumentException {
|
|---|
| 1959 | s = s.replace(" ", "").replace(",", ".");
|
|---|
| 1960 | Matcher m = PATTERN_LENGTH.matcher(s);
|
|---|
| 1961 | if (m.matches()) {
|
|---|
| 1962 | return Double.parseDouble(m.group(1)) * unitToMeterConversion(m.group(2));
|
|---|
| 1963 | } else {
|
|---|
| 1964 | m = PATTERN_LENGTH2.matcher(s);
|
|---|
| 1965 | if (m.matches()) {
|
|---|
| 1966 | /* NOTE: we assume -a'b" means -(a'+b") and not (-a')+b" - because of such issues SI units have been invented
|
|---|
| 1967 | and have been adopted by the majority of the world */
|
|---|
| 1968 | return (Double.parseDouble(m.group(2))*0.3048+Double.parseDouble(m.group(4))*0.0254)*(m.group(1).isEmpty() ? 1.0 : -1.0);
|
|---|
| 1969 | }
|
|---|
| 1970 | }
|
|---|
| 1971 | throw new IllegalArgumentException("Invalid length value: " + s);
|
|---|
| 1972 | }
|
|---|
| 1973 |
|
|---|
| 1974 | /**
|
|---|
| 1975 | * Get the conversion factor for a specified unit to meters
|
|---|
| 1976 | * @param unit The unit to convert to meters
|
|---|
| 1977 | * @return The conversion factor or 1.
|
|---|
| 1978 | * @throws IllegalArgumentException if the unit does not currently have a conversion
|
|---|
| 1979 | */
|
|---|
| 1980 | private static double unitToMeterConversion(String unit) throws IllegalArgumentException {
|
|---|
| 1981 | if (unit == null) {
|
|---|
| 1982 | return 1;
|
|---|
| 1983 | }
|
|---|
| 1984 | switch (unit) {
|
|---|
| 1985 | case "cm": return 0.01;
|
|---|
| 1986 | case "mm": return 0.001;
|
|---|
| 1987 | case "m": return 1;
|
|---|
| 1988 | case "km": return 1000.0;
|
|---|
| 1989 | case "nmi": return 1852.0;
|
|---|
| 1990 | case "mi": return 1609.344;
|
|---|
| 1991 | case "ft":
|
|---|
| 1992 | case "'":
|
|---|
| 1993 | return 0.3048;
|
|---|
| 1994 | case "in":
|
|---|
| 1995 | case "\"":
|
|---|
| 1996 | return 0.0254;
|
|---|
| 1997 | default: throw new IllegalArgumentException("Invalid length unit: " + unit);
|
|---|
| 1998 | }
|
|---|
| 1999 | }
|
|---|
| 2000 |
|
|---|
| 2001 | /**
|
|---|
| 2002 | * Calculate the number of unicode code points. See #24446
|
|---|
| 2003 | * @param s the string
|
|---|
| 2004 | * @return 0 if s is null or empty, else the number of code points
|
|---|
| 2005 | * @since 19437
|
|---|
| 2006 | */
|
|---|
| 2007 | public static int getCodePointCount(String s) {
|
|---|
| 2008 | if (s == null)
|
|---|
| 2009 | return 0;
|
|---|
| 2010 | return s.codePointCount(0, s.length());
|
|---|
| 2011 | }
|
|---|
| 2012 |
|
|---|
| 2013 | /**
|
|---|
| 2014 | * Check if a given string has more than the allowed number of code points.
|
|---|
| 2015 | * See #24446. The OSM server checks this number, not the value returned by String.length()
|
|---|
| 2016 | * @param s the string
|
|---|
| 2017 | * @param maxLen the maximum number of code points
|
|---|
| 2018 | * @return true if s is null or within the given limit, false else
|
|---|
| 2019 | * @since 19437
|
|---|
| 2020 | */
|
|---|
| 2021 | public static boolean checkCodePointCount(String s, int maxLen) {
|
|---|
| 2022 | return getCodePointCount(s) <= maxLen;
|
|---|
| 2023 | }
|
|---|
| 2024 | }
|
|---|