please include function myInnerClasses.

// TODO: inherit from DynamicObject?
static abstract class DynModule {
  // we really shouldn't have used up these variable names
  // (subclasses can't overload them due to how the persistence works)
  S name, toolTip; // module name, module tool tip in task bar
  
  PersistableThrowable _error;
  Map<S, Collection<S>> mechLists;
  Map<S, Bool> _persistenceInfo;
  
  transient O _host;
  transient Map timers = newWeakHashMap(); // all kinds of resources actually; value is closer helper
  transient Set<AutoCloseable> _resources = synchroHashSet();
  transient Lock lock; // set by stem
  transient bool persistOnChangedField = true;
  transient int changeCount;
  transient O changeCountSync = new O;
  transient L onChange;
  transient L onTransientChange;
  transient bool verboseTimers;
  transient ReliableSingleThread rstUpdate;
  transient Set<S> componentFieldsToKeep;
  transient Map<S, O> transientGeneralMap = synchroHashMap();
  transient Q q; // module command queue
  transient L onFieldChange; // also for non-persistent fields. L<voidfunc(S field)>
  transient bool _hasPreviousBounds;
  transient new StringBuffer _printLog;
  transient bool deleted;
  
  *() {
    dm_initErrorHandling();
    setMainDesktopPane((JDesktopPane) getCreatorOpt('desktop));
  }
  
  bool isVisible() { ret isTrue(getOpt(_host, 'visible)); }
  
  S moduleName() { ret name; }
  
  void setModuleName(S name) {
    S oldName = this.name;
    if (!eq(name, oldName)) {
      setField(+name);
      possiblyInternalFrameTitle(vis(), name);
      vmBus_send('moduleNameChange, this, oldName, name);
    }
  }
  
  void setModuleToolTip(S toolTip) {
    this.toolTip = toolTip;
  }
  
  JComponent vis() {
    ret (JComponent) getOpt(_host, 'vis);
  }
  
  <A extends AutoCloseable> A ownResource(A a) {
    if (a != null)
      _resources.add(a);
    ret a;
  }
  
  <A> A ownTimer(A timer) {
    if (timer instanceof AutoCloseable) ownResource((AutoCloseable) timer);
    ownTimer(timer, f cancelTimerOrInterruptThread);
    ret timer;
  }
  
  void ownTimer(O timer, O closerHelper) {
    timers.put(timer, closerHelper);
  }
  
  void singleTimer(java.util.Timer timer) {
    stopAllTimers();
    ownTimer(timer);
  }
  
  void stopAllTimers() {
    for (AutoCloseable resource : getAndClearList(_resources)) {
      if (verboseTimers)
        print("Releasing resource: " + resource);
      pcall { resource.close(); }
    }
      
    for (O timer, closerHelper : getAndClearMap(timers)) {
      if (verboseTimers)
        print("Stopping timer: " + closerHelper + " / " + timer);
      pcallFInRealOrMyMC(this, closerHelper, timer);
    }
  }
  
  bool isDeleted() { ret deleted; }
  
  void cleanMeUp_dynModule {
    set deleted;
    stopAllTimers();
  }
  
  void persistMe {
    synchronized(changeCountSync) { ++changeCount; }
    pcallFAll(onChange); callOpt(_host, '_change);
    updateMe();
  }
  
  void fireChangeWithoutPersisting {
    pcallFAll(onChange);
  }
  
  void fireTransientChange {
    pcallFAll(onTransientChange);
  }
  
  void _change { persistMe(); }
  void change { persistMe(); }
  
  void updateMe {
    rstUpdate().trigger();
  }
  
  void changeAndUpdate { _change(); updateMe(); }
  
  bool setField(S name, O value) enter {
    pcall { // some really weird classes fail on equals() (BCEL JavaClass, I'm looking at you)
      if (eq(get(this, name), value)) false;
    }
    ret setField_noCheck(name, value);
  }
    
  // always set even if equal
  bool setField_noCheck(S name, O value) enter {
    set(this, name, value);
    pcallFAll(onFieldChange, name);
    if (persistOnChangedField
      //&& !isTransientField(this, name)
      && !isFalse(mapGet(_persistenceInfo, name)))
      _change();
    true;
  }
  
  <A> A setFieldAndReturn(S name, A value) {
    setField(name, value);
    ret value;
  }
  
  bool setFields(O... params) {
    bool change = false;
    for (int i = 0; i < l(params); i += 2)
      if (setField((S) params[i], params[i+1])) change = true;
    ret change;
  }
  
  void start {
    _hasPreviousBounds = dm_getBounds() != null;
    if (hasMethod_onTypes(this, 'onTopInput, S))
      dm_onTopInput_q(voidfunc(S s) { call(module(), 'onTopInput, s) });
  }
  
  void revisualize {
    call(creator(), 'revisualizeModule, _host);
  }
  
  AutoCloseable enter() {
    please include function dm_currentModule.
    AutoCloseable c = tempSetThreadLocal(dm_currentModule_value, new WeakReference(this));
    fO realMC = getMainClass(this);
    if (printToModule())
      c = combineAutoCloseables(c, tempInterceptPrintIfNotIntercepted(_printToModuleInterceptor()));
    if (realMC != mc()) {
      if (printToModule())
        c = combineAutoCloseables(c, (AutoCloseable) callOpt(realMC, 'tempInterceptPrintIfNotIntercepted, _printToModuleInterceptor()));
      else
        c = combineAutoCloseables(c, tempInterceptPrintIfNotIntercepted(func(S s) -> bool {
          ret false with call(realMC, 'print, s);
        }));
      c = combineAutoCloseables(c, tempSetTL(realMC_tl(), realMC));
    }
    ret c;
  }
  
  F1<S, Bool> _printToModuleInterceptor() {
    ret func(S s) -> bool {
      if (print_preprocess != null) s = (S) callF(print_preprocess, s);
      s = fixNewLines(s);
      Appendable loc = _printLog;
      Appendable buf = print_log;
      int loc_max = print_log_max;
      if (buf != loc && buf != null) {
        print_append(buf, s, print_log_max);
        loc_max = local_log_max;
      }
      if (loc != null) 
        print_append(loc, s, loc_max);
      System.out.print(s);
      false;
    };
  }
  
  bool printToModule() { true; } // on by default now
  
  AutoCloseable enterAndLock() {
    ret combineAutoCloseables(enter(), tempLock(lock));
  }
  
  bool setError(Throwable e) {
    setField(_error := persistableThrowable(e));
    true;
  }
  
  void clearError {
    setField(_error := null);
  }
  
  void onChange(Runnable r) {
    lock lock;
    if (onChange == null) onChange = synchroList();
    addIfNotThere(onChange, r);
  }
  
  void onFieldChange(VF1<S> r) {
    lock lock;
    if (onFieldChange == null) onFieldChange = synchroList();
    addIfNotThere(onFieldChange, r);
  }
  
  void onChangeAndNow(Runnable r) {
    onChange(r);
    callF(r);
  }
  
  // forward to main class
  void onChangeAndNow(JComponent c, O r) {
    onUpdateAndNow(c, r);
  }
  
  /*static*/ <A extends JComponent> A onChange(A tc, O r) {
    onUpdate(tc, r);
    ret tc;
  }

  void update() {}
  
  void unvisualize() {
    zeroAllFieldsOfTypeExcept(this, Component, componentFieldsToKeep);
  }
  
  // return L<AbstractAction> (legacy)
  // or litobjectarray(text, Runnable, text, Runnable, ...)
  O menuItems() {
    null;
  }
  
  void enhanceFrame(Container f) {
    ifndef NoMenuItems
    O items = menuItems();
    if (items instanceof O[])
      items = paramsToAbstractActions((O[]) items);
    internalFramePopupMenuFromActions_threaded(f, (L) items);
    endifndef
    ifndef NoSwitchableFields
    S switchableFields = cast callOpt(this, 'switchableFields);
    Set<S> set = asLinkedHashSet(splitAtSpace(switchableFields));
    new Matches m;
    for (S field : allFields(this))
      if (startsWith(field, "_switchableField_", m))
        set.add(m.rest());
    for (S field : set) {
      Class type = fieldType(this, field);
      if (eq(bool.class, type))
        dm_boolFieldMenuItem(f, field);
      else if (eq(int.class, type))
        dm_intFieldMenuItem(f, field);
      else if (eq(long.class, type))
        dm_longFieldMenuItem(f, field);
      else if (eq(double.class, type))
        dm_doubleFieldMenuItem(f, field);
      else if (eq(S, type))
        dm_stringFieldMenuItem(f, field);
      else if (eq(File.class, type))
        dm_fileFieldMenuItem(f, field);
    }
    endifndef
  }
  
  // assume this is called in start(), so no locking
  ReliableSingleThread rstUpdate() {
    if (rstUpdate == null) rstUpdate = dm_rst(this, r enter { update(); });
    ret rstUpdate;
  }
  
  Q q() enter {
    if (q == null) {
      lock lock;
      if (q == null) q = dm_startQ();
    }
    ret q;
  }
  
  <A> A withUpdate(A a) {
    rstUpdate().trigger();
    ret a;
  }
  
  DynModule module() { this; }
  DynModule me() { this; }
  
  JComponent visualize() {
    ret dm_noVisualisation();
  }
  
  S programTitle_cache;
  S programTitle() {
    if (programTitle_cache == null)
      programTitle_cache = snippetTitle(programID(mainClass(this)));
    ret programTitle_cache;
  }
  
  // field must be non-transient
  void doPersist(S field) {
    _persistenceInfo = mapMinus(_persistenceInfo, field);
  }
  
  void dontPersist(S field) {
    _persistenceInfo = mapPlus(_persistenceInfo, field := false);
  }
  
  Appendable _actualPrintLog() { ret printToModule() ? _printLog : print_log; }
}

static void _registerTimer(java.util.Timer timer) {
  _registerTimer_original(timer);
  dm_currentModule().ownTimer(timer);
}

!include once #1023504