!7 sinterface IBEAReactor { BEAObject uniqResult(Class c, O... params); } extend BEAObject { bool canReactWith(BEAObject o) { ret isInstanceOfAny(o, (Cl) reactableWithClasses()); } Cl> reactableWithClasses() { ret (Cl) collectFirstMethodArguments(findMethodsNamed_nonSynthetic(this, "reactWith")); } } beaConcept BReactor {} beaConcept BInput { S text; toString { ret "[\*id*/] Input " + quote(text); } } beaConcept BOutput { S text; toString { ret "[\*id*/] Output " + quote(text); } } beaConcept BPattern { S text; toString { ret "[\*id*/] " + shortDynName(this) + " " + quote(text); } void reactWith(BInput input, IBEAReactor reactor) {} @Override Map _fieldMigrations() { ret litmap(pattern := new FieldMigration("text")); } } beaConcept BStarPattern > BPattern { void reactWith(BInput input, IBEAReactor reactor) { LS mapping = matchesToStringList(flexMatchIC_first(text, input.text)); if (mapping == null) ret; reactor.uniqResult(BEAObject, type := "match", +input, pattern := this, +mapping); } } beaConcept BAngleVarPattern > BPattern { void reactWith(BInput input, IBEAReactor reactor) { SS mapping = flexMatchAngleBracketVarsIC_first(text, input.text); if (mapping == null) ret; reactor.uniqResult(BEAObject, type := "match", +input, pattern := this, +mapping); } } beaConcept BMatch { new Ref pattern; SS mapping; } beaConcept BMatchToScenario { void reactWith(BMatch match, IBEAReactor reactor) { reactor.uniqResult(BEAObject, type := "scenario", text := mapToLines(match.mapping, (a, b) -> format_curly("* is a *.", b, a)); } } beaConcept BScenarioToOutput { bool canReactWith(BEAObject o) { ret o.typeIs("scenario"); } void reactWith(BEAObject scenario, IBEAReactor reactor) { reactor.uniqResult(BOutput, type := "Output", text := getString text(scenario)); } } concept Reactable { BEAObject a, b; } cmodule2 > DynCRUD_v2 { S input = "UBports community delivers 'second-largest release of Ubuntu Touch ever'", output; switchable int maxObjects = 1000; transient new BEAUtils beaUtils; transient new Concepts reactableConcepts; transient ReliableSingleThread rstUpdateReactables = dm_rstWithDelay(this, 100, r updateReactables); { tableFontSize = 15; } start { ensureConceptClassIsIndexed(BEAObject); crud.humanizeFieldNames = false; crud.showBackRefs = true; crud.specialFieldsForItem = a -> { MapSO map = litorderedmap(crud.hID, str(a.id)); S j = crud.javaClassDescForItem(a); mapPut(map, "Class/Type", joinUniqueNemptiesCI("/", j, a.type())); ret map; }; crud.itemToMap_inner = a -> { MapSO map = crud.itemToMap_inner_base(a); map.remove("type"); ret map; }; onConceptChangesAndNow(rstUpdateReactables); thread { uniq BMatchToScenario(); uniq BScenarioToOutput(); //uniq BPattern(text := "* delivers *"); uniq BAngleVarPattern(text := " delivers "); } } visualize { JComponent c = jvsplit(0.2, jhsplit(jCenteredSection("Input", withRightAlignedButtons( dm_wordWrapTextArea input(), "Load input", rThreadEnter loadInput, "Load & process", rThreadEnter loadAndProcess)), dm_wordWrapTextAreaAsSection output()), jvsplit(0.75, jCenteredSection("Objects", super.visualize()), jCenteredSection("Reactables", withRightAlignedButtons( new JConceptsTable(reactableConcepts, Reactable), "React all", rThreadEnter reactAll)))); tablePopupMenuFirst(table(), (menu, row) -> { BEAObject o = getItem(row); print("Item: " + o); if (o == null) ret; pcall { L partners = filter(list(BEAObject), partner -> o.canReactWith(partner)); if (nempty(partners)) addScrollingSubMenu(menu, "React with", menu2 -> { for (BEAObject p : partners) addMenuItem(menu2, str(p), rThread { new BEAReactor("User reacts " + o + " with " + p).react(o, p); }); }); } }); addButton("Delete reactors", rThreadEnter deleteReactors); ret c; } // end of visualize class BEAReactor { BReactor reactorConcept; *(S purpose) { reactorConcept = cnew BReactor(+purpose); } void react(BEAObject a, BEAObject b) { call(a, 'reactWith, b, new IBEAReactor { public BEAObject uniqResult(Class c, O... params) { BEAObject product = cnew(c, paramsPlus(params, _fromReaction := ll(a, b), _reactor := reactorConcept)); product = beaUtils.autoMigrate(product); BEAObject existing = findExisting(product); print("Existing for " + product + ": " + existing); if (existing != null) { cdelete(product); ret existing; } else { onReactionProduct(product); ret product; } } }); } } // find an existing reactor-made object with the same values BEAObject findExisting(BEAObject o) { MapSO map = allConceptFieldsAsMap(o); removeAll(map, "_reactor", "globalID"); //print(+map); ret firstThat(conceptsWhere(o.getClass(), mapToParams(map)), a -> a.getClass() == o.getClass() && a != o && cget _reactor(a) != null); } void onReactionProduct(BEAObject a) pcall { if (a.typeIs("output")) setField(output := getStringOpt text(a)); } void deleteReactors { deleteConcepts(BEAObject, o -> cget _reactor(o) != null || o.typeIs("reactor")); } void updateReactables { deleteConcepts(reactableConcepts); L objects = list(BEAObject); for (BEAObject a : objects) for (BEAObject b : filter(objects, b -> a.canReactWith(b))) cnew(reactableConcepts, Reactable, +a, +b); } void reactAll { Cl l = list(reactableConcepts, Reactable); if (empty(l)) ret; BEAReactor reactor = new("User clicks \"react all\""); for (Reactable r : l) pcall { reactor.react(r.a, r.b); } } void reactUntilOutput { while (!hasConcept(BOutput) && countConcepts(BEAObject) < maxObjects) { rstUpdateReactables.waitUntilDone(); reactAll(); } } void loadInput { setField(output := ""); deleteReactors(); deleteConcepts(BInput); uniq BInput(text := input); } void loadAndProcess { loadInput(); reactUntilOutput(); } }