/*
* Copyright 2015 Julien Viet
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.termd.core.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
* Various utils.
*
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
public class Helper {
public static void uncheckedThrow(Throwable throwable) {
Helper.<RuntimeException>throwIt(throwable);
}
private static <T extends Throwable> void throwIt(Throwable throwable) throws T {
throw (T)throwable;
}
/**
* Do absolutely nothing. This can be useful for code coverage analysis.
*/
public static void noop() {}
/**
* Convert the string to an array of code points.
*
* @param s the string to convert
* @return the code points
*/
public static int[] toCodePoints(String s) {
return s.codePoints().toArray();
}
/**
* Code point to string conversion.
*
* @param codePoints the code points
* @return the corresponding string
*/
public static String fromCodePoints(int[] codePoints) {
return new String(codePoints, 0, codePoints.length);
}
public static void appendCodePoints(int[] codePoints, StringBuilder sb) {
consumeTo(codePoints, sb::appendCodePoint);
}
public static void consumeTo(int[] i, IntConsumer consumer) {
for (int codePoint : i) {
consumer.accept(codePoint);
}
}
public static <S> List<S> loadServices(ClassLoader loader, Class<S> serviceClass) {
ArrayList<S> services = new ArrayList<>();
Iterator<S> i = ServiceLoader.load(serviceClass, loader).iterator();
while (i.hasNext()) {
try {
S service = i.next();
services.add(service);
} catch (Exception ignore) {
// Log me
}
}
return services;
}
public static List<Integer> list(int... list) {
ArrayList<Integer> result = new ArrayList<>(list.length);
for (int i : list) {
result.add(i);
}
return result;
}
public static List<String> split(String s, char c) {
List<String> ret = new ArrayList<>();
int prev = 0;
while (true) {
int pos = s.indexOf('\n', prev);
if (pos == -1) {
break;
}
ret.add(s.substring(prev, pos));
prev = pos + 1;
}
ret.add(s.substring(prev));
return ret;
}
/**
* Escape a string to be printable in a terminal: any non printable char is replaced by its
* octal escape and the {@code \} char is replaced by the @{code \\} sequence.
*
* @param s the string to escape
* @return the escaped string
*/
public static String escape(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0;i < s.length();i++) {
char c = s.charAt(i);
if (c == 0) {
sb.append("\\0");
} else if (c < 32) {
sb.append("\\");
String octal = Integer.toOctalString(c);
for (int j = octal.length();j < 3;j++) {
sb.append('0');
}
sb.append(octal);
} else if (c == '\\') {
sb.append("\\\\");
} else {
sb.append(c);
}
}
return sb.toString();
}
public static int[] findLongestCommonPrefix(List<int[]> entries) {
if (entries.isEmpty()) {
return new int[0];
}
int minLen = entries.stream().mapToInt(entry -> entry.length).min().getAsInt();
int len = 0;
out:
while (len < minLen) {
for (int j = 1;j < entries.size();j++) {
if (entries.get(j)[len] != entries.get(j - 1)[len]) {
break out;
}
}
len++;
}
return Arrays.copyOf(entries.get(0), len);
}
public static int[] computeBlock(Vector size, List<int[]> completions) {
if (completions.size() == 0) {
return new int[0];
}
int max = completions.stream().mapToInt(comp -> comp.length).max().getAsInt();
int row = size.x() / (max + 1);
int count = 0;
StringBuilder sb = new StringBuilder();
for (int[] completion : completions) {
Helper.appendCodePoints(completion, sb);
for (int i = completion.length;i < max;i++) {
sb.append(' ');
}
if (count++ < row) {
sb.append(' ');
} else {
sb.append('\n');
count = 0;
}
}
sb.append("\n");
return Helper.toCodePoints(sb.toString());
}
/**
* Compute the position of the char at the specified {@literal offset} of the {@literal codePoints} given a
* {@literal width} and a relative {@literal origin} position.
*
* @param origin the relative position to start from
* @param width the screen width
* @return the height
*/
public static Vector computePosition(int[] codePoints, Vector origin, int offset, int width) {
if (offset < 0) {
throw new IndexOutOfBoundsException("Offset cannot be negative");
}
if (offset > codePoints.length) {
throw new IndexOutOfBoundsException("Offset cannot bebe greater than the length");
}
int col = origin.x();
int row = origin.y();
for (int i = 0;i < offset;i++) {
int cp = codePoints[i];
int w = Wcwidth.of(cp);
if (w == -1) {
if (cp == '\r') {
col = 0;
} else if (cp == '\n') {
col = 0;
row++;
}
} else {
if (col + w > width) {
if (w > width) {
throw new UnsupportedOperationException("Handle this case gracefully");
}
col = 0;
row++;
}
col += w;
if (col >= width) {
col -= width;
row++;
}
}
}
return new Vector(col, row);
}
public static Consumer<Throwable> startedHandler(CompletableFuture<?> fut) {
return err -> {
if (err == null) {
fut.complete(null);
} else {
fut.completeExceptionally(err);
}
};
}
public static Consumer<Throwable> stoppedHandler(CompletableFuture<?> fut) {
return err -> {
fut.complete(null);
};
}
}