blob: 0e23dbf2fad1bbaf0af7a8a57440dec0f42e5635 [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;
tom5f38b3a2014-08-27 23:50:54 -070049
Madan Jampani2bfa94c2015-04-11 05:03:49 -070050import org.slf4j.Logger;
51
52import com.google.common.base.Strings;
53import com.google.common.primitives.UnsignedLongs;
54import com.google.common.util.concurrent.ThreadFactoryBuilder;
Ray Milkey705d9bc2014-11-18 08:19:00 -080055
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080056/**
57 * Miscellaneous utility methods.
58 */
tom5f38b3a2014-08-27 23:50:54 -070059public abstract class Tools {
60
61 private Tools() {
62 }
63
Thomas Vachuska02aeb032015-01-06 22:36:30 -080064 private static final Logger log = getLogger(Tools.class);
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080065
Thomas Vachuskaadba1522015-06-04 15:08:30 -070066 private static Random random = new Random();
67
tom5f38b3a2014-08-27 23:50:54 -070068 /**
69 * Returns a thread factory that produces threads named according to the
70 * supplied name pattern.
71 *
72 * @param pattern name pattern
73 * @return thread factory
74 */
75 public static ThreadFactory namedThreads(String pattern) {
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080076 return new ThreadFactoryBuilder()
77 .setNameFormat(pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080078 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
79 .build();
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080080 }
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080081
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080082 /**
83 * Returns a thread factory that produces threads named according to the
84 * supplied name pattern and from the specified thread-group. The thread
85 * group name is expected to be specified in slash-delimited format, e.g.
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080086 * {@code onos/intent}. The thread names will be produced by converting
87 * the thread group name into dash-delimited format and pre-pended to the
88 * specified pattern.
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080089 *
90 * @param groupName group name in slash-delimited format to indicate hierarchy
91 * @param pattern name pattern
92 * @return thread factory
93 */
94 public static ThreadFactory groupedThreads(String groupName, String pattern) {
95 return new ThreadFactoryBuilder()
96 .setThreadFactory(groupedThreadFactory(groupName))
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080097 .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080098 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
99 .build();
tom5f38b3a2014-08-27 23:50:54 -0700100 }
101
tom782a7cf2014-09-11 23:58:38 -0700102 /**
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800103 * Returns a thread factory that produces threads with MIN_PRIORITY.
104 *
105 * @param factory backing ThreadFactory
106 * @return thread factory
107 */
108 public static ThreadFactory minPriority(ThreadFactory factory) {
109 return new ThreadFactoryBuilder()
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800110 .setThreadFactory(factory)
111 .setPriority(Thread.MIN_PRIORITY)
112 .build();
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800113 }
114
115 /**
Brian O'Connore2eac102015-02-12 18:30:22 -0800116 * Returns true if the collection is null or is empty.
117 *
118 * @param collection collection to test
119 * @return true if null or empty; false otherwise
120 */
121 public static boolean isNullOrEmpty(Collection collection) {
122 return collection == null || collection.isEmpty();
123 }
124
125 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700126 * Returns the specified item if that item is not null; otherwise throws
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700127 * not found exception.
128 *
129 * @param item item to check
130 * @param message not found message
131 * @param <T> item type
132 * @return item if not null
133 * @throws org.onlab.util.ItemNotFoundException if item is null
134 */
135 public static <T> T nullIsNotFound(T item, String message) {
136 if (item == null) {
137 throw new ItemNotFoundException(message);
138 }
139 return item;
140 }
141
142 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700143 * Returns the specified item if that item is not null; otherwise throws
144 * bad argument exception.
145 *
146 * @param item item to check
147 * @param message not found message
148 * @param <T> item type
149 * @return item if not null
150 * @throws IllegalArgumentException if item is null
151 */
152 public static <T> T nullIsIllegal(T item, String message) {
153 if (item == null) {
154 throw new IllegalArgumentException(message);
155 }
156 return item;
157 }
158
159 /**
tom782a7cf2014-09-11 23:58:38 -0700160 * Converts a string from hex to long.
161 *
162 * @param string hex number in string form; sans 0x
163 * @return long value
164 */
165 public static long fromHex(String string) {
166 return UnsignedLongs.parseUnsignedLong(string, 16);
167 }
168
169 /**
170 * Converts a long value to hex string; 16 wide and sans 0x.
171 *
172 * @param value long value
173 * @return hex string
174 */
175 public static String toHex(long value) {
176 return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
177 }
178
179 /**
180 * Converts a long value to hex string; 16 wide and sans 0x.
181 *
182 * @param value long value
183 * @param width string width; zero padded
184 * @return hex string
185 */
186 public static String toHex(long value, int width) {
187 return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
188 }
tomf110fff2014-09-26 00:38:18 -0700189
190 /**
Thomas Vachuska6519e6f2015-03-11 02:29:31 -0700191 * Get property as a string value.
192 *
193 * @param properties properties to be looked up
194 * @param propertyName the name of the property to look up
195 * @return value when the propertyName is defined or return null
196 */
197 public static String get(Dictionary<?, ?> properties, String propertyName) {
198 Object v = properties.get(propertyName);
199 String s = (v instanceof String) ? (String) v :
200 v != null ? v.toString() : null;
201 return Strings.isNullOrEmpty(s) ? null : s.trim();
202 }
203
204 /**
tomf110fff2014-09-26 00:38:18 -0700205 * Suspends the current thread for a specified number of millis.
206 *
207 * @param ms number of millis
208 */
209 public static void delay(int ms) {
210 try {
211 Thread.sleep(ms);
212 } catch (InterruptedException e) {
213 throw new RuntimeException("Interrupted", e);
214 }
215 }
216
tom53efab52014-10-07 17:43:48 -0700217 /**
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700218 * Suspends the current thread for a random number of millis between 0 and
219 * the indicated limit.
220 *
221 * @param ms max number of millis
222 */
223 public static void randomDelay(int ms) {
224 try {
225 Thread.sleep(random.nextInt(ms));
226 } catch (InterruptedException e) {
227 throw new RuntimeException("Interrupted", e);
228 }
229 }
230
231 /**
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700232 * Suspends the current thread for a specified number of millis and nanos.
233 *
234 * @param ms number of millis
235 * @param nanos number of nanos
236 */
237 public static void delay(int ms, int nanos) {
238 try {
239 Thread.sleep(ms, nanos);
240 } catch (InterruptedException e) {
241 throw new RuntimeException("Interrupted", e);
242 }
243 }
244
245 /**
tom53efab52014-10-07 17:43:48 -0700246 * Slurps the contents of a file into a list of strings, one per line.
247 *
248 * @param path file path
249 * @return file contents
250 */
251 public static List<String> slurp(File path) {
Ray Milkey705d9bc2014-11-18 08:19:00 -0800252 try {
253 BufferedReader br = new BufferedReader(
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800254 new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8));
Ray Milkey705d9bc2014-11-18 08:19:00 -0800255
tom53efab52014-10-07 17:43:48 -0700256 List<String> lines = new ArrayList<>();
257 String line;
258 while ((line = br.readLine()) != null) {
259 lines.add(line);
260 }
261 return lines;
262
263 } catch (IOException e) {
264 return null;
265 }
266 }
267
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800268 /**
269 * Purges the specified directory path.&nbsp;Use with great caution since
270 * no attempt is made to check for symbolic links, which could result in
271 * deletion of unintended files.
272 *
273 * @param path directory to be removed
274 * @throws java.io.IOException if unable to remove contents
275 */
276 public static void removeDirectory(String path) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800277 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700278 File dir = new File(path);
279 if (dir.exists() && dir.isDirectory()) {
280 walkFileTree(Paths.get(path), visitor);
281 if (visitor.exception != null) {
282 throw visitor.exception;
283 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800284 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800285 }
286
287 /**
288 * Purges the specified directory path.&nbsp;Use with great caution since
289 * no attempt is made to check for symbolic links, which could result in
290 * deletion of unintended files.
291 *
292 * @param dir directory to be removed
293 * @throws java.io.IOException if unable to remove contents
294 */
295 public static void removeDirectory(File dir) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800296 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700297 if (dir.exists() && dir.isDirectory()) {
298 walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
299 if (visitor.exception != null) {
300 throw visitor.exception;
301 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800302 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800303 }
304
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800305 // Auxiliary path visitor for recursive directory structure removal.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800306 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800307
308 private IOException exception;
309
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800310 @Override
311 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
312 throws IOException {
313 if (attributes.isRegularFile()) {
314 delete(file);
315 }
316 return FileVisitResult.CONTINUE;
317 }
318
319 @Override
320 public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
321 throws IOException {
322 delete(directory);
323 return FileVisitResult.CONTINUE;
324 }
325
326 @Override
327 public FileVisitResult visitFileFailed(Path file, IOException ioe)
328 throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800329 this.exception = ioe;
330 return FileVisitResult.TERMINATE;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800331 }
332 }
333
Madan Jampani30a57f82015-03-02 12:19:41 -0800334 /**
335 * Returns a human friendly time ago string for a specified system time.
Thomas Vachuska480adad2015-03-06 10:27:09 -0800336 *
Madan Jampani30a57f82015-03-02 12:19:41 -0800337 * @param unixTime system time in millis
338 * @return human friendly time ago
339 */
340 public static String timeAgo(long unixTime) {
341 long deltaMillis = System.currentTimeMillis() - unixTime;
342 long secondsSince = (long) (deltaMillis / 1000.0);
343 long minsSince = (long) (deltaMillis / (1000.0 * 60));
344 long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
345 long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
346 if (daysSince > 0) {
347 return String.format("%dd ago", daysSince);
348 } else if (hoursSince > 0) {
349 return String.format("%dh ago", hoursSince);
350 } else if (minsSince > 0) {
351 return String.format("%dm ago", minsSince);
352 } else if (secondsSince > 0) {
353 return String.format("%ds ago", secondsSince);
354 } else {
355 return "just now";
356 }
357 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800358
359 /**
360 * Copies the specified directory path.&nbsp;Use with great caution since
361 * no attempt is made to check for symbolic links, which could result in
362 * copy of unintended files.
363 *
364 * @param src directory to be copied
365 * @param dst destination directory to be removed
366 * @throws java.io.IOException if unable to remove contents
367 */
368 public static void copyDirectory(String src, String dst) throws IOException {
369 walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
370 }
371
372 /**
373 * Copies the specified directory path.&nbsp;Use with great caution since
374 * no attempt is made to check for symbolic links, which could result in
375 * copy of unintended files.
376 *
377 * @param src directory to be copied
378 * @param dst destination directory to be removed
379 * @throws java.io.IOException if unable to remove contents
380 */
381 public static void copyDirectory(File src, File dst) throws IOException {
382 walkFileTree(Paths.get(src.getAbsolutePath()),
383 new DirectoryCopier(src.getAbsolutePath(),
384 dst.getAbsolutePath()));
385 }
386
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700387 /**
388 * Returns the future value when complete or if future
389 * completes exceptionally returns the defaultValue.
390 * @param future future
391 * @param defaultValue default value
392 * @param <T> future value type
393 * @return future value when complete or if future
394 * completes exceptionally returns the defaultValue.
395 */
396 public static <T> T futureGetOrElse(Future<T> future, T defaultValue) {
397 try {
398 return future.get();
399 } catch (InterruptedException e) {
400 Thread.currentThread().interrupt();
401 return defaultValue;
402 } catch (ExecutionException e) {
403 return defaultValue;
404 }
405 }
406
407 /**
408 * Returns the future value when complete or if future
409 * completes exceptionally returns the defaultValue.
410 * @param future future
411 * @param timeout time to wait for successful completion
412 * @param timeUnit time unit
413 * @param defaultValue default value
414 * @param <T> future value type
415 * @return future value when complete or if future
416 * completes exceptionally returns the defaultValue.
417 */
418 public static <T> T futureGetOrElse(Future<T> future,
419 long timeout,
420 TimeUnit timeUnit,
421 T defaultValue) {
422 try {
423 return future.get(timeout, timeUnit);
424 } catch (InterruptedException e) {
425 Thread.currentThread().interrupt();
426 return defaultValue;
427 } catch (ExecutionException | TimeoutException e) {
428 return defaultValue;
429 }
430 }
431
Madan Jampani27b69c62015-05-15 15:49:02 -0700432 /**
433 * Returns a future that is completed exceptionally.
434 * @param t exception
435 * @param <T> future value type
436 * @return future
437 */
438 public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
439 CompletableFuture<T> future = new CompletableFuture<>();
440 future.completeExceptionally(t);
441 return future;
442 }
443
444 /**
445 * Returns the contents of {@code ByteBuffer} as byte array.
446 * <p>
447 * WARNING: There is a performance cost due to array copy
448 * when using this method.
449 * @param buffer byte buffer
450 * @return byte array containing the byte buffer contents
451 */
452 public static byte[] byteBuffertoArray(ByteBuffer buffer) {
453 int length = buffer.remaining();
454 if (buffer.hasArray()) {
455 int offset = buffer.arrayOffset() + buffer.position();
456 return Arrays.copyOfRange(buffer.array(), offset, offset + length);
457 }
458 byte[] bytes = new byte[length];
459 buffer.duplicate().get(bytes);
460 return bytes;
461 }
462
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800463 // Auxiliary path visitor for recursive directory structure copying.
464 private static class DirectoryCopier extends SimpleFileVisitor<Path> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800465 private Path src;
466 private Path dst;
467 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
468
469 DirectoryCopier(String src, String dst) {
470 this.src = Paths.get(src);
471 this.dst = Paths.get(dst);
472 }
473
474 @Override
475 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
476 Path targetPath = dst.resolve(src.relativize(dir));
477 if (!Files.exists(targetPath)) {
478 Files.createDirectory(targetPath);
479 }
480 return FileVisitResult.CONTINUE;
481 }
482
483 @Override
484 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
485 Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
486 return FileVisitResult.CONTINUE;
487 }
488 }
489
tom5f38b3a2014-08-27 23:50:54 -0700490}