Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

2908
LINES

< > BotCompany Repo | #1001638 - x30.java (JavaX)

JavaX module (desktop) [tags: use-pretranspiled]

Libraryless. Click here for Pure Java version (25484L/151K).

!7

set flag AllPublic.
set flag InCore.

should not include function cget.
do not include function has.
do not include function list.
should not include class RemoteDB.
should not include class Concepts.

/**
 JavaX runner version 30

 Changes to v29:
 -added integrated MultiPort
 -useBossBot = false
 -Beginning support for offline mode (transpiled programs only so far)
 -faster console update (100ms)
 -weakref
 -made first program an injection
 -manage multi-ports
 -multitry for loadPage (partially implemented)
 -list injections
 -additional "get output" questions
 -send line * to system.in
 -multi-port 1 accessible directly through VM android
 -getInjectionID (from main.class)
 -allow programs without main method
 -increase buffer size for PipedInputStream (console output/input)
 -registerSourceCode + getSourceCodeForClass
 -autoScroll
 -retaining class files for 30 days (restart JavaX programs after 30 days!!)
 -close confirmation for console (no running apps without console!)
 -utf8 fixed in transpilations!
 -maxConsoleChars
 -pause button
 -regular GC every 60s
 -GC after main ran
 -and more
 
 TODO when making x31: change program ID in source
 */

m { p { throw new RuntimeException("placebo"); } }

interface StringFunc {
  String get(String s);
}

/*interface DynamicMethods {
  O _dynCall(S methodName, O[] args);
}*/

class x30 implements Runnable {
  static final String version = "JavaX 30";
  static final int subversion = 2;
  static S dateChanged = "2018/05/23";
  static final S javaxProgramID = "#1001638";
  
  // If programs run longer than this, they might have their class files
  // deleted. Should set to 30 for servers. Working on real solution.
  static int tempFileRetentionTime = 2*24; // hours
  
  static int maxConsoleChars = 1024*1024;
  static int consoleUpdateDelay = 50; // ms
  static int pipeDelay = 50;
  static bool focusConsoleInputOnActivate = false;
  sS donePrefix = "."; // "[Done] ";
  static bool consoleGrabFocus;
  
  static int consoleWidth = 550, consoleHeight = 200;
  static int consoleXGap = 10, consoleYGap = 10;
  
  static bool gcAfterMainDone = true, registerSourceCodes = false;
  static bool verbose = false, translate = false, list = false, virtualizeTranslators = true;
  sbool dontCompileThroughPort5000;
  static S translateTo = null;
  static bool noID = false, noPrefetch = false, noAWT = false;
  static bool safeOnly = false, safeTranslate = false, javacOnly = false, logOn = true, noCleanCache;
  static volatile bool cleanCacheDone;
  static bool runMainInProcess = true, consoleOn = true, hasHelloMessage = false;
  static L<S[]> mainTranslators = new ArrayList<S[]>();
  private static Map<Long, String> memSnippetCache = new HashMap<Long, String>();
  private static int processesStarted, compilations;

  // snippet ID -> md5
  private static HashMap<Long, String> prefetched = new HashMap<Long, String>();
  private static File virtCache;
  
  // doesn't work yet
  private static Map<String, Class<?>> programCache = new HashMap<String, Class<?>>();
  static boolean cacheTranslators = false;

  // this should work (caches transpiled translators)
  private static HashMap<Long, Object[]> translationCache = new HashMap<Long, Object[]>();
  static boolean cacheTranspiledTranslators = true;

  // which snippets are available pre-transpiled server-side?
  private static Set<Long> hasTranspiledSet = new HashSet<Long>();
  static boolean useServerTranspiled = true;
  
  static boolean useBossBot = false;

  static Object androidContext;
  static boolean android = isAndroid();
  
  // We used to stick to 1.7 to support Android.
  // Now the default is 1.8.
  static S javaTarget =
      System.getProperty("java.version").startsWith("1.6.") ? "1.6" 
    : "1.8";

  // Translators currently being translated (to detect recursions)
  private static Set<Long> translating = new HashSet<Long>();

  static String lastOutput;
  static String[] fullArgs;
  private static Console console;
  
  static String javaCompilerOutput;
  
  static int systemOutPipeSize = 128*1024; // 128 K
  static int systemErrPipeSize = 4*1024; // 4 K
  static int systemInPipeSize = 4*1024; // 4 K
  
  static S caseID;
  static volatile bool hiddenVM;
  static long vmStarted = now();
  static long vmStarted_sysTime = sysNow();
  
  sclass VMKeep {
    S programMD5;
    new HashMap<S, S> vars; // var name -> structure
  }
  
  static new HashMap<S, VMKeep> vmKeep;
  static Lock vmKeep_lock = lock();
  
  // store anything here
  static Map generalMap = synchroHashMap();
  
  public static void main(String[] args) {
    try {
      goMain(args);
    } catch (Throwable e) {
      printStackTrace(e);
    }
  }
  
  // rewrite command line args if we're called from browser
  // e.g. "javax:123"
  static S[] rewriteArgsForURLHandler(S[] args) {
    //print("Args: " + sfu(args));
    if (l(args) == 1 && args[0].startsWith("javax:")) {
      S a = args[0];
      //print("Rewriting argument: " + a);
      a = dropPrefix("javax:", a);
      a = trim(dropPrefix("//", a));
      a = dropSuffix("/", a);
      ret asStringArray(splitAtSpace(a)); // TODO: handle quotes
    }
    ret args;
  }
  
  sbool inited;
  
  svoid initMe {
    if (inited) ret;
    inited = true;
    
    loadAllClasses();
    
    __javax = x30.class; // for hotwire
    // init Global Util's reference to me
    Class x30_util = classForName("x30_pkg.x30_util");
    setOpt(x30_util, "__javax", x30.class);
    
    callOnLoadMethods(x30_util);
    
    regularGC();
    computerID(); // Generate!
  }
  
  svoid initHelloMessage {
    if (!hasHelloMessage) {
      hasHelloMessage = true;
      //installHelloMessage(args.length == 0 ? "JavaX Start-Up VM" : "JavaX VM (" + smartJoin(args) + ")");
      makeVMAndroid();
    }
  }
  
  static void goMain(String[] args) throws Exception {
    initMe();
    
    //appendToMechQ_withDate("x30 start log on computer " + computerID(), quote(first(args)));
    
    args = rewriteArgsForURLHandler(args);
    
    if (args.length != 0 && args[0].equals("-v")) verbose = true;
    redirectSystemOutAndErr();

    for (S arg : args)
      if (arg.equals("-noawt"))
        noAWT = true;
        
    if (consoleOn && console == null && !noAWT)
      tryToOpenConsole(args);
    
    String autoReport = loadTextFile(new File(userHome(), ".javax/auto-report-to-chat").getPath(), "").trim();
    //print("autoReport=" + autoReport);
    if (!isChatServer(args) && autoReport.equals("1"))
      autoReportToChat();

    initHelloMessage();
    
    File ioBaseDir = new File("."), inputDir = null, outputDir = null;
    String src = null;
    List<String> programArgs = new ArrayList<String>();
    fullArgs = args;

    for (int i = 0; i < args.length; i++) {
      String arg = args[i];
      
      // --quiet to -quiet
      arg = regexpReplace_direct(arg, "^--(?=\\w)", "-");

      if (arg.equals("-version")) {
        showVersion();
        System.exit(0);
      }

      if (arg.equals("-sysprop")) {
        showSystemProperties();
        return;
      }
      
      if (arg.equals("-v") || arg.equals("-verbose"))
        verbose = true;
      else if (arg.equals("-nocerts"))
        disableCertificateValidation();
      else if (arg.equals("-finderror"))
        verbose = true;
      else if (arg.equals("-offline") || arg.equalsIgnoreCase("-prefercached"))
        preferCached = true;
      else if (arg.equals("-novirt"))
        virtualizeTranslators = false;
      else if (arg.equals("-safeonly"))
        safeOnly = true;
      else if (arg.equals("-safetranslate"))
        safeTranslate = true;
      else if (arg.equals("-noawt"))
        noAWT = true;
      else if (arg.equals("-quiet"))
        quietLoading = true;
      else if (arg.equals("-verbose-illegal"))
        verboseIllegal = true;
      else if (arg.equals("-case"))
        caseID = args[++i];
      else if (arg.equals("-noid"))
        noID = true;
      else if (arg.equals("-nocachetranspiled"))
        cacheTranspiledTranslators = false;
      else if (arg.equals("-javac"))
        javacOnly = true;
      else if (arg.equals("-nocleancache"))
        noCleanCache = true;
      else if (arg.equals("-localtranspile"))
        useServerTranspiled = false;
      else if (arg.equals("translate") && src == null)
        translate = true;
      else if (arg.equals("eval") && src == null)
        src = #1006008;
      else if (arg.equals("list") && src == null) {
        list = true;
        virtualizeTranslators = false; // so they are silenced
      } else if (arg.equals("run") && src == null) {
        // it's the default command anyway
      } else if (arg.startsWith("input="))
        inputDir = new File(arg.substring(6));
      else if (arg.startsWith("output="))
        outputDir = new File(arg.substring(7));
      else if (arg.equals("with"))
        mainTranslators.add(new String[] {args[++i], null});
      else if (translate && arg.equals("to"))
        translateTo = args[++i];
      else if (eqic(arg, "-dontcompilethroughport5000"))
        dontCompileThroughPort5000 = true;

      // add more options here

      else if (src == null) {
        //System.out.println("src=" + arg);
        src = arg;
      } else
        programArgs.add(arg);
    }

    if (!noCleanCache) thread "Clean Cache" { cleanCache(); }

    if (useServerTranspiled)
      noPrefetch = true;

    if (src == null && fileExists("main.java")) {
      src = ".";
      print("WARNING: Runnning " + absolutePath("main.java") + " - this may not be what you want. Change to another directory to resume normal operation.");
    }

    // Might actually want to write to 2 disk caches (global/per program).
    if (virtualizeTranslators && !preferCached)
      virtCache = TempDirMaker_make();

    if (inputDir != null) {
      ioBaseDir = TempDirMaker_make();
      System.out.println("Taking input from: " + inputDir.getAbsolutePath());
      System.out.println("Output is in: " + new File(ioBaseDir, "output").getAbsolutePath());
      copyInput(inputDir, new File(ioBaseDir, "input"));
    }
    
    if (logOn)
      logStart(args);
      
    temp quietLoading ? temp_loadPage_silent() : null;
    if (verboseIllegal) {
      print("verboseIllegal ON");
      vmBus_onMessage makeAccessible_error(voidfunc(Throwable e, O o) {
        System.out.print(e);
      });
    }

    javaxmain(src, ioBaseDir, translate, list, programArgs.toArray(new String[programArgs.size()]));

    if (outputDir != null) {
      copyInput(new File(ioBaseDir, "output"), outputDir);
      System.out.println("Output copied to: " + outputDir.getAbsolutePath());
    }

    if (verbose) {
      // print stats
      print("Processes started: " + processesStarted + ", compilations: " + compilations);
      print("Timers: " + l(_registeredTimers()));
    }
  }

