blob: 301f4113419bfed9ed37d177d3b3dd3cd9660816 [file] [log] [blame]
Thomas Vachuska24c849c2014-10-27 09:53:05 -07001/*
Ray Milkey34c95902015-04-15 09:47:53 -07002 * Copyright 2014-2015 Open Networking Laboratory
Thomas Vachuska24c849c2014-10-27 09:53:05 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
Thomas Vachuska24c849c2014-10-27 09:53:05 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Thomas Vachuska24c849c2014-10-27 09:53:05 -070015 */
tom5f38b3a2014-08-27 23:50:54 -070016package org.onlab.util;
17
Madan Jampani2bfa94c2015-04-11 05:03:49 -070018import static java.nio.file.Files.delete;
19import static java.nio.file.Files.walkFileTree;
20import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory;
21import static org.slf4j.LoggerFactory.getLogger;
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080022
tom53efab52014-10-07 17:43:48 -070023import java.io.BufferedReader;
24import java.io.File;
Ray Milkey705d9bc2014-11-18 08:19:00 -080025import java.io.FileInputStream;
tom53efab52014-10-07 17:43:48 -070026import java.io.IOException;
Ray Milkey705d9bc2014-11-18 08:19:00 -080027import java.io.InputStreamReader;
Madan Jampani27b69c62015-05-15 15:49:02 -070028import java.nio.ByteBuffer;
Ray Milkey705d9bc2014-11-18 08:19:00 -080029import java.nio.charset.StandardCharsets;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080030import java.nio.file.FileVisitResult;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080031import java.nio.file.Files;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080032import java.nio.file.Path;
33import java.nio.file.Paths;
34import java.nio.file.SimpleFileVisitor;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080035import java.nio.file.StandardCopyOption;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080036import java.nio.file.attribute.BasicFileAttributes;
tom53efab52014-10-07 17:43:48 -070037import java.util.ArrayList;
Madan Jampani27b69c62015-05-15 15:49:02 -070038import java.util.Arrays;
Brian O'Connore2eac102015-02-12 18:30:22 -080039import java.util.Collection;
Thomas Vachuska6519e6f2015-03-11 02:29:31 -070040import java.util.Dictionary;
tom53efab52014-10-07 17:43:48 -070041import java.util.List;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070042import java.util.Random;
Madan Jampani27b69c62015-05-15 15:49:02 -070043import java.util.concurrent.CompletableFuture;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070044import java.util.concurrent.ExecutionException;
45import java.util.concurrent.Future;
tom5f38b3a2014-08-27 23:50:54 -070046import java.util.concurrent.ThreadFactory;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070047import java.util.concurrent.TimeUnit;
48import java.util.concurrent.TimeoutException;
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -070049import java.util.stream.Stream;
50import java.util.stream.StreamSupport;
tom5f38b3a2014-08-27 23:50:54 -070051
Madan Jampani2bfa94c2015-04-11 05:03:49 -070052import org.slf4j.Logger;
53
54import com.google.common.base.Strings;
55import com.google.common.primitives.UnsignedLongs;
56import com.google.common.util.concurrent.ThreadFactoryBuilder;
Ray Milkey705d9bc2014-11-18 08:19:00 -080057
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080058/**
59 * Miscellaneous utility methods.
60 */
tom5f38b3a2014-08-27 23:50:54 -070061public abstract class Tools {
62
63 private Tools() {
64 }
65
Thomas Vachuska02aeb032015-01-06 22:36:30 -080066 private static final Logger log = getLogger(Tools.class);
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080067
Thomas Vachuskaadba1522015-06-04 15:08:30 -070068 private static Random random = new Random();
69
tom5f38b3a2014-08-27 23:50:54 -070070 /**
71 * Returns a thread factory that produces threads named according to the
72 * supplied name pattern.
73 *
74 * @param pattern name pattern
75 * @return thread factory
76 */
77 public static ThreadFactory namedThreads(String pattern) {
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080078 return new ThreadFactoryBuilder()
79 .setNameFormat(pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080080 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
81 .build();
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080082 }
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080083
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080084 /**
85 * Returns a thread factory that produces threads named according to the
86 * supplied name pattern and from the specified thread-group. The thread
87 * group name is expected to be specified in slash-delimited format, e.g.
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080088 * {@code onos/intent}. The thread names will be produced by converting
89 * the thread group name into dash-delimited format and pre-pended to the
90 * specified pattern.
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080091 *
92 * @param groupName group name in slash-delimited format to indicate hierarchy
93 * @param pattern name pattern
94 * @return thread factory
95 */
96 public static ThreadFactory groupedThreads(String groupName, String pattern) {
97 return new ThreadFactoryBuilder()
98 .setThreadFactory(groupedThreadFactory(groupName))
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080099 .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -0800100 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
101 .build();
tom5f38b3a2014-08-27 23:50:54 -0700102 }
103
tom782a7cf2014-09-11 23:58:38 -0700104 /**
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800105 * Returns a thread factory that produces threads with MIN_PRIORITY.
106 *
107 * @param factory backing ThreadFactory
108 * @return thread factory
109 */
110 public static ThreadFactory minPriority(ThreadFactory factory) {
111 return new ThreadFactoryBuilder()
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800112 .setThreadFactory(factory)
113 .setPriority(Thread.MIN_PRIORITY)
114 .build();
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800115 }
116
117 /**
Brian O'Connore2eac102015-02-12 18:30:22 -0800118 * Returns true if the collection is null or is empty.
119 *
120 * @param collection collection to test
121 * @return true if null or empty; false otherwise
122 */
123 public static boolean isNullOrEmpty(Collection collection) {
124 return collection == null || collection.isEmpty();
125 }
126
127 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700128 * Returns the specified item if that item is not null; otherwise throws
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700129 * not found exception.
130 *
131 * @param item item to check
132 * @param message not found message
133 * @param <T> item type
134 * @return item if not null
135 * @throws org.onlab.util.ItemNotFoundException if item is null
136 */
137 public static <T> T nullIsNotFound(T item, String message) {
138 if (item == null) {
139 throw new ItemNotFoundException(message);
140 }
141 return item;
142 }
143
144 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700145 * Returns the specified item if that item is not null; otherwise throws
146 * bad argument exception.
147 *
148 * @param item item to check
149 * @param message not found message
150 * @param <T> item type
151 * @return item if not null
152 * @throws IllegalArgumentException if item is null
153 */
154 public static <T> T nullIsIllegal(T item, String message) {
155 if (item == null) {
156 throw new IllegalArgumentException(message);
157 }
158 return item;
159 }
160
161 /**
tom782a7cf2014-09-11 23:58:38 -0700162 * Converts a string from hex to long.
163 *
164 * @param string hex number in string form; sans 0x
165 * @return long value
166 */
167 public static long fromHex(String string) {
168 return UnsignedLongs.parseUnsignedLong(string, 16);
169 }
170
171 /**
172 * Converts a long value to hex string; 16 wide and sans 0x.
173 *
174 * @param value long value
175 * @return hex string
176 */
177 public static String toHex(long value) {
178 return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
179 }
180
181 /**
182 * Converts a long value to hex string; 16 wide and sans 0x.
183 *
184 * @param value long value
185 * @param width string width; zero padded
186 * @return hex string
187 */
188 public static String toHex(long value, int width) {
189 return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
190 }
tomf110fff2014-09-26 00:38:18 -0700191
192 /**
Madan Jampani9eb55d12015-08-14 07:47:56 -0700193 * Returns a copy of the input byte array.
194 *
195 * @param original input
196 * @return copy of original
197 */
198 public static byte[] copyOf(byte[] original) {
199 return Arrays.copyOf(original, original.length);
200 }
201
202 /**
Thomas Vachuska6519e6f2015-03-11 02:29:31 -0700203 * Get property as a string value.
204 *
205 * @param properties properties to be looked up
206 * @param propertyName the name of the property to look up
207 * @return value when the propertyName is defined or return null
208 */
209 public static String get(Dictionary<?, ?> properties, String propertyName) {
210 Object v = properties.get(propertyName);
211 String s = (v instanceof String) ? (String) v :
212 v != null ? v.toString() : null;
213 return Strings.isNullOrEmpty(s) ? null : s.trim();
214 }
215
216 /**
tomf110fff2014-09-26 00:38:18 -0700217 * Suspends the current thread for a specified number of millis.
218 *
219 * @param ms number of millis
220 */
221 public static void delay(int ms) {
222 try {
223 Thread.sleep(ms);
224 } catch (InterruptedException e) {
225 throw new RuntimeException("Interrupted", e);
226 }
227 }
228
tom53efab52014-10-07 17:43:48 -0700229 /**
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700230 * Suspends the current thread for a random number of millis between 0 and
231 * the indicated limit.
232 *
233 * @param ms max number of millis
234 */
235 public static void randomDelay(int ms) {
236 try {
237 Thread.sleep(random.nextInt(ms));
238 } catch (InterruptedException e) {
239 throw new RuntimeException("Interrupted", e);
240 }
241 }
242
243 /**
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700244 * Suspends the current thread for a specified number of millis and nanos.
245 *
246 * @param ms number of millis
247 * @param nanos number of nanos
248 */
249 public static void delay(int ms, int nanos) {
250 try {
251 Thread.sleep(ms, nanos);
252 } catch (InterruptedException e) {
253 throw new RuntimeException("Interrupted", e);
254 }
255 }
256
257 /**
tom53efab52014-10-07 17:43:48 -0700258 * Slurps the contents of a file into a list of strings, one per line.
259 *
260 * @param path file path
261 * @return file contents
262 */
263 public static List<String> slurp(File path) {
Ray Milkey705d9bc2014-11-18 08:19:00 -0800264 try {
265 BufferedReader br = new BufferedReader(
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800266 new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8));
Ray Milkey705d9bc2014-11-18 08:19:00 -0800267
tom53efab52014-10-07 17:43:48 -0700268 List<String> lines = new ArrayList<>();
269 String line;
270 while ((line = br.readLine()) != null) {
271 lines.add(line);
272 }
273 return lines;
274
275 } catch (IOException e) {
276 return null;
277 }
278 }
279
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800280 /**
281 * Purges the specified directory path.&nbsp;Use with great caution since
282 * no attempt is made to check for symbolic links, which could result in
283 * deletion of unintended files.
284 *
285 * @param path directory to be removed
286 * @throws java.io.IOException if unable to remove contents
287 */
288 public static void removeDirectory(String path) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800289 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700290 File dir = new File(path);
291 if (dir.exists() && dir.isDirectory()) {
292 walkFileTree(Paths.get(path), visitor);
293 if (visitor.exception != null) {
294 throw visitor.exception;
295 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800296 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800297 }
298
299 /**
300 * Purges the specified directory path.&nbsp;Use with great caution since
301 * no attempt is made to check for symbolic links, which could result in
302 * deletion of unintended files.
303 *
304 * @param dir directory to be removed
305 * @throws java.io.IOException if unable to remove contents
306 */
307 public static void removeDirectory(File dir) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800308 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700309 if (dir.exists() && dir.isDirectory()) {
310 walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
311 if (visitor.exception != null) {
312 throw visitor.exception;
313 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800314 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800315 }
316
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800317 // Auxiliary path visitor for recursive directory structure removal.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800318 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800319
320 private IOException exception;
321
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800322 @Override
323 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
324 throws IOException {
325 if (attributes.isRegularFile()) {
326 delete(file);
327 }
328 return FileVisitResult.CONTINUE;
329 }
330
331 @Override
332 public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
333 throws IOException {
334 delete(directory);
335 return FileVisitResult.CONTINUE;
336 }
337
338 @Override
339 public FileVisitResult visitFileFailed(Path file, IOException ioe)
340 throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800341 this.exception = ioe;
342 return FileVisitResult.TERMINATE;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800343 }
344 }
345
Madan Jampani30a57f82015-03-02 12:19:41 -0800346 /**
347 * Returns a human friendly time ago string for a specified system time.
Thomas Vachuska480adad2015-03-06 10:27:09 -0800348 *
Madan Jampani30a57f82015-03-02 12:19:41 -0800349 * @param unixTime system time in millis
350 * @return human friendly time ago
351 */
352 public static String timeAgo(long unixTime) {
353 long deltaMillis = System.currentTimeMillis() - unixTime;
354 long secondsSince = (long) (deltaMillis / 1000.0);
355 long minsSince = (long) (deltaMillis / (1000.0 * 60));
356 long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
357 long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
358 if (daysSince > 0) {
359 return String.format("%dd ago", daysSince);
360 } else if (hoursSince > 0) {
361 return String.format("%dh ago", hoursSince);
362 } else if (minsSince > 0) {
363 return String.format("%dm ago", minsSince);
364 } else if (secondsSince > 0) {
365 return String.format("%ds ago", secondsSince);
366 } else {
367 return "just now";
368 }
369 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800370
371 /**
372 * Copies the specified directory path.&nbsp;Use with great caution since
373 * no attempt is made to check for symbolic links, which could result in
374 * copy of unintended files.
375 *
376 * @param src directory to be copied
377 * @param dst destination directory to be removed
378 * @throws java.io.IOException if unable to remove contents
379 */
380 public static void copyDirectory(String src, String dst) throws IOException {
381 walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
382 }
383
384 /**
385 * Copies the specified directory path.&nbsp;Use with great caution since
386 * no attempt is made to check for symbolic links, which could result in
387 * copy of unintended files.
388 *
389 * @param src directory to be copied
390 * @param dst destination directory to be removed
391 * @throws java.io.IOException if unable to remove contents
392 */
393 public static void copyDirectory(File src, File dst) throws IOException {
394 walkFileTree(Paths.get(src.getAbsolutePath()),
395 new DirectoryCopier(src.getAbsolutePath(),
396 dst.getAbsolutePath()));
397 }
398
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700399 /**
400 * Returns the future value when complete or if future
401 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700402 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700403 * @param future future
404 * @param defaultValue default value
405 * @param <T> future value type
406 * @return future value when complete or if future
407 * completes exceptionally returns the defaultValue.
408 */
409 public static <T> T futureGetOrElse(Future<T> future, T defaultValue) {
410 try {
411 return future.get();
412 } catch (InterruptedException e) {
413 Thread.currentThread().interrupt();
414 return defaultValue;
415 } catch (ExecutionException e) {
416 return defaultValue;
417 }
418 }
419
420 /**
421 * Returns the future value when complete or if future
422 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700423 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700424 * @param future future
425 * @param timeout time to wait for successful completion
426 * @param timeUnit time unit
427 * @param defaultValue default value
428 * @param <T> future value type
429 * @return future value when complete or if future
430 * completes exceptionally returns the defaultValue.
431 */
432 public static <T> T futureGetOrElse(Future<T> future,
433 long timeout,
434 TimeUnit timeUnit,
435 T defaultValue) {
436 try {
437 return future.get(timeout, timeUnit);
438 } catch (InterruptedException e) {
439 Thread.currentThread().interrupt();
440 return defaultValue;
441 } catch (ExecutionException | TimeoutException e) {
442 return defaultValue;
443 }
444 }
445
Madan Jampani27b69c62015-05-15 15:49:02 -0700446 /**
447 * Returns a future that is completed exceptionally.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700448 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700449 * @param t exception
450 * @param <T> future value type
451 * @return future
452 */
453 public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
454 CompletableFuture<T> future = new CompletableFuture<>();
455 future.completeExceptionally(t);
456 return future;
457 }
458
459 /**
460 * Returns the contents of {@code ByteBuffer} as byte array.
461 * <p>
462 * WARNING: There is a performance cost due to array copy
463 * when using this method.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700464 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700465 * @param buffer byte buffer
466 * @return byte array containing the byte buffer contents
467 */
468 public static byte[] byteBuffertoArray(ByteBuffer buffer) {
469 int length = buffer.remaining();
470 if (buffer.hasArray()) {
471 int offset = buffer.arrayOffset() + buffer.position();
472 return Arrays.copyOfRange(buffer.array(), offset, offset + length);
473 }
474 byte[] bytes = new byte[length];
475 buffer.duplicate().get(bytes);
476 return bytes;
477 }
478
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700479 /**
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700480 * Converts an iterable to a stream.
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700481 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700482 * @param it iterable to convert
483 * @param <T> type if item
484 * @return iterable as a stream
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700485 */
486 public static <T> Stream<T> stream(Iterable<T> it) {
487 return StreamSupport.stream(it.spliterator(), false);
488 }
489
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800490 // Auxiliary path visitor for recursive directory structure copying.
491 private static class DirectoryCopier extends SimpleFileVisitor<Path> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800492 private Path src;
493 private Path dst;
494 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
495
496 DirectoryCopier(String src, String dst) {
497 this.src = Paths.get(src);
498 this.dst = Paths.get(dst);
499 }
500
501 @Override
502 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
503 Path targetPath = dst.resolve(src.relativize(dir));
504 if (!Files.exists(targetPath)) {
505 Files.createDirectory(targetPath);
506 }
507 return FileVisitResult.CONTINUE;
508 }
509
510 @Override
511 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
512 Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
513 return FileVisitResult.CONTINUE;
514 }
515 }
516
tom5f38b3a2014-08-27 23:50:54 -0700517}