content: package javarepl;
  
  import com.googlecode.totallylazy.Pair;
  import com.googlecode.totallylazy.Sequence;
  import com.googlecode.totallylazy.Sequences;
  import com.googlecode.totallylazy.functions.Function1;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Type;
  import java.net.ServerSocket;
  import java.net.URI;
  import java.net.URL;
  import java.util.function.Consumer;
  import java.util.jar.JarFile;
  import java.util.jar.JarInputStream;
  import java.util.jar.Manifest;
  import java.util.zip.ZipEntry;
  
  import static com.googlecode.totallylazy.Files.*;
  import static com.googlecode.totallylazy.Randoms.takeFromValues;
  import static com.googlecode.totallylazy.Sequences.*;
  import static com.googlecode.totallylazy.Strings.replace;
  import static com.googlecode.totallylazy.io.URLs.url;
  import static com.googlecode.totallylazy.predicates.Predicates.is;
  import static com.googlecode.totallylazy.predicates.Predicates.where;
  import static java.lang.String.format;
  import static java.lang.reflect.Modifier.isPublic;
  import static java.net.URLDecoder.decode;
  
  public class Utils {
      public static Boolean javaVersionAtLeast(String version) {
          return (System.getProperty("java.version").compareTo(version) >= 0);
      }
  
      public static String randomIdentifier(String prefix) {
          return prefix + "$" + takeFromValues(characters("abcdefghijklmnopqrstuvwxyz1234567890")).take(20).toString("");
      }
  
      public static Type extractType(Type type) {
          if (type instanceof Class<?>) {
              Class<?> clazz = (Class<?>) type;
              if (clazz.isAnonymousClass() || clazz.isSynthetic() || clazz.isMemberClass()) {
                  if (clazz.getGenericSuperclass().equals(Object.class)) {
                      return extractType(sequence(clazz.getGenericInterfaces())
                              .headOption()
                              .getOrElse(Object.class));
                  } else {
                      return extractType(clazz.getGenericSuperclass());
                  }
              }
  
              if (!isPublic(clazz.getModifiers()))
                  return extractType(clazz.getGenericSuperclass());
  
              return clazz;
          }
  
          return type;
      }
  
      public static Throwable unwrapException(Throwable e) {
          if (e instanceof InvocationTargetException)
              return unwrapException(((InvocationTargetException) e).getTargetException());
  
          return e;
      }
  
      public static URL resolveURL(String path) {
          try {
              return url(path);
          } catch (Exception e) {
              return url("file:" + path);
          }
      }
  
      public static boolean isWebUrl(URL classpathUrl) {
          return sequence("http", "https").contains(classpathUrl.getProtocol());
      }
  
      public static String applicationVersion() {
  
          try {
              File path = new File(decode(Main.class.getProtectionDomain().getCodeSource().getLocation().getPath(), "ISO-8859-1"));
              if (!path.isDirectory()) {
                  JarInputStream jarStream = new JarInputStream(new FileInputStream(path));
                  Manifest manifest = jarStream.getManifest();
                  return manifest.getMainAttributes().getValue("Implementation-Version");
              }
          } catch (Exception e) {
              // ignore
          }
  
          return "[unknown]";
      }
  
      public static <T> Sequence<Sequence<T>> permutations(Sequence<T> items) {
          return powerSetPermutations(items).filter(where(Sequence::size, is(items.size())));
      }
  
      public static <T> Sequence<Sequence<T>> powerSetPermutations(Sequence<T> items) {
          return cartesianProductPower(items, items.size()).append(Sequences.<T>empty());
      }
  
      private static <T> Sequence<Sequence<T>> cartesianProductPower(Sequence<T> items, int times) {
          if (times == 0)
              return items.cartesianProduct().map(Pair.functions.values()).unsafeCast();
  
          return cartesianProductPower(items, times - 1)
                  .cartesianProduct(items)
                  .map(pair -> pair.first().append(pair.second()).unique())
                  .unique();
      }
  
      public static Function1<Class<?>, String> canonicalName() {
          return Class::getCanonicalName;
      }
  
      public static String listValues(String name, Sequence<?> list) {
          return format(name + ":\n    %s\n", list.toString("\n").replaceAll("\n", "\n    "));
      }
  
      public static int randomServerPort() {
          try {
              ServerSocket serverSocket = new ServerSocket(0);
              Integer serverPort = serverSocket.getLocalPort();
              serverSocket.close();
              return serverPort;
          } catch (Exception e) {
              throw new RuntimeException(e);
          }
      }
  
      public static File randomOutputDirectory() {
          File file = temporaryDirectory("JavaREPL/" + randomFilename());
          file.deleteOnExit();
          return file;
      }
  
  
      public static JarFile jarFile(URI path) {
          try {
              return new JarFile(new File(path));
          } catch (IOException e) {
              throw new RuntimeException("Couldn't create jar file for path " + path, e);
          }
      }
  
      public static Function1<URL, String> urlAsFilePath() {
          return url -> new File(url.getFile()).getPath();
      }
  
      public static Sequence<String> entries(File file) {
          if (file.isDirectory()) {
              return recursiveFiles(file)
                      .map(path().then(replace(file.getPath() + File.separator, "")));
          } else {
              try {
                  return memorise(new JarFile(new File(file.toURI())).entries())
                          .map(ZipEntry::getName);
              } catch (Exception e) {
                  System.err.println("Couldn't load entries from jar " + file.toURI() + ". " + e.getLocalizedMessage());
                  return empty();
              }
          }
      }
  
      public static Consumer<Throwable> throwException() {
          return throwable -> {
              throw new RuntimeException(throwable);
          };
      }
  }
  