  public static void javaxmain(String src, File ioDir, boolean translate, boolean list,
                               String[] args) throws Exception {
    String programID = isSnippetID(src) ? "" + parseSnippetID(src) : null;
    
    if (programID != null && !quietLoading)
      System.out.println(trim("JavaX TRANSLATE " + programID + " " + smartJoin(args)) + " / " + vmPort());
    
    List<File> libraries = new ArrayList<File>();
    new LS libraries2;
    File x = transpileMain(src, libraries, libraries2);
    if (verbose)
      print("After transpileMain: " + x);
      
    if (x == null) {
      showVersion();
      
      // DEFAULT PROGRAM TO RUN
      
      if (fullArgs != null) {
        String[] nargs;
        if (fullArgs.length == 0) {
          nargs = new String[] {str(psI(javaxDefaultProgram()))};
          loadPage_retries = 60*60; // try to get internet for an hour instead of a minute, lol
        } else {
          // forward to search
          nargs = new String[fullArgs.length+1];
          nargs[0] = str(psI(#636));
          // nargs[1] = "search-runnables";
          System.arraycopy(fullArgs, 0, nargs, 1, fullArgs.length);
        }
        if (console != null)
          console.args = nargs;
        main(nargs); // Hopefully we get no infinite recursion :)
        return;
      }
      
      System.out.println("No main.java found, exiting");
      return;
    }
    
    info.transpiledSrc = x;

    // list or run

    if (translate) {
      File to = x;
      if (translateTo != null) {
        StringBuilder buf = new StringBuilder();
        // XXX for (File f : libraries) buf.append(f.getName() + "\n");
        for (S s : libraries2) buf.append("data_" + s + ".jar\n");
        if (new File(translateTo).isDirectory()) {
          to = new File(translateTo, "main.java");
          saveTextFile(new File(translateTo, "libraries.txt").getPath(), buf.toString());
        } else {
          to = new File(translateTo);
          saveTextFile(new File(translateTo + "_libraries").getPath(), buf.toString());
        }
      }
      if (to != x)
        copy(new File(x, "main.java"), to);
      System.out.println("Program translated to: " + to.getAbsolutePath());
    } else if (list)
      System.out.println(loadTextFile(new File(x, "main.java").getPath(), null));
    else {
      if (!quietLoading) {
        if (programID != null)
          System.out.println("JavaX RUN " + programID + " " + smartJoin(args));
        System.out.println(); // Make empty line before actual program starts
      }
      
      PaA paa = javax2(x, ioDir, false, runMainInProcess, libraries, args, null, programID, info);
      
      final bool error = paa != null && paa.exception != null;
      if (error ||
        !(quietLoading || !isTrue(getOpt(paa.mainClass, 'mainDoneQuietly_on))))
        System.out.println("\n" + (error ? "[main done with error]" : "[main done]"));
      if (console != null) awt {
        if (eq(console.frame.getTitle(), console.title + " [Output]") && autoVMExit_visibleObjects() <= 1)
          console.frame.setTitle((error ? "[Error] " : donePrefix) + console.title);
      }
      
      // cleanup reportToChat thread
      if (reportToChat_q != null) {
        if (customSystemOut != null)
          Thread.sleep(1000); // delay to finish autoReportToChat. Yes it's hacky.
        if (verbose) System.out.println("Closing reportToChat queue");
        reportToChat_q.done();
      }
      
      if (gcAfterMainDone) gc();
    }
  }

  // called by clients somewhere
  static File transpileMain(String src, List<File> libraries) throws Exception {
    ret transpileMain(src, libraries, new L);
  }
  
  static File transpileMain(String src, List<File> libraries, LS libraries2) throws Exception {
    File srcDir = null;
    boolean isTranspiled = false;
    if (isSnippetID(src)) {
      S transpiledSrc = useBossBot ? getTranspilationFromBossBot(parseSnippetID(src)) : null;
      if (transpiledSrc != null) {
        int i = transpiledSrc.indexOf('\n');
        String libs = transpiledSrc.substring(0, Math.max(0, i));
        //print("libs for " + src + ": " + libs);
        transpiledSrc = transpiledSrc.substring(i+1);
        if (!transpiledSrc.isEmpty()) {
          srcDir = TempDirMaker_make();
          saveTextFile(new File(srcDir, "main.java").getPath(), transpiledSrc);
          isTranspiled = true;

          Matcher m = Pattern.compile("\\d+").matcher(libs);
          while (m.find()) {
            String libid = m.group();
            File libraryFile = DiskSnippetCache_getLibrary(parseSnippetID(libid));
            loadLib(libid, libraries, libraries2, libraryFile);
          }
        }
      }
     
      if (srcDir == null) {
        prefetch(src);
        long id = parseSnippetID(src);
        prefetched.remove(id); // hackfix to ensure transpiled main program is found.
        bool offline = isOfflineMode();
        srcDir = offline ? null : loadSnippetAsMainJava(src);
        if (verbose)
          System.err.println("hasTranspiledSet: " + hasTranspiledSet);
        if (hasTranspiledSet.contains(id) && useServerTranspiled || offline) {
          //System.err.println("Trying pretranspiled main program: #" + id);
          transpiledSrc = getServerTranspiled2("#" + id);
          int i = transpiledSrc.indexOf('\n');
          String libs = transpiledSrc.substring(0, Math.max(0, i));
          //print("libs for " + src + ": " + libs);
          transpiledSrc = transpiledSrc.substring(i+1);
          if (!transpiledSrc.isEmpty()) {
            srcDir = TempDirMaker_make();
            saveTextFile(new File(srcDir, "main.java").getPath(), transpiledSrc);
            isTranspiled = true;
            //translationCache.put(id, new Object[] {srcDir, libraries});
  
            Matcher m = Pattern.compile("\\d+").matcher(libs);
            while (m.find()) {
              String libid = m.group();
              File libraryFile = DiskSnippetCache_getLibrary(parseSnippetID(libid));
              loadLib(libid, libraries, libraries2, libraryFile);
            }
          }
        }
      }
    } else {
      if (src == null) null;
      srcDir = new File(src);

      // if the argument is a file, it is assumed to be main.java
      if (srcDir.isFile()) {
        srcDir = TempDirMaker_make();
        copy(new File(src), new File(srcDir, "main.java"));
      }

      if (!new File(srcDir, "main.java").exists())
        return null;
    }

    // translate

    File x = srcDir;

    if (!isTranspiled) {
      temp tempSetThreadLocal(transpilingSnippetID, isSnippetID(src) ? fsI(src) : null);
      x = topLevelTranslate(x, libraries, libraries2);
      System.err.println("Translated " + src);

      // save prefetch data
      if (isSnippetID(src))
        savePrefetchData(src);
    }
    return x;
  }
  
  static new ThreadLocal<S> transpilingSnippetID;

  private static void prefetch(String mainSnippetID) throws IOException {
    if (noPrefetch) return;

    long mainID = parseSnippetID(mainSnippetID);
    String s = mainID + " " + loadTextFile(javaxCachesDir("prefetch/" + mainID + ".txt").getPath(), "");
    String[] ids = s.trim().split(" ");
    if (ids.length > 1) {
      String url = tb_mainServer() + "/tb-int/prefetch.php?ids=" + URLEncoder.encode(s, "UTF-8") + standardCredentials();
      String data = loadPage(new URL(url));
      String[] split = data.split(" ");
      if (split.length == ids.length)
        for (int i = 0; i < ids.length; i++)
          prefetched.put(parseSnippetID(ids[i]), split[i]);
    }
  }

  static String userHome() {
    if (android)
      return ((File) call(androidContext, "getFilesDir")).getAbsolutePath();
    else
      return System.getProperty("user.home");
  }

  private static void savePrefetchData(String mainSnippetID) throws IOException {
    List<String> ids = new ArrayList<String>();
    long mainID = parseSnippetID(mainSnippetID);

    for (long id : memSnippetCache.keySet())
      if (id != mainID)
        ids.add(String.valueOf(id));

    saveTextFile(javaxCachesDir("prefetch/" + mainID + ".txt").getPath(), join(" ", ids));
  }
  
  // libraries_out = normal libs
  // libraries2_out = source libs

  static File topLevelTranslate(File srcDir, List<File> libraries_out, LS libraries2_out) throws Exception {
    File x = srcDir;
    x = applyTranslators(x, mainTranslators, libraries_out, libraries2_out); // translators supplied on command line (unusual)

    // actual inner translation of the JavaX source
    x = defaultTranslate(x, libraries_out, libraries2_out);
    return x;
  }

  private static File defaultTranslate(File x, List<File> libraries_out, LS libraries2_out) throws Exception {
    x = luaPrintToJavaPrint(x);
    x = repeatAutoTranslate(x, libraries_out, libraries2_out);
    return x;
  }

  private static File repeatAutoTranslate(File x, List<File> libraries_out, LS libraries2_out) throws Exception {
    List<String[]> postTranslators = new ArrayList;
    
    while (true) {
      String main = loadTextFile(new File(x, "main.java").getPath(), null);
      List<String> lines = toLines(main);
      List<String[]> t = findPostTranslators(lines);
      postTranslators.addAll(t);
      
      if (!t.isEmpty()) {
        main = fromLines(lines);
        x = TempDirMaker_make();
        saveTextFile(new File(x, "main.java").getPath(), main);
      }

      File y = autoTranslate(x, libraries_out, libraries2_out);
      if (y == x)
        break;
      x = y;
    }
    
    x = applyTranslators(x, postTranslators, libraries_out, libraries2_out);
    
    return x;
  }

  private static File autoTranslate(File x, List<File> libraries_out, LS libraries2_out) throws Exception {
    String main = loadTextFile(new File(x, "main.java").getPath(), null);
    L<S[]> translators = new L;
    main = findTranslators2x(main, translators);
    if (verbose) print("autoTranslate: Translators=" + sfu(translators));
    if (translators.isEmpty())
      return x;

    //main = fromLines(lines);
    File newDir = TempDirMaker_make();
    saveTextFile(new File(newDir, "main.java").getPath(), main);
    return applyTranslators(newDir, translators, libraries_out, libraries2_out);
  }
  
  static S findTranslators2x(S src, L<S[]> translatorsOut) {
    L<S> tok = javaTok(src);
    int i;
    while ((i = jfind(tok, "!<int>(")) >= 0) {
      int j = indexOf(tok, ")", i);
      translatorsOut.add(new S[] {tok.get(i+2), join(subList(tok, i+4, j+1))});
      clearTokens(tok, i, j+1);
    }
    while ((i = jfind(tok, "!<int>")) >= 0) {
      translatorsOut.add(new S[] {tok.get(i+2), null});
      clearTokens(tok, i, i+3);
    }
    ret join(tok);
  }

  static List<String[]> findPostTranslators(List<String> lines) {
    List<String[]> translators = new ArrayList<String[]>();
    Pattern pattern = Pattern.compile("^!post\\s*([0-9# \t]+)");
    Pattern pArgs = Pattern.compile("^\\s*\\((.*)\\)");
    for (ListIterator<String> iterator = lines.listIterator(); iterator.hasNext(); ) {
      String line = iterator.next();
      line = line.trim();
      Matcher matcher = pattern.matcher(line);
      if (matcher.find()) {
        String[] t = matcher.group(1).split("[ \t]+");
        String rest = line.substring(matcher.end());
        String arg = null;
        if (t.length == 1) {
          Matcher mArgs = pArgs.matcher(rest);
          if (mArgs.find())
            arg = mArgs.group(1);
        }
        for (String transi : t)
          translators.add(new String[]{transi, arg});
        iterator.remove();
      }
    }
    return translators;
  }

  private static File applyTranslators(File x, List<String[]> translators, List<File> libraries_out, LS libraries2_out) throws Exception {
    for (String[] translator : translators)
      x = applyTranslator(x, translator[0], translator[1], libraries_out, libraries2_out);
    return x;
  }

  // also takes a library
  private static File applyTranslator(File x, String translator, String arg, List<File> libraries_out, LS libraries2_out) throws Exception {
    if (verbose)
      System.out.println("Using translator " + translator + " on sources in " + x.getPath());

    File newDir = runTranslatorOnInput(translator, null, arg, x, !verbose, libraries_out, libraries2_out);

    if (!new File(newDir, "main.java").exists()) {
      throw new Exception("Translator " + translator + " did not generate main.java");
      // TODO: show translator output
    }
    if (verbose)
      System.out.println("Translated with " + translator + " from " + x.getPath() + " to " + newDir.getPath());
    x = newDir;
    return x;
  }

  private static File luaPrintToJavaPrint(File x) throws IOException {
    File newDir = TempDirMaker_make();
    String code = loadTextFile(new File(x, "main.java").getPath(), null);
    code = luaPrintToJavaPrint(code);
    saveTextFile(new File(newDir, "main.java").getPath(), code);
    return newDir;
  }

  public static String luaPrintToJavaPrint(String code) {
    return ("\n" + code).replaceAll(
      "(\n\\s*)print (\".*\")",
      "$1System.out.println($2);").substring(1);
  }

  public static File loadSnippetAsMainJava(String snippetID) throws IOException {
    checkProgramSafety(snippetID);
    File srcDir = TempDirMaker_make();
    saveTextFile(new File(srcDir, "main.java").getPath(), loadSnippet_code(parseSnippetID(snippetID)));
    return srcDir;
  }

  public static File loadSnippetAsMainJavaVerified(String snippetID, String hash) throws IOException {
    checkProgramSafety(snippetID);
    File srcDir = TempDirMaker_make();
    saveTextFile(new File(srcDir, "main.java").getPath(), loadSnippetVerified(snippetID, hash));
    return srcDir;
  }

  @SuppressWarnings( "unchecked" )
  /** returns output dir */
  private static File runTranslatorOnInput(String snippetID, String hash, String arg, File input,
                                           boolean silent,
                                           List<File> libraries_out,
                                           LS libraries2_out) throws Exception {
    if (safeTranslate)
      checkProgramSafetyImpl(snippetID);
    long id = parseSnippetID(snippetID);
    
    File libraryFile = DiskSnippetCache_getLibrary(id);
    
    print("Checking if " + snippetID + " is translator. marked as src lib: " + isMarkedAsSrcLib(snippetID) + ". has lib: " + (libraryFile != null));
    
    if (isMarkedAsSrcLib(snippetID) || isJavaxModuleSnippetType(getSnippetType(snippetID))) {
      setAdd(libraries2_out, snippetID);
      ret input;
    }
    
    if (verbose)
      System.out.println("Library file for " + id + ": " + libraryFile);
    if (libraryFile != null) {
      loadLib(snippetID, libraries_out, libraries2_out, libraryFile);
      return input;
    }

    String[] args = arg != null ? new String[]{arg} : new String[0];

    File srcDir = hash == null ? loadSnippetAsMainJava(snippetID)
      : loadSnippetAsMainJavaVerified(snippetID, hash);
    long mainJavaSize = new File(srcDir, "main.java").length();

    if (verbose)
      System.out.println(snippetID + ": length = " + mainJavaSize);
    if (mainJavaSize == 0) { // no text in snippet? assume it's a library
      loadLib(snippetID, libraries_out, libraries2_out, libraryFile);
      return input;
    }

    List<File> libraries = new ArrayList<File>();
    new LS libraries2;
    Object[] cached = translationCache.get(id);
    if (cached != null) {
      //System.err.println("Taking translator " + snippetID + " from cache!");
      srcDir = (File) cached[0];
      libraries = (List<File>) cached[1];
    } else if (hasTranspiledSet.contains(id) && useServerTranspiled) {
      print("Trying pretranspiled translator: #" + snippetID);
      S transpiledSrc = getServerTranspiled2(snippetID);
      
      int i = transpiledSrc.indexOf('\n');
      S libs = takeFirst(i, transpiledSrc);
      transpiledSrc = transpiledSrc.substring(i+1);
      
      print("libs=" + libs);
      libraries.addAll(javax_librariesToFiles(libs));
      libraries2.addAll(regexpAllMatches("\\d+", libs));

      if (!transpiledSrc.isEmpty()) {
        srcDir = TempDirMaker_make();
        saveTextFile(new File(srcDir, "main.java").getPath(), transpiledSrc);
        translationCache.put(id, cached = new Object[] {srcDir, libraries});
      }
    }

    File ioBaseDir = TempDirMaker_make();

    /*Class<?> mainClass = programCache.get("" + parseSnippetID(snippetID));
    if (mainClass != null)
      return runCached(ioBaseDir, input, args);*/
    // Doesn't work yet because virtualized directories are hardcoded in translator...

    if (cached == null) {
      System.err.println("Translating translator #" + id);
      if (translating.contains(id))
        throw new RuntimeException("Recursive translator reference chain: " + structure(translating));
      translating.add(id);
      try {
        srcDir = defaultTranslate(srcDir, libraries, libraries2);
      } finally {
        translating.remove(id);
      }
      System.err.println("Translated translator #" + id);
      translationCache.put(id, new Object[]{srcDir, libraries});
    }

    boolean runInProcess = false;

    if (virtualizeTranslators) {
      if (verbose) System.out.println("Virtualizing translator");

      // TODO: don't virtualize class _javax (as included in, say, #636)

      //srcDir = applyTranslator(srcDir, "#2000351"); // I/O-virtualize the translator
      // that doesn't work because it recurses infinitely...

      // So we do it right here:
      String s = loadTextFile(new File(srcDir, "main.java").getPath(), null);
      s = s.replaceAll("new\\s+File\\(", "virtual.newFile(");
      s = s.replaceAll("new\\s+FileInputStream\\(", "virtual.newFileInputStream(");
      s = s.replaceAll("new\\s+FileOutputStream\\(", "virtual.newFileOutputStream(");
      s += "\n\n" + loadSnippet("#1004414"); // load class virtual

      // forward snippet cache (virtualized one)
      File dir = virtCache != null ? virtCache : DiskSnippetCache_dir;
      s = s.replace("static File DiskSnippetCache_dir" + ";",
        "static File DiskSnippetCache_dir " + "= new File(" + javaQuote(dir.getAbsolutePath()) + ");"); // extra + is necessary for Dumb TinyBrain :)
      s = s.replace("static boolean preferCached = false;", "static boolean preferCached = true;");

      /*if (verbose) {
        System.out.println("==BEGIN VIRTUALIZED TRANSLATOR==");
        System.out.println(s);
        System.out.println("==END VIRTUALIZED TRANSLATOR==");
      }*/
      
      srcDir = TempDirMaker_make();
      saveTextFile(new File(srcDir, "main.java").getPath(), s);

      // TODO: silence translator also
      runInProcess = true;
    }

    return runJavaX(ioBaseDir, srcDir, input, silent, runInProcess, libraries,
      args, cacheTranslators ? "" + id : null, "" + id);
  }

  static void checkProgramSafety(String snippetID) throws IOException {
    if (!safeOnly) return;
    checkProgramSafetyImpl(snippetID);
  }

  static void checkProgramSafetyImpl(String snippetID) throws IOException {
    URL url = new URL(tb_mainServer() + "/tb-int/is-javax-safe.php?id=" + parseSnippetID(snippetID) + standardCredentials());
    String text = loadPage(url);
    if (!text.startsWith("{\"safe\":\"1\"}"))
      throw new RuntimeException("Program not safe: #" + parseSnippetID(snippetID));
  }

  static void loadLib(String snippetID, List<File> libraries_out, LS libraries2_out, File libraryFile) throws IOException {
    if (verbose)
      System.out.println("Assuming " + snippetID + " is a library.");

    if (libraryFile == null) {
      byte[] data = loadDataSnippetImpl(snippetID);
      DiskSnippetCache_putLibrary(parseSnippetID(snippetID), data);
      libraryFile = DiskSnippetCache_getLibrary(parseSnippetID(snippetID));
    }

    if (!libraries_out.contains(libraryFile)) {
      libraries_out.add(libraryFile);
      libraries2_out.add(str(psI(snippetID)));
    }
  }

  /** returns output dir */
  private static File runJavaX(File ioBaseDir, File originalSrcDir, File originalInput,
                               boolean silent, boolean runInProcess,
                               List<File> libraries, String[] args, String cacheAs,
                               String programID) throws Exception {
    File srcDir = new File(ioBaseDir, "src");
    File inputDir = new File(ioBaseDir, "input");
    File outputDir = new File(ioBaseDir, "output");
    copyInput(originalSrcDir, srcDir);
    copyInput(originalInput, inputDir);
    javax2(srcDir, ioBaseDir, silent, runInProcess, libraries, args, cacheAs, programID, null);
    return outputDir;
  }

  private static void copyInput(File src, File dst) throws IOException {
    copyDirectory(src, dst);
  }

  public static boolean hasFile(File inputDir, String name) {
    return new File(inputDir, name).exists();
  }

  public static void copyDirectory(File src, File dst) throws IOException {
    if (verbose) System.out.println("Copying " + src.getAbsolutePath() + " to " + dst.getAbsolutePath());
    dst.mkdirs();
    File[] files = src.listFiles();
    if (files == null) return;
    for (File file : files) {
      File dst1 = new File(dst, file.getName());
      if (file.isDirectory())
        copyDirectory(file, dst1);
      else {
        if (verbose) System.out.println("Copying " + file.getAbsolutePath() + " to " + dst1.getAbsolutePath());
        copy(file, dst1);
      }
    }
  }

  /** Quickly copy a file without a progress bar or any other fancy GUI... :) */
  public static void copy(File src, File dest) throws IOException {
    FileInputStream inputStream = newFileInputStream(src);
    FileOutputStream outputStream = newFileOutputStream(dest);
    try {
      copy(inputStream, outputStream);
      inputStream.close();
    } finally {
      outputStream.close();
    }
  }

  private static FileInputStream newFileInputStream(File f) throws FileNotFoundException {
    /*if (androidContext != null)
      return (FileInputStream) call(androidContext,
        "openFileInput", f.getPath());
    else*/
    return new // line break for Dumb TinyBrain :)
    FileInputStream(f);
  }

  public static void copy(InputStream in, OutputStream out) throws IOException {
    byte[] buf = new byte[65536];
    while (true) {
      int n = in.read(buf);
      if (n <= 0) return;
      out.write(buf, 0, n);
    }
  }

  public static String loadSnippetVerified(String snippetID, String hash) throws IOException {
    String text = loadSnippet(snippetID);
    String realHash = getHash(text.getBytes("UTF-8"));
    if (!realHash.equals(hash)) {
      String msg;
      if (hash.isEmpty())
        msg = "Here's your hash for " + snippetID + ", please put in your program: " + realHash;
      else
        msg = "Hash mismatch for " + snippetID + ": " + realHash + " (new) vs " + hash + " - has tinybrain.de been hacked??";
      throw new RuntimeException(msg);
    }
    return text;
  }

  public static String getHash(byte[] data) {
    return bytesToHex(getFullFingerprint(data));
  }

  public static byte[] getFullFingerprint(byte[] data) {
    try {
      return MessageDigest.getInstance("MD5").digest(data);
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException(e);
    }
  }

  public static long parseSnippetID(String snippetID) {
    return Long.parseLong(shortenSnippetID(snippetID));
  }

  private static String shortenSnippetID(String snippetID) {
    if (snippetID.startsWith("#"))
      snippetID = snippetID.substring(1);
    String httpBlaBla = "http://tinybrain.de/";
    if (snippetID.startsWith(httpBlaBla))
      snippetID = snippetID.substring(httpBlaBla.length());
    return snippetID;
  }

  static String getTranspilationFromBossBot(long snippetID) {
    return boss(format3("get transpilation for *", snippetID));
  }
  
  // This is DIFFERENT from the std library loadSnippet
  // (this is only for code!)
  public static String loadSnippet_code(long snippetID) throws IOException {
    String text = getSnippetFromBossBot(snippetID);
    if (text != null) return text;
    
    if (snippetID >= 1400000 && snippetID < 1500000) ret ""; // binary snippets
      
    text = memSnippetCache.get(snippetID);
    if (text != null) {
      if (verbose)
        System.out.println("Getting " + snippetID + " from mem cache");
      return text;
    }
    
    initSnippetCache();
    text = DiskSnippetCache_get(snippetID);
    if (preferCached && text != null) {
      if (verbose)
        System.out.println("Getting " + snippetID + " from disk cache (preferCached)");
      return text;
    }

    String md5 = text != null ? md5(text) : "-";
    if (text != null) {
      String hash = prefetched.get(snippetID);
      if (hash != null) {
        if (md5.equals(hash)) {
          memSnippetCache.put(snippetID, text);
          if (verbose)
            System.out.println("Getting " + snippetID + " from prefetched");
          return text;
        } else
          prefetched.remove(snippetID); // (maybe this is not necessary)
      }
    }

    try {
      String theURL = tb_mainServer() + "/getraw.php?id=" + snippetID + "&getmd5=1&utf8=1&usetranspiled=1" + standardCredentials();
      if (text != null) {
        //System.err.println("MD5: " + md5);
        theURL += "&md5=" + md5;
      }
      URL url = new URL(theURL);
      String page = loadPage(url);

      // parse & drop transpilation flag available line
      int i = page.indexOf('\n');
      boolean hasTranspiled = page.substring(0, i).trim().equals("1");
      if (hasTranspiled)
        hasTranspiledSet.add(snippetID);
      else
        hasTranspiledSet.remove(snippetID);
      page = page.substring(i+1);

      if (page.startsWith("==*#*==")) {
        // same, keep text
        //System.err.println("Snippet unchanged, keeping.");
      } else {
        // drop md5 line
        i = page.indexOf('\n');
        String hash = page.substring(0, i).trim();
        text = page.substring(i+1);

        String myHash = md5(text);
        if (myHash.equals(hash)) {
          //System.err.println("Hash match: " + hash);
        } else
          System.err.println("Hash mismatch");
      }
    } catch (Exception e) {
      printStackTrace(e);
      throw new IOException("Snippet #" + snippetID + " not found or not public");
    }

    memSnippetCache.put(snippetID, text);

    try {
      initSnippetCache();
      DiskSnippetCache_put(snippetID, text);
    } catch (IOException e) {
      System.err.println("Minor warning: Couldn't save snippet to cache ("  + DiskSnippetCache_getDir() + ")");
    }

    return text;
  }

  /** runs a transpiled set of sources */
  public static PaA javax2(File srcDir, File ioBaseDir, boolean silent, boolean runInProcess,
                            List<File> libraries, String[] args, String cacheAs,
                            String programID, Info info) throws Exception {
    if (android) {
      // TODO: no translator virtualization? huh?
      javax2android(srcDir, args, programID);
      null;
    } else {
      File jarFile = null, classesDir = null;
      bool jarOK = false;
      S javacOutput = "";
      
      if (useDummyMainClasses()) {
        //print("Trying to rename " + srcDir + ", " + programID);
        if (isSnippetID(programID))
          renameMainJava(srcDir, programID);
      }

      if (isSnippetID(programID)) {
        S md5 = md5(struct(libraries) + "\n" 
          + loadTextFile(new File(srcDir, "main.java")) 
          + loadTextFile(new File(srcDir, dummyMainClassName(programID) + ".java")));
        S psi = str(parseSnippetID(programID));
        jarFile = new File(getCacheProgramDir("#1001638"), psi + "-" + md5 + ".jar");
        jarOK = jarFile.length() >= 50;
        if (jarOK)
          classesDir = jarFile;
      }
    
      if (!jarOK) {
        classesDir = TempDirMaker_make();
        temp quietLoading ? null : tempLoadingAnim("Compiling Java");
        javacOutput = compileJava(srcDir, libraries, classesDir);
      }
      
      // save jar
      
      if (!jarOK && isSnippetID(programID)) pcall {
        dir2zip_recurse_verbose = false;
        dir2jar(classesDir, jarFile);
      }

      // run

      if (verbose) System.out.println("Running program (" + srcDir.getAbsolutePath()
        + ") on io dir " + ioBaseDir.getAbsolutePath() + (runInProcess ? "[in-process]" : "") + "\n");
      ret runProgram(javacOutput, classesDir, ioBaseDir, silent, runInProcess, libraries, args, cacheAs, programID, info);
    }
  }

  static Class<?> loadx2android(File srcDir, String programID) throws Exception {
    // TODO: optimize if it's a loaded snippet anyway
    URL url = new URL(tb_mainServer() + "/dexcompile.php");
    URLConnection conn = url.openConnection();
    String postData = "src=" + URLEncoder.encode(loadTextFile(new File(srcDir, "main.java").getPath(), null), "UTF-8") + standardCredentials();
    byte[] dexData = doPostBinary(postData, conn);
    if (!isDex(dexData))
      throw new RuntimeException("Dex generation error: " + dexData.length + " bytes - " + new String(dexData, "UTF-8"));
    System.out.println("Dex loaded: " + dexData.length + "b");

    File dexDir = TempDirMaker_make();
    File dexFile = new File(dexDir, System.currentTimeMillis() + ".dex");
    File dexOutputDir = TempDirMaker_make();

    System.out.println("Saving dex to: " + dexDir.getAbsolutePath());
    try {
      saveBinaryFile(dexFile.getPath(), dexData);
    } catch (Throwable e) {
      System.out.println("Whoa!");
      throw new RuntimeException(e);
    }

    System.out.println("Getting parent class loader.");
    ClassLoader parentClassLoader =
      //ClassLoader.getSystemClassLoader(); // does not find support jar
      //getClass().getClassLoader(); // Let's try this...
      x30.class.getClassLoader().getParent(); // XXX !

    //System.out.println("Making DexClassLoader.");
    //DexClassLoader classLoader = new DexClassLoader(dexFile.getAbsolutePath(), dexOutputDir.getAbsolutePath(), null,
    //  parentClassLoader);
    Class dcl = Class.forName("dalvik.system.DexClassLoader");
    Object classLoader = dcl.getConstructors()[0].newInstance(dexFile.getAbsolutePath(), dexOutputDir.getAbsolutePath(), null,
      parentClassLoader);

    //System.out.println("Loading main class.");
    //Class<?> theClass = classLoader.loadClass(mainClassName);
    Class<?> theClass = (Class<?>) call(classLoader, "loadClass", "main");

    //System.out.println("Main class loaded.");
    try {
      set(theClass, "androidContext", androidContext);
    } catch (Throwable e) {}

    setVars(theClass, programID);
    
    addInstance(programID, theClass);
    
    return theClass;
  }
  
  static void addInstance(S programID, Class mainClass) {
    programID = "" + parseSnippetID(programID);
    instances.put(programID, new WeakReference<Class>(mainClass));
  }
  
  static Class getInstance(S programID) {
    programID = "" + parseSnippetID(programID);
    L<WeakReference<Class>> l = instances.get(programID);
    for (WeakReference<Class> c : l) {
      Class theClass = c.get();
      // TODO: shorten the list
      if (theClass != null)
        return theClass;
    }
    return null;
  }
  
  static MultiMap<String, WeakReference<Class>> instances = new MultiMap<String, WeakReference<Class>>();
  
  !include #1007192 // autoVMExit (core version)

  static void javax2android(File srcDir, String[] args, String programID) throws Exception {
    Class<?> theClass = loadx2android(srcDir, programID);

    Method main = null;
    try {
      main = findStaticMethod(theClass, "main", new Object[]{androidContext});
    } catch (RuntimeException e) {
    }

    //System.out.println("main method for " + androidContext + " of " + theClass + ": " + main);

    if (main != null) {
      // old style main program that returns a View
      // TODO: maybe allow programs without main method, although it doesn't seem to make sense here really (Android main program)
      System.out.println("Calling main (old-style)");
      Object view = main.invoke(null, androidContext);
      System.out.println("Calling setContentView with " + view);
      call(Class.forName("main"), "setContentViewInUIThread", view);
      //call(androidContext, "setContentView", view);
      System.out.println("Done.");
    } else {
      System.out.println("New-style main method running.\n\n====\n");
      runMainMethod(args, theClass);
    }
  }

  static byte[] DEX_FILE_MAGIC = { 0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00 };

  static boolean isDex(byte[] dexData) {
    if (dexData.length < DEX_FILE_MAGIC.length) return false;
    for (int i = 0; i < DEX_FILE_MAGIC.length; i++)
      if (dexData[i] != DEX_FILE_MAGIC[i])
        return false;
    return true;
  }

  static String compileJava(File srcDir, List<File> libraries, File classesDir) throws IOException {
    javaCompilerOutput = null;
    ++compilations;

    // collect sources

    List<File> sources = new ArrayList<File>();
    if (verbose) System.out.println("Scanning for sources in " + srcDir.getPath());
    scanForSources(srcDir, sources, true);
    if (sources.isEmpty())
      throw new IOException("No sources found");

    // compile

    File optionsFile = File.createTempFile("javac", "", javaxTempDir());
    try {
      if (verbose) System.out.println("Compiling " + sources.size() + " source(s) to " + classesDir.getPath());
      if (verbose) System.out.println("Libraries: " + libraries);
      String options = "-d " + bashQuote(classesDir.getPath());
      writeOptions(sources, libraries, optionsFile, options);
      classesDir.mkdirs();
      return invokeJavaCompiler(optionsFile);
    } finally {
      optionsFile.delete();
    }
  }

  private static PaA runProgram(String javacOutput, File classesDir, File ioBaseDir,
                                 boolean silent, boolean runInProcess,
                                 List<File> libraries, String[] args, String cacheAs,
                                 String programID, Info info) throws Exception {
    // print javac output if compile failed and it hasn't been printed yet
    if (info != null) {
      info.programID = programID;
      info.programArgs = args;
    }
    boolean didNotCompile = !didCompile(classesDir);
    if (verbose || didNotCompile)
      System.out.println(javacOutput);
    if (didNotCompile)
      null;

    if (runInProcess
      || (ioBaseDir.getAbsolutePath().equals(new File(".").getAbsolutePath()) && !silent)) {
      ret runProgramQuick(classesDir, libraries, args, cacheAs, programID, info, ioBaseDir);
    }

    boolean echoOK = false;
    // TODO: add libraries to class path
    String bashCmd = "(cd " + bashQuote(ioBaseDir.getAbsolutePath()) + " && (java -cp "
      + bashQuote(classesDir.getAbsolutePath()) + " main" + (echoOK ? "; echo ok" : "") + "))";
    if (verbose) System.out.println(bashCmd);
    String output = backtick(bashCmd);
    lastOutput = output;
    if (verbose || !silent)
      System.out.println(output);
    null;
  }

  // classesDir can also be a .jar
  static boolean didCompile(File classesDir) {
    return classesDir.length() >= 50 || hasFile(classesDir, "main.class");
  }

  private static PaA runProgramQuick(File classesDir, List<File> libraries,
                                      String[] args, String cacheAs,
                                      String programID, Info info,
                                      File ioBaseDir) throws Exception {
    // make class loader
    JavaXClassLoader classLoader = new JavaXClassLoader(programID, concatLists(ll(classesDir), libraries));

    // load JavaX main class
    Class<?> mainClass = classLoader.loadClass("main");
    
    S src = null;
    if (info != null && info.transpiledSrc != null)
      src = loadTextFile(findMainJavaInDir(info.transpiledSrc));
    
    if (info == null) {
      //print("no info");
    } else {
      //print("info.transpiledSrc = " + info.transpiledSrc);
      info.mainClass = mainClass;
      if (hasFieldNamed(mainClass, "myJavaSource_code")) {
        //print("src = " + l(src));
        set(mainClass, "myJavaSource_code", src);
      }
      if (registerSourceCodes && info.transpiledSrc != null)
        registerSourceCode(mainClass, src);
    }
      
    if (cacheAs != null)
      programCache.put(cacheAs, mainClass);
      
    // record injection
    final PaA paa = new PaA(programID, args);
    paa.injectionID = randomID(8);
    paa.mainClass = mainClass;
    if (src != null) paa.srcMD5 = md5(src);
    addInjection(paa);
    
    // set case
    if (caseID != null)
      setOpt(mainClass, 'caseID_caseID, caseID);

    // change baseDir
    try {
      //print("Changing base dir to " + ioBaseDir.getAbsolutePath());
      Class virtual = mainClass.getClassLoader().loadClass("virtual");
      set(virtual, "virtual_baseDir", ioBaseDir.getAbsolutePath());
    } catch (Throwable e) { /* whatever */ }

    setVars(mainClass, programID);
    addInstance(programID, mainClass);
    paaRun(paa);
    
    ret paa;
  }
  
  static void paaRun(PaA paa) {
    moveThisThreadToChild(paa.mainClass);
    paa.mainThread = new WeakReference(currentThread());
    paa.exception = null;
    try {
      runMainMethod(paa.arguments, paa.mainClass);
    } catch (Throwable e) {
      paa.exception = e;
      printStackTrace2(e);
      //throw e;
    } finally {
      paa.mainDone = true;
      synchronized(x30.class) {}
    }
  }

  static void setVars(Class<?> theClass, String programID) {
    try {
      set(theClass, "programID", formatSnippetIDOpt(programID));
    } catch (Throwable e) {}

    try {
      set(theClass, "__javax", x30.class);
    } catch (Throwable e) {}
    
    callOnLoadMethods(theClass);
  }


  static void runMainMethod(String[] args, Class<?> mainClass) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    callMain(mainClass, args);
  }

  static String invokeJavaCompiler(File optionsFile) throws IOException {
    long time = now();
    String output;
    if (hasEcj() && !javacOnly)
      output = invokeEcj(optionsFile);
    else
      output = invokeJavac(optionsFile);
    if (verbose) System.out.println(output);
    if (!quietLoading) done(time, "Java compile");
    return output;
  }

  private static boolean hasEcj() {
    try {
      Class.forName("org.eclipse.jdt.internal.compiler.batch.Main");
      return true;
    } catch (ClassNotFoundException e) {
      return false;
    }
  }

  // TODO: fix UTF-8 here too
  private static String invokeJavac(File optionsFile) throws IOException {
    String output;
    output = backtick("javac " + bashQuote("@" + optionsFile.getPath()));
    javaCompilerOutput = output;
    if (backtick_exitValue != 0) {
      System.out.println(output);
      throw new RuntimeException("javac returned errors.");
    }
    return output;
  }

  // throws ClassNotFoundException if ecj is not in classpath
  static String invokeEcj(File optionsFile) {
    if (vmPort() != 5000 && !dontCompileThroughPort5000) pcall-short {
      ret invokeEcjInFirstVM(optionsFile);
    }
    
    try {
      StringWriter writer = new StringWriter();
      PrintWriter printWriter = new PrintWriter(writer);

      // add more eclipse options in the line below

      String[] args = {
        "-source", javaTarget,
        "-target", javaTarget,
        "-nowarn",
        "-encoding", "UTF-8",
        "@" + optionsFile.getPath()
      };
      
      if (verbose)
        print("ECJ options: " + structure(args));

      Class ecjClass = Class.forName("org.eclipse.jdt.internal.compiler.batch.Main");
      Object main = newInstance(ecjClass, printWriter, printWriter, false);
      call(main, "compile", new Object[]{args});
      int errors = (Integer) get(main, "globalErrorsCount");

      String output = cleanJavaCompilerOutput(writer.toString());
      javaCompilerOutput = output;
      if (errors != 0) {
        //System.out.println(output);
        throw new JavaCompileException(output);
      }
      return output;
    } catch (Exception e) {
      throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
    }
  }

  static Object newInstance(Class c, Object... args) { try {
    Constructor m = findConstructor(c, args);
    m.setAccessible(true);
    return m.newInstance(args);
  } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}

  static Constructor findConstructor(Class c, Object... args) {
    for (Constructor m : c.getDeclaredConstructors()) {
      if (!checkArgs(m.getParameterTypes(), args, false))
        continue;
      return m;
    }
    throw new RuntimeException("Constructor with " + args.length + " matching parameter(s) not found in " + c.getName());
  }

  static boolean checkArgs(Class[] types, Object[] args, boolean debug) {
    if (types.length != args.length) {
      if (debug)
        System.out.println("Bad parameter length: " + args.length + " vs " + types.length);
      return false;
    }
    for (int i = 0; i < types.length; i++)
      if (!(args[i] == null || isInstanceX(types[i], args[i]))) {
        if (debug)
          System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]);
        return false;
      }
    return true;
  }

  private static void writeOptions(List<File> sources, List<File> libraries,
                                   File optionsFile, String moreOptions) throws IOException {
    FileWriter writer = new FileWriter(optionsFile);
    for (File source : sources)
      writer.write(bashQuote(source.getPath()) + " ");
    if (!libraries.isEmpty()) {
      List<String> cp = new ArrayList<String>();
      for (File lib : libraries)
        cp.add(lib.getAbsolutePath());
      writer.write("-cp " + bashQuote(join(File.pathSeparator, cp)) + " ");
    }
    writer.write(moreOptions);
    writer.close();
  }

  static void scanForSources(File source, List<File> sources, boolean topLevel) {
    if (source.isFile() && source.getName().endsWith(".java"))
      sources.add(source);
    else if (source.isDirectory() && !isSkippedDirectoryName(source.getName(), topLevel)) {
      File[] files = source.listFiles();
      for (File file : files)
        scanForSources(file, sources, false);
    }
  }

  private static boolean isSkippedDirectoryName(String name, boolean topLevel) {
    if (topLevel) return false; // input or output ok as highest directory (intentionally specified by user, not just found by a directory scan in which case we probably don't want it. it's more like heuristics actually.)
    return name.equalsIgnoreCase("input") || name.equalsIgnoreCase("output");
  }

  public final static String charsetForTextFiles = "UTF8";

  static long TempDirMaker_lastValue;

  public static File TempDirMaker_make() {
    File dir = javaxTempDir("" + TempDirMaker_newValue());
    dir.mkdirs();
    chmod_aPlusRX(dir);
    return dir;
  }

  private static long TempDirMaker_newValue() {
    long value;
    do
      value = System.currentTimeMillis();
    while (value == TempDirMaker_lastValue);
    TempDirMaker_lastValue = value;
    return value;
  }

  !include #1000810 // join
  
  static String computerID;
  public static String getComputerID() throws IOException {
    if (noID) return null;
    if (computerID == null) {
      File file = computerIDFile();
      computerID = loadTextFile(file.getPath());
      if (computerID == null) {
        // legacy load
        computerID = loadTextFile(userDir(".tinybrain/computer-id"));
        if (computerID == null)
          computerID = makeRandomID(12);
        saveTextFile(file.getPath(), computerID);
      }
      if (verbose)
        System.out.println("Local computer ID: " + computerID);
    }
    return computerID;
  }

  static void cleanCache() {
    try {
      pcall {
        S s = trim(loadTextFile(getProgramFile("#1001638", "temp-file-retention-time")));
        if (nempty(s))
          tempFileRetentionTime = parseInt(s);
      }
      cleanJavaXCache(tempFileRetentionTime, verbose);
    } finally {
      cleanCacheDone = true;
    }
  }

  static void showSystemProperties() {
    System.out.println("System properties:\n");
    for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
      System.out.println("  " + entry.getKey() + " = " + entry.getValue());
    }
    System.out.println();
  }

  static void showVersion() {
    //showSystemProperties();
    boolean eclipseFound = hasEcj();
    //String platform = System.getProperty("java.vendor") + " " + System.getProperty("java.runtime.name") + " " + System.getProperty("java.version");
    String platform = System.getProperty("java.vm.name") + " " + System.getProperty("java.version");
    String os = System.getProperty("os.name"), arch = System.getProperty("os.arch");
    System.out.println("This is " + version + ", last changed " + dateChanged + ", transpiled " + localDateWithMinutes(myTranspilationDate()) + ".");
    System.out.println("[Details: " +
      (eclipseFound ? "Eclipse compiler (good)" : "javac (not so good)")
      + ", " + platform + ", " + arch + ", " + os + "]");
  }

  static boolean isAndroid() {
    return System.getProperty("java.vendor").toLowerCase().indexOf("android") >= 0;
  }
  
  !include #1000415 // set function

  static String smartJoin(String[] args) {
    String[] a2 = new String[args.length];
    for (int i = 0; i < args.length; i++) {
      a2[i] = Pattern.compile("\\w+").matcher(args[i]).matches() ? args[i] : quote(args[i]);
    }
    return join(" ", a2);
  }
  
  static void logStart(String[] args) throws IOException {
    String line = smartJoin(args);
    appendToLog(new File(userHome(), ".javax/log.txt").getPath(), line);
  }
  
  static void appendToLog(String path, String line) throws IOException {
    appendToFile(path, "\n" + line + "\n");
  }
  
  static PrintStream oldOut, oldErr;
  static Thread reader, reader2;
  static boolean quit; // always false now
  static PipedInputStream pin=new PipedInputStream(systemOutPipeSize);
  static PipedInputStream pin2=new PipedInputStream(systemErrPipeSize);
  static PipedInputStream pin3=new PipedInputStream(systemInPipeSize);
  static PipedOutputStream pout3;

  static class Console /*extends WindowAdapter implements WindowListener,*/ implements ActionListener {
    JFrame frame;
    JFastLogView_noWrap logView;
    JTextField tfInput;
    JComboBox cbInput; // alternative to tfInput
    StringBuffer buf = new StringBuffer();
    JButton buttonclear, buttonkill, buttonrestart, buttonduplicate, buttonassist, buttonpause, buttonhide;
    String[] args;
    volatile boolean autoScroll = true;
    JScrollPane scrollPane;
    S title;
    JLabel memoryView;

    final DelayedUpdate du = new DelayedUpdate(r {
      bool show = !consoleUpdateOff && isVisibleFrame(frame);
      S text = "";
      synchronized(buf) {
        int max = maxConsoleChars;
        if (buf.length() > max) {
          try {
            int newLength = max/2;
            int ofs = buf.length()-newLength;
            S newString = buf.substring(ofs);
            buf.setLength(0);
            buf.append("[...] ").append(newString);
          } catch (Exception e) {
            buf.setLength(0);
          }
          buf.trimToSize();
        }

        if (show) text = str(buf);
      }
      
      if (!show) ret;
      
      // TODO: optimize more?
      if (logView != null && logView.setText(text) && autoScroll)
        scrollAllTheWayDown(logView);
    });

    public Console(final String[] args) ctex {
      du.delay = consoleUpdateDelay;
      this.args = args;
      // create all components and add them
      frame = new JFrame(args.length == 0 ? "JavaX Starter Output" : "JavaX Output - " + join(" ", args));
      if (!consoleGrabFocus)
        frame.setAutoRequestFocus(false);

    /*Dimension screenSize=Toolkit.getDefaultToolkit().getScreenSize();
    Dimension frameSize=new Dimension((int)(screenSize.width/2),(int)(screenSize.height/2));
    int x=(int)(frameSize.width/2);
    int y=(int)(frameSize.height/2);
    frame.setBounds(x,y,frameSize.width,frameSize.height);*/

      // put in bottom-right corner
      Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
      frame.setBounds(r.x+r.width-consoleWidth-consoleXGap, r.y+r.height-consoleHeight-consoleYGap, consoleWidth, consoleHeight);
      moveWindowIntoScreen(frame);

      if (verbose) print("Making log view");
      logView = jFastLogView_noWrap();
      logView.verbose = verbose;
      if (verbose) print("Made log view");
      buttonclear = consoleButton("clear");
      buttonkill = consoleButton("kill");
      buttonrestart = consoleButton("restart");
      buttonrestart.setToolTipText("Restart program");
      buttonduplicate = consoleButton("duplicate");
      buttonassist = consoleButton("assist");
      buttonpause = consoleButton("pause");
      buttonhide = consoleButton("hide");
      JLabel mv = memoryView = jMemoryView();
      JPanel buttons = westAndCenter(withRightMargin(mv), hgrid(
        buttonclear,
        buttonkill,
        buttonrestart,
        buttonduplicate,
        buttonassist,
        buttonpause,
        buttonhide));
        
      componentPopupMenuItem(mv, "Re-run main", f reRunMain);
      componentPopupMenuItem(mv, "Print stack traces", f listUserThreadsWithStackTraces);
      componentPopupMenuItem(mv, "10 second profile", f swing_tenSecondProfile);
      componentPopupMenuItem(mv, "Hide VM", f hideVM);
      componentPopupMenuItem(mv, "Set variable...", f setVarDialog);
      
      componentPopupMenu(mv, voidfunc(JPopupMenu menu) {
        if (isOfflineMode())
          addMenuItem(menu, "Switch to online mode", f goOnlineMode);
        else
          addMenuItem(menu, "Switch to offline mode", f goOfflineMode);
      });
      
      //componentPopupMenuItem(mv, "Bigger fonts", f swingBiggerFonts);
      //componentPopupMenuItem(mv, "Smaller fonts", f swingSmallerFonts);

      pout3 = new PipedOutputStream(pin3);
      
      tfInput = new JTextField();
      tfInput.addActionListener(actionListener {
        S line = tfInput.getText();
        try {
          pout3.write((line + "\n").getBytes("UTF-8"));
          pout3.flush();
        } catch (Exception e) {}
        //tfInput.setText("");
        tfInput.selectAll();
      });
      addHistoryToTextField(tfInput);
      
      JPanel panel = new JPanel(new BorderLayout());
      if (verbose) print("Making scroll pane");
      panel.add(scrollPane = jscroll_copyBackground(logView), BorderLayout.CENTER);
      if (verbose) print("Made scroll pane");
      panel.add(tfInput, BorderLayout.SOUTH);
      
      frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
      frame.addWindowListener(new WindowAdapter() {
        public void windowActivated(WindowEvent e) {
          if (focusConsoleInputOnActivate)
            tfInput.requestFocus();
        }
        
        public synchronized void windowClosing(WindowEvent evt) {
          if (JOptionPane.showConfirmDialog(frame,
            "Close " + (empty(title) ? "JavaX Application" : title) + "?", "JavaX", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
            cleanKillVM_noSleep();
        }
      });
      
      frame.getContentPane().setLayout(new BorderLayout());
      frame.getContentPane().add(panel, BorderLayout.CENTER);
      frame.getContentPane().add(buttons, BorderLayout.SOUTH);
      if (verbose) print("Showing console frame");
      if (consoleOn) frame.setVisible(true);
      if (verbose) print("Showed console frame");
      setFrameIconLater(frame, javaxDefaultIcon());
      
      //frame.addWindowListener(this); // disabled for now
      buttonclear.addActionListener(this);
      buttonkill.addActionListener(this);
      buttonrestart.addActionListener(this);
      buttonduplicate.addActionListener(this);
      buttonassist.addActionListener(this);
      buttonpause.addActionListener(this);
      buttonhide.addActionListener(this);

      quit=false; // signals the Threads that they should exit
      
      if (args.length != 0) {
        //print("Starting title updater");
        new Thread("Console Title Updater :)") {
          public void run() {
            if (args.length != 0) {
              int i = 0;
              while (i < args.length && !isSnippetID(args[i])) {
                if (eq(args[i], "-case")) ++i;
                ++i;
              }
              //print("Getting title for " + args[i]);
              title = getSnippetTitle(get(args, i));
              //print("Title: " + title);
              if (title != null && title.length() != 0)
                frame.setTitle(title + " [Output]");
            }
          }
        }.start();
      }
      
      System.setIn(pin3);
      
    }
    
    void scrollToBottomLater {
      awt { scrollToBottom(); }
    }
    
    void scrollToBottom {
      JScrollBar vertical = scrollPane.getVerticalScrollBar();
      vertical.setValue(vertical.getMaximum());
    }

    /*public synchronized void windowClosed(WindowEvent evt)
    {
      console = null;
      //quit=true;
      //this.notifyAll(); // stop all threads
      //try { reader.join(1000);pin.close();   } catch (Exception e){}
      //try { reader2.join(1000);pin2.close(); } catch (Exception e){}
      //System.exit(0);
    }*/

    /*public synchronized void windowClosing(WindowEvent evt)
    {
      frame.setVisible(false); // default behaviour of JFrame
      frame.dispose();
    }*/

    public synchronized void actionPerformed(ActionEvent evt) {
      if (evt.getSource() == buttonkill) {
        cleanKillVM_noSleep();
      } else if (evt.getSource() == buttonrestart) {
        cleanRestart(args);
      } else if (evt.getSource() == buttonduplicate) {
        print("Console: Duplicate button pressed.");
        nohupJavax(smartJoin(args));
      } else if (evt.getSource() == buttonassist) {
        assist();
      } else if (evt.getSource() == buttonpause) {
        O mainClass = getMainMainClass();
        if (mainClass != null) {
          if (eq(buttonpause.getText(), "pause")) {
            buttonpause.setText("resume");
            pauseAll(true);
          } else {
            buttonpause.setText("pause");
            pauseAll(false);
          }
        }
      } else if (evt.getSource() == buttonhide) {
        hideConsole();
      } else { // clear log
        if (logView != null) logView.setText("");
        buf = new StringBuffer();
      }
    }
    
    void showConsole() {
      swing {
        if (!frame.isVisible()) {
          makeFrameVisible(frame);
          du.trigger();
        }
      }
    }

    void hideConsole() {
      autoVMExit();
      frame.setVisible(false);
    }
    
    public void appendText(String s, boolean outNotErr) {
      //if (verbose) oldOut.println("Console appendText " + outNotErr + " " + quote(s));
      synchronized(buf) {
        buf.append(s);
      }
      du.trigger();
    }
  } // Console

  static void tryToOpenConsole(String[] args) {
    try {
      if (isHeadless()) ret;
      swing {
        if (console == null)
          console = new Console(args);
      }
    } catch (HeadlessException e) {
      // ok, we're headless.
    } catch (Throwable e) {
      // some other error in console - continue without it
      printStackTrace(e);
    }
  }

  //// END CONSOLE STUFF

  static long now_virtualTime;
  static long now() {
    return now_virtualTime != 0 ? now_virtualTime : System.currentTimeMillis();
  }

  static <A> A print(A a) {
    System.out.println(a);
    ret a;
  }
  
  static void print() { print(""); }
  
  static <A> A print(S s, A o) {
    print((endsWithLetterOrDigit(s) ? s + ": " : s) + o);
    ret o;
  }

  public void run()
  {
    try
    {
      new UTF8Processor p;
      while (Thread.currentThread()==reader) {
        sleep(pipeDelay);
        if (pin.available()!=0) {
          String input=readLineThroughUTF8Processor(p, pin);
          //if (verbose) oldOut.println("reader: " + quote(input));
          appendText(input, true);
        }
        if (quit) return;
      }

      while (Thread.currentThread()==reader2) {
        sleep(pipeDelay);
        if (pin2.available()!=0) {
          String input=readLineThroughUTF8Processor(p, pin2);
          //if (verbose) oldOut.println("reader2: " + quote(input));
          appendText(input, false);
        }
        if (quit) return;
      }
    } catch (Exception e)
    {
      appendText("\nConsole reports an Internal error.", false);
      appendText("The error is: "+e, false);
    }
  }

  static void redirectSystemOutAndErr() {
    if (reader != null) return; // did this already

    x30 _this = new x30();
    
    if (verbose) print("Redirecting System.out");
    try
    {
      PipedOutputStream pout=new PipedOutputStream(pin);
      oldOut = System.out;
      TeeOutputStream tee = new TeeOutputStream(oldOut, pout);
      System.setOut(new PrintStream(tee,true));
    }
    catch (Exception io)
    {
      System.err.println("Couldn't redirect STDOUT - " + io.getMessage());
    }

    if (verbose) System.out.println("Redirecting System.err");
    try
    {
      PipedOutputStream pout2=new PipedOutputStream(pin2);
      oldErr = System.err;
      TeeOutputStream tee = new TeeOutputStream(oldErr, pout2);
      System.setErr(new PrintStream(tee,true));
    }
    catch (Exception io)
    {
      System.err.println("Couldn't redirect STDERR - " + io.getMessage());
    }

    if (verbose) System.out.println("Redirects done. Starting readers");
    
    // Starting two seperate threads to read from the PipedInputStreams
    //
    reader=new Thread(_this, "StdOut Piper");
    reader.setDaemon(true);
    reader.start();
    //
    reader2 = new Thread(_this, "StdErr Piper");
    reader2.setDaemon(true);
    reader2.start();
  }

  static Appendable customSystemOut;
  static StringBuffer outBuf; // optional all-logging
  
  static new ArrayDeque<S> systemInBuf;

  static void appendText(String s, boolean outNotErr) {
    if (empty(s)) ret;
    // We do this with a TeeOutputStream now (safer).
    // (outNotErr ? oldOut : oldErr).print(s);
    
    if (console != null)
      console.appendText(s, outNotErr);
    if (outBuf != null)
      outBuf.append(s);
    if (customSystemOut != null)
      try {
        customSystemOut.append(s);
      } catch (IOException e) {
        printStackTrace(e);
      }
  }

  /*static String readLine(PipedInputStream in) throws IOException
  {
    new StringBuilder input;
    do
    {
      int available=in.available();
      if (available==0) break;
      byte b[]=new byte[available];
      in.read(b);
      S s = new String(b,0,b.length);
      input.append(s);
      if (s.contains("\n")) break;
    // }while( !input.endsWith("\n") &&  !input.endsWith("\r\n") && !quit);
    } while (!quit);
    return str(input);
  }*/
  
  static void autoReportToChat() {
    if (customSystemOut == null) {
      print("Auto-reporting to chat.");
      customSystemOut = new Appendable() {
        LineBuf buf = new LineBuf();
        
        // only using this one
        public Appendable append(CharSequence cs) {
          buf.append(cs.toString());
          while (true) {
            String s = buf.nextLine();
            if (s == null) break;
            reportToChat(s, true);
          }
          return this;
        }
    
        public Appendable append(char c) { return this; }
        public Appendable append(CharSequence s, int start, int end) { return this; }
      };
     }
  }

static void reportToChat(final String s, boolean silent) {
    if (s == null || s.length() == 0) return;
    if (!silent)
      print("reportToChat: " + quote(s));
    reportToChat_getChatThread().add(new Runnable() {
    public void run() { try {
        startChatServerIfNotUp();
        waitForChatServer();
        chatSend(s);
    } catch (Exception __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}});
   }
  
  static Q reportToChat_q;
  
  static Q reportToChat_getChatThread() {
    if (reportToChat_q == null)
      reportToChat_q = new Q("reportToChat");
    return reportToChat_q;
  }
  
  static void startChatServerIfNotUp() {
    if (portIsBound(9751)) {
      //print("Chat seems to be up.");
    } else {
      nohupJavax("1000867");
      print("Chat server should be coming up any minute now.");
    }
  }
  
  static void waitForChatServer() {
    if (!portIsBound(9751)) {
      //System.out.print("Waiting for chat server... ");
      do {
        sleep(1000);
      } while (!portIsBound(9751));
      //print("OK.");
    }
  }

  static boolean portIsBound(int port) {
    try {
      ServerSocket s = new ServerSocket(port);
      s.close();
      return false;
    } catch (IOException e) {
      return true;
    }
  }
  
  static class LineBuf {
    StringBuffer buf = new StringBuffer();
    
    void append(String s) {
      buf.append(s);
    }
    
    String nextLine() {
      int i = buf.indexOf("\n");
      if (i >= 0) {
        String s = buf.substring(0, i > 0 && buf.charAt(i-1) == '\r' ? i-1 : i);
        buf.delete(0, i+1);
        return s;
      }
      return null;
    }
  } // LineBuf

  !include #1000937 // chatSend

  static class TeeOutputStream extends OutputStream {
    
    protected OutputStream out, branch;

    public TeeOutputStream( OutputStream out, OutputStream branch ) {
      this.out = out;
      this.branch = branch;
    }

    @Override
    public synchronized void write(byte[] b) throws IOException {
      write(b, 0, b.length);
    }

    @Override
    public synchronized void write(byte[] b, int off, int len) throws IOException {
      //if (verbose) oldOut.println("Tee write " + new String(b, "UTF-8"));
      out.write(b, off, len);
      this.branch.write(b, off, len);
    }

    @Override
    public synchronized void write(int b) throws IOException {
      write(new byte[] {(byte) b});
    }

    /**
     * Flushes both streams.
     * @throws IOException if an I/O error occurs
     */
    @Override
    public void flush() throws IOException {
      out.flush();
      this.branch.flush();
    }

    /**
     * Closes both streams.
     * @throws IOException if an I/O error occurs
     */
    @Override
    public void close() throws IOException {
      out.close();
      this.branch.close();
    }
  }
  
  static boolean isChatServer(String[] args) {
    for (int i = 0; i < args.length; i++)
      if (isSnippetID(args[i]))
        return parseSnippetID(args[i]) == 1000867;
    return false;
  }
  
  static void listUserThreadsWithStackTraces() {
    print("");
    Map<Thread, StackTraceElement[]> threadMap = Thread.getAllStackTraces();
    int n = 0;
    for (Thread t : threadMap.keySet()) {
      ThreadGroup g = t.getThreadGroup();
      if (g != null && g.getName().equals("system")) continue;
      ++n;
      print(t);
      for (StackTraceElement e : threadMap.get(t)) {
        print("  " + e);
      }
      print("");
    }
    print(n + " user threads.");
  }
  
  static void makeVMAndroid() {
    Android3 a = new Android3("This is a JavaX VM.");
    a.responder = new Responder() {
      S answer(S s, L<S> history) {
        return x30.answer(s, history);
      }
    };
    a.daemon = true;
    a.console = false;
    a.incomingSilent = true; // to avoid too much printing
    a.useMultiPort = false;
    a.quiet = true;
    makeAndroid3(a);
    
    new MultiPort; // auto-add multi-port
    
    sendOptInNewThreadQuietly("VM Lister.", "started vm * *", vmPort(), vmID());
  }
  
  static class Info {
    S programID;
    S[] programArgs;
    File transpiledSrc;
    Class mainClass;
  }
  
  static new Info info; // hmm...
  
  // injectable info
  
  static new L<PaA> injectable_programsInjected;
  static boolean injectable_on = true;
  static class PaA { // "Program and Arguments"
    S injectionID;
    S progID;
    S[] arguments;
    transient Class mainClass; // TODO: release eventually...
    transient WeakReference<Thread> mainThread;
    volatile boolean mainDone;
    transient volatile Throwable exception;
    S srcMD5;
    
    *(S *progID, S[] *arguments) {}
    *() {}
  }
  
  static S vmID = makeRandomID(10);
  
  static synchronized void addInjection(PaA paa) {
    injectable_programsInjected.add(paa);
  }
  
  static synchronized void removeInjection(PaA paa) {
    cleanUp(paa.mainClass);
    injectable_programsInjected.remove(paa);
  }
  
  static synchronized L<PaA> getInjections() {
    ret cloneList(injectable_programsInjected);
  }
  
  static S answer(S s, L<S> history) ctex {
    new Matches m;
    
    if (match3("kill!", s)) {
      cleanKill();
      return "ok";
    }
    if (match3("restart", s)) {
      cleanRestart(fullArgs);
      ret "ok";
    }
    if (match3("What is your process ID?", s) || match3("what is your pid?", s))
      return getPID();
    if (match3("get vm id", s))
      return vmID;
    if (match3("what is the javax program id?", s))
      return javaxProgramID;
    if (match3("what is the main program id?", s))
      return info.programID;
    if (match3("what are your program arguments?", s))
      return structure(info.programArgs);
    if (match3("get output", s))
      return outBuf != null ? quote(outBuf.toString()) : "Not logged";
    if (match3("get output length", s))
      return outBuf != null ? "" + outBuf.length() : "Not logged";
    if (match3("get output substring from *", s, m))
      return quote(outBuf.substring(parseInt(m.get(0))));
    if (match3("get output substring from * to *", s, m))
      return quote(outBuf.substring(parseInt(m.get(0)), parseInt(m.get(1))));
    if (match3("clear output", s)) {
      synchronized(x30.class) {
        int len = outBuf.length();
        outBuf.setLength(0);
        outBuf.trimToSize();
        return "OK, deleted " + len + " chars.";
      }
    }
    if (match3("send line * to system.in", s, m)) {
      S line = m.unq(0);
      synchronized(x30.class) {
        systemInBuf.add(line);
      }
      ret "ok";
    }
    if (match3("get fields of main class", s))
      return structure(listFields(info.mainClass));
    if (match3("get field * of main class", s, m))
      return structure(get(info.mainClass, m.m[0]));
    if (match3("invoke function * of main class", s, m))
      return structure(call(info.mainClass, m.m[0]));
    if (match3("set field * of main class to *", s, m)) {
      set(info.mainClass, m.m[0], unstructure(m.m[1]));
      return "ok";
    }
    if (match3("how much memory are you consuming", s))
      return "Java heap size: " + (Runtime.getRuntime().totalMemory()+1024*1024-1)/1024/1024 + " MB";
    if (match3("how much memory is used after GC?", s)) {
      gc();
      return "Java heap used: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()+1024*1024-1)/1024/1024 + " MB";
    }
    if (match3("how much memory is used?", s))
      return "Java heap used: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()+1024*1024-1)/1024/1024 + " MB";
      
    if (match3("please run program *", s, m) || match3("please run program * with arguments *", s, m)) {
      final S progID = $1;
      final S[] arguments = m.m.length > 1 ? toStringArray(unstructure($2)) : new S[0];
      final PaA paa = preInjectProgram(progID, arguments);
      // program runs in same thread
      paaRun(paa);
      Throwable error = paa.exception;
      O result = getOpt(paa.mainClass, "result");
      removeInjection(paa);
      ret error != null ? "Runtime Error: " + getStackTrace(error) : "OK " + struct(result);
    }
    
    if (match3("please inject program *", s, m) || match3("please inject program * with arguments *", s, m)) {
      final S progID = $1;
      final S[] arguments = m.m.length > 1 ? toStringArray(unstructure($2)) : new S[0];
      final PaA paa = preInjectProgram(progID, arguments);
      // program may run in its own thread.
      thread progID { paaRun(paa); }
      ret format3("OK. Injection ID: *", paa.injectionID);
    }
    
    if (match3("get injection exception *", s, m)) {
      S injectionID = unquote(m.m[0]);
      PaA paa = findInjection(injectionID);
      if (paa == null)
        ret "Sorry. Injection not found";
      ret "OK: " + paa.exception == null ? "no exception" : getStackTrace(paa.exception);
    }
     
    if (match3("get injection * variable *", s, m)) {
      S injectionID = unquote(m.m[0]);
      S var = unquote(m.m[1]);
      PaA paa = findInjection(injectionID);
      if (paa == null)
        ret "Sorry. Injection not found";
      ret "OK: " + structure(getOpt(paa.mainClass, var));
    }
     
    if (match3("get injection result *", s, m)) {
      S injectionID = unquote(m.m[0]);
      PaA paa = findInjection(injectionID);
      if (paa == null)
        ret "Sorry. Injection not found";
      ret "OK: " + structure(getOpt(paa.mainClass, "result"));
    }
     
    if (match3("is injection's * main done", s, m)) {
      S injectionID = unquote(m.m[0]);
      PaA paa = findInjection(injectionID);
      if (paa == null)
        ret "Sorry. Injection not found";
      ret paa.mainDone ? "Yes." : "No.";
    }
    
    if (match3("get injections", s, m)) {
      ret structure(getInjections());
    }
    
    if (match3("remove injection *", s, m)) {
      S injectionID = unquote(m.m[0]);
      PaA paa = findInjection(injectionID);
      if (paa == null)
        ret "Sorry. Injection not found";
      removeInjection(paa);
      ret "OK, removed.";
    }
    
    if (match3("which programs are you running (ids only)", s, m)) {
      synchronized(x30.class) {
        new L<S> l;
        for (S progID : instances.keySet())
          if (getInstance(progID) != null)
            l.add(progID);
        return format3("these: *", structure(l));
      }
    }
    
    if "show console"
      ret _showConsole();

    if "hide console" {
      if (console != null) {
        console.hideConsole();
        ret "ok";
      }
      ret "no console";
    }
     
    L multiPorts = getMultiPorts();
    if (!multiPorts.isEmpty()) {
      O multiPort = multiPorts.get(0);
      S answer = makeResponder(multiPort).answer(s, history);
      if (answer != null) ret answer;
    }
    
    if "list bots"
      ret structure(litmap());
      
    if "get last files written"
      ret structure(cloneList(lastFilesWritten));
      
    if "dump heap to *" {
      dumpHeap(new File(assertAbsolutePath($1)));
      ret "OK";
    }
    
    if "prepare for javax upgrade" {
      loadMyClasses();
      ret "OK";
    }
    
    if "invoke ecj on options file *" {
      long time = sysNow();
      print("Invoking ECJ: " + $1);
      S answer = "ok " + invokeEcj(new File($1));
      done2(time, "ecj");
      ret answer;
    }
    
    // These don't show or hide any windows; it's just a flag
    // that says whether the VM is allowed to run without any
    // windows.
    
    // This does hide the console actually
    if "hidden vm yes" {
      hideVM();
      ret "OK";
    }
    
    if "hidden vm no" {
      hiddenVM = false;
      sendOpt("VM Lister.", "unhiding vm " + vmPort());
      ret "OK";
    }
    
    if "is hidden vm" ret yn(hiddenVM);
    
    if "vm start date" ret str(vmStarted);
    
    if "gc" ret "OK" with timedGC();
    
    if "all loaded program jars" ret struct(allLoadedProgramJars());
    if "all loaded library jars" ret struct(allLoadedLibraryJars());
    
    if "stack traces"
      ret renderAllThreadsWithStackTraces();
    
    null;
  }
  
  static void loadMyClasses() {
    for (S name : classNamesInJarOrDir(javaxJarPath()))
      pcall-silent { Class.forName(name); }
  }
  
  static synchronized PaA findInjection(S injectionID) {
    for (PaA paa : injectable_programsInjected)
      if (eq(paa.injectionID, injectionID))
        ret paa;
    ret null;
  }
  
  static new AtomicBoolean readLine_used;
  static BufferedReader readLine_reader;
  static bool readLine_noReadLine;
  sbool quietLoading, verboseIllegal;
  
  static synchronized S pollSystemInBuf() {
    ret systemInBuf.poll();
  }

  static S readLine() {
    ret readLine(false);
  }
  
  static S readLine(bool quiet) ctex {
    if (!readLine_used.compareAndSet(false, true))
      throw fail("readLine is in use.");
    try {
      while (true) {
        S s = pollSystemInBuf();
        if (s != null) {
          print(s);
          ret s;
        }
        
        if (readLine_reader == null)
          readLine_reader = new BufferedReader(new InputStreamReader(System.in, "UTF-8")); // XX - is that right?
          
        if (!readLine_reader.ready())
          sleep(100);
        else {
          s = readLine_reader.readLine();
          if (s != null) {
            if (!quiet) print(s);
            ret s;
          }
        }
      }
    } finally {
      readLine_used.set(false);
    }
  }
  
  static new HashMap<S, WeakReference> weakrefs;
  // static new WeakIdentityHashMap<O, S> reverseWeakrefs;
  static long weakRefCounter;
  
  // TODO: lookup in reverse map
  static synchronized S weakref(O o) {
    if (o == null) ret "null";
    S id = vmID + "/" + ++weakRefCounter;
    weakrefs.put(id, new WeakReference(o));
    ret id;
  }

  static synchronized O getRef(S id) {
    // TODO: clean up the list some time
    
    WeakReference ref = weakrefs.get(id);
    if (ref == null) ret null;
    ret ref.get();
  }
  
  static new L<O> multiPorts;
  
  static synchronized L<O> getMultiPorts() {
    ret cloneList(multiPorts);
  }
  
  // true if you're the first one
  static synchronized boolean addMultiPort(O o) {
    multiPorts.add(o);
    /*if (multiPorts.size() == 1) {
      thread "keep alive" { x30.sleep(); } // keep VM alive since there is a multiport
    }*/
    ret multiPorts.size() == 1;
  }
  
  static synchronized void removeMultiPort(O o) {
    multiPorts.remove(o);
  }
  
  static S getInjectionID(Class mainClass) {
    L<PaA> l = getInjections();
    for (PaA injection : l)
      if (injection.mainClass == mainClass)
        ret injection.injectionID;
    ret null;
  }
  
  static S getInjectionID() { return null; } // used somewhere...
  
  static void sleep(long ms) {
    try {
      Thread.sleep(ms);
    } catch (Exception e) { throw new RuntimeException(e); }
  }

  static void sleep() ctex {
    print("Sleeping.");
    synchronized(x30.class) { x30.class.wait(); }
  }
  
  static Map<Class, S> classToSourceCode = newWeakHashMap();
  
  synchronized static void registerSourceCode(Class c, S src) {
    classToSourceCode.put(c, src);
  }
  
  synchronized static S getSourceCodeForClass(Class c) {
    ret classToSourceCode.get(c);
  }
  
  static void autoScroll(boolean on) {
    if (console == null) ret;
    console.autoScroll = on;
    if (on) console.scrollToBottomLater();
  }
  
  // get main class of first program (for console)
  static Class getMainMainClass() {
    PaA paa = first(getInjections());
    ret paa == null ? null : paa.mainClass;
  }
  
  // program ID of first program loaded
  static S mainProgramID() {
    PaA paa = first(getInjections());
    ret paa == null ? null : paa.progID;
  }
  
  static bool _inCore() {
    ret true;
  }
  
  static int cleanKillTimeout = 60000;
  static volatile bool killing;
  sbool preKill_verbose = false;
  sbool sendKillNotice = false;
  
  static void preKill() {
    if (killing) ret;
    killing = true;
    directNohupJava_loggingOn = false;
    final new Flag flag;
    thread "Cleaning" {
      try {
        new HashSet<Class> cleanedUp;
        if (preKill_verbose) print("Cleaning up injections");
        for (PaA p : getInjections())
          if (cleanedUp.add(p.mainClass))
            preKill_cleanUp(p.mainClass);
        if (preKill_verbose) print("Cleaning up main classes");
        for (Class c : allMainClasses())
          if (cleanedUp.add(c)) preKill_cleanUp(c);
        if (preKill_verbose) print("Clean-up done");
      } finally {
        flag.raise();
      }
    }
    if (sendKillNotice) {
      if (verbose || preKill_verbose) print("Sending kill notice to vm lister");
      evalWithTimeout(1.0, r { sendOptQuietly("VM Lister.", "killing vm " + vmPort()) });
    }
    if (verbose) print("Waiting for clean kill flag with timeout " + cleanKillTimeout);
    flag.waitUntilUp(cleanKillTimeout);
    if (verbose) print("Clean kill flag up (or timeout)");
  }
  
  svoid preKill_cleanUp(O o) {
    S programID = null;
    if (preKill_verbose) {
      programID = (S) getOpt(o, 'programID);
      print("prekill: Cleaning " + o + " (" + programID + ")");
    }
    cleanUp(o);
    if (preKill_verbose)
      print("prekill: Cleaned " + programID);
  }
  
  static void cleanRestart(final S[] args) {
    print("\nClean-restarting myself.");
    thread "Clean Restart" {
      preKill();
      nohupJavax(smartJoin(args), fullVMArguments());
      System.exit(0);
    }
  }
  
  static void cleanKill() {
    print("\nClean-killing myself");
    thread "Clean Kill" {
      preKill();
      System.exit(0);
    }
  }
  
  static void pauseAll(bool b) {
    for (PaA injection : getInjections())
      setOpt(injection.mainClass, "ping_pauseAll", b);
  }
  
  static Class mc() { return x30.class; }
  static Class getMainClass() { return x30.class; }

  static Class getMainClass(O o) ctex {
    ret (o instanceof Class ? (Class) o : o.getClass()).getClassLoader().loadClass("main");
  }
  
  static class FileAccess {
    S path;
    long time;
    
    *(S *path) { time = now(); }
  }
  
  static L<FileAccess> lastFilesWritten = synchroList();
  static int lastFilesWritten_max = 5;
  
  static void registerIO(O o, S path, bool forWriting) {
    if (forWriting)
      recordWriteAccess(path);
  }
  
  static void recordWriteAccess(S path) {
    synchronized(lastFilesWritten) {
      trimListToSizeInFront(lastFilesWritten, lastFilesWritten_max);
      lastFilesWritten.add(new FileAccess(new File(path).getAbsolutePath()));
    }
  }
  
  static L<S> lastURLsOpened = synchroList();
  static int lastURLsOpened_max = 10;
  static volatile long urlsOpenedCounter;
  
  static void recordOpenURLConnection(S url) {
    url = hideCredentials(url);
    synchronized(lastURLsOpened) {
      urlsOpenedCounter++;
      trimListToSizeInFront(lastURLsOpened, lastURLsOpened_max);
      lastURLsOpened.add(url);
    }
  }
  
  static void dropIO(O o) {}
  
  // for the "assist" button
  static Class include(S progID) {
    Class c = hotwire(progID);
    setOpt(c, "programID", mainProgramID());
    ret c;
  }
  
  static void reRunMain {
    final PaA paa = first(getInjections());
    if (paa != null) {
      if (!paa.mainDone) print("Main still running");
      else {
        paa.mainDone = false;
        S progID = paa.progID;
        thread progID {
          print();
          paaRun(paa);
          System.out.println(paa.exception != null ? "[main done with error]" : "[main done]");
        }
      }
    }
  }
  
  !include #1000963 // hotwire
  !include #1001372 // nohupJavax
  
  volatile sbool consoleUpdateOff;
  
  // static Map<Class, L<Thread>> mainThreads = newWeakHashMap();
  
  static synchronized PaA preInjectProgram(S progID, S[] arguments) {
    progID = fsI(progID);
    PaA paa = new PaA(progID, arguments);
    paa.injectionID = randomID(8);
    addInjection(paa);

    // better call JavaX for translation in a single thread.
    paa.mainClass = hotwire(progID);
    ret paa;
  }
  
  please include function startDeadlockDetector.
  
  static Map<Class, Bool> allMainClasses = newWeakHashMap();
  
  static void registerAMainClass(Class c) {
    allMainClasses.put(c, Boolean.TRUE);
  }
  
  static L<Class> allMainClasses() {
    ret asList(keys(cloneMap(allMainClasses)));
  }
  
  svoid hideVM {
    hiddenVM = true;
    sendOpt("VM Lister.", "hiding vm " + vmPort());
    hideConsole();
  }
  
  static void vmKeep_store(S programID, S programMD5, S var, S structure) {
    lock vmKeep_lock;
    programID = fsI(programID);
    VMKeep data = vmKeep.get(programID);
    if (data == null || neq(data.programMD5, programMD5))
      vmKeep.put(programID, data = nu(VMKeep, +programMD5));
    data.vars.put(var, structure);
  }
  
  static S vmKeep_get(S programID, S programMD5, S var) {
    lock vmKeep_lock;
    programID = fsI(programID);
    VMKeep data = vmKeep.get(programID);
    if (data == null) null;
    if (neq(data.programMD5, programMD5)) {
      vmKeep.remove(programID);
      null;
    }
    ret data.vars.get(var);
  }
  
  static JButton consoleButton(S text) {
    ret setHorizontalMargin(0, jbutton(text));
  }
  
  // value = true (dangerous) or init function (dangerous) or false (not dangerous)
  static WeakIdentityHashMap<Map, O> weakMaps;
  static bool weakMaps_initing, weakMaps_debug;
  
  static <A extends Map> A _registerWeakMap(A o) {
    ret _registerWeakMap(o, false);
  }
  
  static <A extends Map> A _registerDangerousWeakMap(A o) {
    ret _registerWeakMap(o, true);
  }
  
  static <A extends Map> A _registerDangerousWeakMap(A o, O init) {
    ret _registerWeakMap(o, init == null ? true : init);
  }
  
  static <A extends Map> A _registerWeakMap(A o, O init) {
    if (weakMaps == null) {
      if (weakMaps_debug) print("_registerWeakMap init cycle");
      if (weakMaps_initing) ret o;
      weakMaps_initing = true;
      weakMaps = newWeakIdentityHashMap();
      weakMaps_initing = false;
    }
    
    synchronized(weakMaps) {
      if (weakMaps_debug) print("_registerWeakMap adding " + getClassName(o) + ", size=" + l(weakMaps));
      O result = weakMaps.put(o, init);
      if (weakMaps_debug) print("_registerWeakMap added. " + result + ", size=" + l(weakMaps));
    }
    ret o;
  }
  
  static void cleanWeakMaps() { pcall {
    new L<Map> maps;
    new Map<Map, O> dangerousMaps;
    if (weakMaps == null) ret;
    synchronized(weakMaps) {
      for (Map map : keys(weakMaps)) { // This cleans the weakMaps map itself
        O init = weakMaps.get(map);
        if (eq(init, false)) maps.add(map);
        else dangerousMaps.put(map, init);
      }
      if (weakMaps_debug) print("cleanWeakMaps: got " + l(maps));
    }
    for (Map o : maps) pcall { cleanWeakMap(o); }
    for (Map o : keys(dangerousMaps)) pcall {
      synchronized(o) {
        o.clear();
        O init = dangerousMaps.get(o);
        //print("Calling init on dangerous map: " + init + " / " + className(o));
        if (!init instanceof Bool)
          callF(init, o);
      }
    }
  }}
  
  svoid setVarDialog {
    final Class c = getMainMainClass();
    final JComboBox cb = jcombobox(staticFieldNames(c));
    final JTextField tf = jtextfield();
    showFormTitled("Set variable",
      "Name", cb,
      "Value", tf, func {
      try {
        S var = getSelectedItem(cb), value = getText(tf);
        set(c, var, unstructure(value));
        infoBox("Variable " + var + " set to " + value + "!");
      } catch e {
        messageBox(e);
        false;
      }
      null;
    });
  }
  
  please include function findTranslators. // 1006722 calls this
  
  // VM-wide FAST string interning (need to expose to programs per interface for more speed)
  please include function internPerProgram.
  
  please include function setGCFrequency.
  please include function noRegularGC.
  
  please include function myTranspilationDate.

  // for programs:
  please include function newWeakHashMap.
  please include function print_append.
  please include function get.
  please include function getOpt.
  please include function callF.
  please include function call.
  please include function fixNewLines.
  
  !include once #1016423 // _handleError (core version)
  
  //please include function substance.
  
  static Thread _registerThread(Thread t) { ret t; }
  svoid _registerThread() {}
  static Thread _unregisterThread(Thread t) { ret t; }
  svoid _unregisterThread() {}
  static Map<Thread, Bool> _registerThread_threads;
  
  svoid failIfUnlicensed() {}

  sS _showConsole() {  
    if (console == null) tryToOpenConsole(new S[0]);
    if (console != null) {
      console.showConsole();
      ret "ok";
    }
    ret "no console";
  }
  
  please include function loadNativeLibrary.
}

Author comment

Began life as a copy of #1001600

download  show line numbers  debug dex   

Travelled to 18 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, ddnzoavkxhuk, gwrvuhgaqvyk, irmadwmeruwu, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, nysugtttblhj, omdjrrnzbjjv, onxytkatvevr, podlckwnjdmb, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, xrpafgyirdlv

No comments. add comment

Snippet ID: #1001638
Snippet name: x30.java (JavaX)
Eternal ID of this version: #1001638/348
Text MD5: 486673d197db8d04b6474230117d92e6
Transpilation MD5: 80f503f0cd7cfe53323826e1f688e955
Author: stefan
Category: javax
Type: JavaX module (desktop)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2020-07-21 15:19:37
Source code size: 98122 bytes / 2908 lines
Pitched / IR pitched: No / No
Views / Downloads: 2990 / 7602
Version history: 347 change(s)
Referenced in: [show references]

Formerly at http://tinybrain.de/1001638 & http://1001638.tinybrain.de