Eclipse editor keeps hanging when editing Xtend files ( 'updating editor state (sleeping)' ) - eclipse

Recently, my Eclipse editor started becoming really sluggish when editing Xtend files. I am using Xtend in the context of the Xtext framework (which provides certain interfaces using Xtend). I thought it had something to do with my Eclipse installation, so I deleted it entirely off my MacBook and re-installed the newest version (Eclipse 2019-09). However, it was still really sluggish when editing Xtend files. It does this every few seconds (loading figure appears, it hangs for a few seconds after which it repeats this sequence). I notice that the Progress tab in my Eclipse editor shows the message 'updating editor state (sleeping)' after it stops hanging. It only happens with Xtend files (other files like java work just fine in Eclipse without hanging). The Xtend file I am working in is currently about 500 Lines of Codes. Not a small file, but could this be the reason my Eclipse IDE keeps hanging? Things I already tried to fix this hanging problem:
Fully delete and re-install Eclipse on my MacBook (only installing the necessary plug-ins that I need: The Palladio PCM framework, and the Xtext Framework).
Provide extra memory (up to 6000 MB) for the Eclipse environment and its VMs (using the memory parameters (XmX e.g.) in the eclipse.ini file).
Suspend all validators in the Eclipse environment (disabling the option to override this).
The weird thing is that it keeps telling 'updating editor state (sleeping)' after it stops hanging. This message appears for about 0.1 to 2 seconds. I think that disabling the 'editor state update' might resolve this problem, but I don't know where I can suspend this option. Does anyone have a clue on why my Eclipse IDE keeps hanging every few seconds when editing Xtend files?
EDIT 9 December 2019:
/*
* generated by Xtext 2.17.1
*/
package org.xtext.example.mydsl.generator
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.AbstractGenerator
import org.eclipse.xtext.generator.IFileSystemAccess2
import org.eclipse.xtext.generator.IGeneratorContext
import org.xtext.example.mydsl.finalDsl.UserProfile
import org.xtext.example.mydsl.finalDsl.Model
import org.xtext.example.mydsl.finalDsl.ConceptualMethod
import org.xtext.example.mydsl.finalDsl.Expression
import org.xtext.example.mydsl.finalDsl.AdditionalExpressions
import org.xtext.example.mydsl.finalDsl.RelationalOperator
import org.xtext.example.mydsl.finalDsl.SingleLibraryBusinessMethodStatement
import org.xtext.example.mydsl.finalDsl.SingleLibraryPersistenceMethodStatement
import org.xtext.example.mydsl.finalDsl.SingleLibraryInterFaceMethodStatement
import org.xtext.example.mydsl.finalDsl.IfStatements
import org.xtext.example.mydsl.finalDsl.IfElseStatements
import org.eclipse.emf.ecore.EcoreFactory
import org.eclipse.emf.ecore.EPackage
import org.eclipse.xtext.ISetup
import org.eclipse.emf.ecore.EStructuralFeature
import java.util.TreeMap
import java.util.HashMap
import java.util.Set
import org.palladiosimulator.pcm.util.*
import org.palladiosimulator.pcm.repository.impl.*
import org.palladiosimulator.pcm.repository.BasicComponent
import org.eclipse.emf.ecore.EClass
import org.palladiosimulator.pcm.core.CoreFactory
import org.palladiosimulator.pcm.repository.RepositoryFactory
import org.palladiosimulator.pcm.seff.SeffFactory
import org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl
import org.eclipse.emf.common.util.URI
import com.google.inject.Injector
import org.xtext.example.mydsl.FinalDslStandaloneSetup
import org.eclipse.emf.ecore.resource.ResourceSet
import com.google.inject.Injector;
import org.eclipse.emf.ecore.util.EcoreUtil
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl
import org.palladiosimulator.pcm.seff.seff_performance.ParametricResourceDemand
import org.palladiosimulator.pcm.seff.seff_performance.impl.ParametricResourceDemandImpl
import org.palladiosimulator.pcm.seff.seff_performance.SeffPerformanceFactory
import java.util.Collections
import org.eclipse.emf.ecore.EObject
import org.xtext.example.mydsl.finalDsl.ElseStatement
import java.util.ArrayList
import java.math.BigDecimal
import org.xtext.example.mydsl.finalDsl.Statement
import org.xtext.example.mydsl.finalDsl.RelationalOperator
import org.xtext.example.mydsl.finalDsl.AdditionOperator
import org.xtext.example.mydsl.finalDsl.MultiplicationOperator
import org.xtext.example.mydsl.finalDsl.libraryInterFaceMethodStatementEnum
/**
* Generates code from your model files on save.
*
* See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation
*/
class FinalDslGenerator extends AbstractGenerator {
override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context ) {
// Method call to build and store PCM Palladio repository. Right click on the repository file
// to create a .repository_diagram file to 'graphically' view and edit the repository accordingly.
RepositoryBuilder(resource,fsa)
// Method call to calculate the resource demands for the If/Else statements in the model according
// to the specified probabilities in the user profiles.
UserProfileEstimator(resource,fsa)
// Method call to build and store your modeled DSL instance accordingly. Mind that this has nothing
// to do with the creation of the PCM Palladio repository above, but it might come in handy if you want
// to export your DSL instances (for use outside of the xText IDE environment).
ModelXMLBuilder(resource)
}
// Method for calculating the resource demands for the If/Else statements in the model according to the
// specified probabilities in the user profiles. The declared probabilities are mapped to the If/Else Statements
// by using a set of for loops (see loops for elaboration documentation).
def UserProfileEstimator (Resource resource,IFileSystemAccess2 fsa) {
// Treemap of <String,Integer> pairs describing the set of library functions and their resource demands. These
// resource demands can later be substituted for resource demand sample data in order to calculate the resource
// demand estimations for the performance models.
var TreeMap<String,Integer> resourceTable = new TreeMap
resourceTable.put("Hash",120)
resourceTable.put("Concatenate",1000)
resourceTable.put("Average",60)
resourceTable.put("Count",80)
resourceTable.put("Today",80)
resourceTable.put("Time",80)
resourceTable.put("Random",80)
resourceTable.put("Max",43)
resourceTable.put("Min",45)
resourceTable.put("Root",80)
resourceTable.put("Square",80)
resourceTable.put("ReadDatabase",43)
resourceTable.put("Message",123)
resourceTable.put("ErrorMessage",100)
resourceTable.put("WarningMessage",43)
resourceTable.put("WriteDatabase",78)
resourceTable.put("DeleteFromDatabase",67)
resourceTable.put("Log",40)
resourceTable.put('>',40)
resourceTable.put('<>',40)
resourceTable.put('<',40)
resourceTable.put('=>',40)
resourceTable.put('<=',40)
resourceTable.put('==',40)
resourceTable.put('in',40)
resourceTable.put("+",40)
resourceTable.put("-",40)
resourceTable.put("OR",40)
resourceTable.put("*",40)
resourceTable.put("/",40)
resourceTable.put("AND",40)
// check if statements
var conceptualMethodDemandList = newArrayList
var ifElseDemandList = newArrayList
// TreeMap variable in order to store all the user profiles (indicated by the String variable in the list
// and the corresponding resource demands (stored as Arraylist <String> arrays). The strings in this latter
// Arraylist represent the probabilities for the If/Else statements in the model. Each of these probabilities
// Contains the resource demand for the condition evaluation of the If-Statement, the If Statement body itself
// and the Else statement. If one of these parts contains no Resource demand (if no library functions are present)
// if an Else statement is not defined, the Resource Demand is zero.
var TreeMap <String, ArrayList <String> > completeDemandList = new TreeMap
// For loop to traverse through all declared user profiles in the model.
for (userprofile : resource.allContents.toIterable.filter(UserProfile)) {
// For loop to traverse through all declared Conceptual Methods in the model. A conceptual method counter
// variable is stored in order to keep track of which Conceptual Method is processed by the for loop. This
// counter is used as the index for processing all user profile and resource demand data which are both stored
// as arrays.
var conceptualMethodCounter = 0
for (conceptualMethod : resource.allContents.toIterable.filter(ConceptualMethod)) {
// For loop traversing through all If/Else statements in the corresponding conceptual methods declared in the model.
// An ifElseStatementCounter variable is stored for the same reason as specified in the Conceptual Method for loop
// (see documentation above Conceptual Method for loop).
var ifElseStatementCounter = 0
for (ifElseStatement : conceptualMethod.eAllContents.toIterable.filter(IfElseStatements)) {
var ifDemand = 0
var elseDemand = 0
var expressionDemand = 0
// In the for-loop each if/Else statement is filtered for its If Statement part. This if Statement part is then filtered for
// all three Library Function sorts. This happens for the functions in the expression part of the If Statement (containing the
// condition that is evaluated), the actual If Statement part, and for the Else Statement part (in the case
// an else statement is present in the If/Else statment).
// The dispatch method getDemand method is called for each library function statement together with the
// resourceTable variable containing all library functions and their resource demands. This dispatch method returns
// the resource demand for the respective library function, after which this result is added to a 'Demand' variable
// which contains the aggregate sum of all library functions for the calculated If/Else Statement part.
for (ifStatement : ifElseStatement.eAllContents.toIterable.filter(IfStatements)){
for (expressionResourceStatement : ifStatement.expression.eAllContents.toIterable) {
switch(expressionResourceStatement){
SingleLibraryPersistenceMethodStatement,
SingleLibraryInterFaceMethodStatement,
SingleLibraryBusinessMethodStatement,
RelationalOperator,
AdditionOperator,
MultiplicationOperator:
expressionDemand += getDemand(expressionResourceStatement,resourceTable)
}
}
for (innerIfStatements : ifStatement.statements){
for (ifResourceStatement : innerIfStatements.eAllContents.toIterable) {
switch(ifResourceStatement){
SingleLibraryPersistenceMethodStatement,
SingleLibraryInterFaceMethodStatement,
SingleLibraryBusinessMethodStatement,
RelationalOperator,
AdditionOperator,
MultiplicationOperator:
ifDemand += getDemand(ifResourceStatement,resourceTable)
}
}
}
for (elseStatement : ifElseStatement.eAllContents.toIterable.filter(ElseStatement)){
for (elseResourceStatement : elseStatement.eAllContents.toIterable) {
switch(elseResourceStatement){
SingleLibraryPersistenceMethodStatement,
SingleLibraryInterFaceMethodStatement,
SingleLibraryBusinessMethodStatement,
RelationalOperator,
AdditionOperator,
MultiplicationOperator:
elseDemand += getDemand(elseResourceStatement,resourceTable)
}
}
}
// end of IfStatements for loop
}
// Add the resource demands of the evaluationDemand variable to the ifElseDemandList variable containing the resource demands
// for the If/Else statement that is currently processed in the for-loop.
ifElseDemandList.add("EvaluationDemands" + (conceptualMethodCounter+1).toString + "." + (ifElseStatementCounter+1).toString + ": " + BigDecimal.valueOf(expressionDemand))
// Add the resource demands of the ifDemand and elseDemand variable to the ifElseDemandList variable containing the resource demands
// for the If/Else statement that is currently processed in the for-loop. In the case the probability is declared in the user profile
// as zero (.0), the probability is assumed to be .50. The second if-statement below filters and handles these cases accordingly.
if (userprofile.probabilitysets.get(conceptualMethodCounter).probability.get(ifElseStatementCounter) != 0) {
ifElseDemandList.add("IfStatementDemands" + (conceptualMethodCounter+1).toString + "." + (ifElseStatementCounter+1).toString + ": " + BigDecimal.valueOf(ifDemand*userprofile.probabilitysets.get(conceptualMethodCounter).probability.get(ifElseStatementCounter)))
ifElseDemandList.add("ElseStatementDemands" + (conceptualMethodCounter+1).toString + "." + (ifElseStatementCounter+1).toString + ": " + BigDecimal.valueOf(elseDemand*(1-userprofile.probabilitysets.get(conceptualMethodCounter).probability.get(ifElseStatementCounter))))
}
if (userprofile.probabilitysets.get(conceptualMethodCounter).probability.get(ifElseStatementCounter) == 0) {
ifElseDemandList.add("IfStatementDemands" + (conceptualMethodCounter+1).toString + "." + (ifElseStatementCounter+1).toString + ": " + BigDecimal.valueOf(ifDemand*0.5))
ifElseDemandList.add("ElseStatementDemands" + (conceptualMethodCounter+1).toString + "." + (ifElseStatementCounter+1).toString + ": " + BigDecimal.valueOf(elseDemand*0.5))
}
// Once all the parts of the If/Else statement in the for-loop are processed, the resulting list of resource demands for the If/Else statement (stored
// in the ifElseDemandList variable) are added to the conceptualMethodDemandList variable containing the resource demands for all
// If/Else statements in the conceptual method. Next the ifElseDemandList is cleared and the ifElseStatementCounter is incremented in order for the next for-loop
// iteration to process the next If/Else statement of the conceptual method.
conceptualMethodDemandList.add('''
«ifElseDemandList.clone.toString»
''')
ifElseDemandList.clear
ifElseStatementCounter++
}
// Once all the If/Else statements in the for-loop are processed, the resulting list of resource demands for all If/Else statements (stored
// in the conceptualMethodDemandList variable) are added to the completeDemandList variable containing the resource demands for all
// conceptual methods and user profiles.
conceptualMethodCounter++
completeDemandList.put('''
«userprofile.name»
''',conceptualMethodDemandList.clone() as ArrayList<String>)
}
conceptualMethodDemandList.clear
}
// The completeDemandList variable containing all resource demands according to all the specified user profiles, are
// stored below in a text file.
fsa.generateFile(
"IfElseStatementsDemands.txt",
completeDemandList.toString)
}
// Dispatch method for looking up the resource demand of a library business function in a TreeMap variable
// containing the resource demands for all library functions.
def dispatch getDemand (SingleLibraryBusinessMethodStatement statement, TreeMap<String,Integer> resourceTable) {
return resourceTable.get(statement.libraryFunction.toString)
}
// Dispatch method for looking up the resource demand of a library interface function in a TreeMap variable
// containing the resource demands for all library functions.
def dispatch getDemand (SingleLibraryInterFaceMethodStatement statement, TreeMap<String,Integer> resourceTable) {
return resourceTable.get(statement.libraryFunction.toString)
}
// Dispatch method for looking up the resource demand of a library persistence function in a TreeMap variable
// containing the resource demands for all library functions.
def dispatch getDemand (SingleLibraryPersistenceMethodStatement statement, TreeMap<String,Integer> resourceTable) {
return resourceTable.get(statement.libraryFunction.toString)
}
def dispatch getDemand (RelationalOperator statement, TreeMap<String,Integer> resourceTable) {
return resourceTable.get(statement.operator.toString)
}
def dispatch getDemand (AdditionOperator statement, TreeMap<String,Integer> resourceTable) {
return resourceTable.get(statement.operator.toString)
}
def dispatch getDemand (MultiplicationOperator statement, TreeMap<String,Integer> resourceTable) {
return resourceTable.get(statement.operator.toString)
}
def RepositoryBuilder (Resource resource,IFileSystemAccess2 fsa) {
var repository = RepositoryFactory.eINSTANCE.createRepository
repository.setEntityName("Repository for Functional Model")
var myInterface = RepositoryFactory.eINSTANCE.createOperationInterface()
myInterface.setEntityName("My Interface")
repository.getInterfaces__Repository().add(myInterface); //add first class entity
var opProvRole = RepositoryFactory.eINSTANCE.createOperationProvidedRole()
opProvRole.setEntityName("Provided Role of Basic Component")
// set the interface for the role:
opProvRole.setProvidedInterface__OperationProvidedRole(myInterface)
for (element : resource.allContents.toIterable.filter(ConceptualMethod))
{
var component = RepositoryFactory.eINSTANCE.createBasicComponent()
component.setEntityName(element.name)
ConceptualClass2SEFF(component, element)
// set/add the role for basic component:
component.getProvidedRoles_InterfaceProvidingEntity().add(opProvRole)
repository.getComponents__Repository().add(component) //add first class entity
}
var storeRepositoryURI = URI.createURI(resource.URI.trimSegments(1).toString + "/src-gen/TestRepository.repository")
var result = new XMLResourceImpl(storeRepositoryURI)
result.getContents().add(repository)
result.save(null)
}
def ConceptualClass2SEFF (BasicComponent component, ConceptualMethod conceptualmethod) {
for (statement : conceptualmethod.eAllContents.toIterable.filter(SingleLibraryPersistenceMethodStatement))
{
var seff = SeffFactory.eINSTANCE.createResourceDemandingSEFF()
var internalAction = SeffFactory.eINSTANCE.createInternalAction()
internalAction.entityName = statement.libraryFunction.toString
internalAction.resourceDemand_Action.add(SeffPerformanceFactory.eINSTANCE.createParametricResourceDemand)
seff.steps_Behaviour.add(internalAction)
component.serviceEffectSpecifications__BasicComponent.add(seff)
}
for (statement : conceptualmethod.eAllContents.toIterable.filter(SingleLibraryInterFaceMethodStatement))
{
var seff = SeffFactory.eINSTANCE.createResourceDemandingSEFF()
var internalAction = SeffFactory.eINSTANCE.createInternalAction()
internalAction.entityName = statement.libraryFunction.toString
internalAction.resourceDemand_Action.add(SeffPerformanceFactory.eINSTANCE.createParametricResourceDemand)
seff.steps_Behaviour.add(internalAction)
component.serviceEffectSpecifications__BasicComponent.add(seff)
}
for (statement : conceptualmethod.eAllContents.toIterable.filter(SingleLibraryBusinessMethodStatement))
{
var seff = SeffFactory.eINSTANCE.createResourceDemandingSEFF()
var internalAction = SeffFactory.eINSTANCE.createInternalAction()
internalAction.entityName = statement.libraryFunction.toString
internalAction.resourceDemand_Action.add(SeffPerformanceFactory.eINSTANCE.createParametricResourceDemand)
seff.steps_Behaviour.add(internalAction)
component.serviceEffectSpecifications__BasicComponent.add(seff)
}
}
def ModelXMLBuilder (Resource dslInstance) {
var injector = new FinalDslStandaloneSetup().createInjectorAndDoEMFRegistration()
var resourceSet = injector.getInstance(ResourceSet)
dslInstance.load(null)
EcoreUtil.resolveAll(resourceSet)
var storeXmiURI = URI.createURI(dslInstance.URI.trimSegments(1).toString + "/src-gen/DslModel.xmi")
var xmiResource = resourceSet.createResource(storeXmiURI)
xmiResource.getContents().add(dslInstance.getContents().get(0))
xmiResource.save(null)
}
}

Related

Chisel persist value in module until new write

I have created a basic module that is meant to represent a unit of memory in Chisel3:
class MemristorCellBundle() extends Bundle {
val writeBus = Input(UInt(1.W))
val dataBus = Input(UInt(8.W))
val cellBus = Output(UInt(8.W))
}
class MemCell() extends Module {
val io = IO(new MemCellBundle())
val write = Wire(UInt())
write := io.voltageBus
val internalValue = Reg(UInt())
// More than 50% of total voltage in (255).
when(write === 1.U) {
internalValue := io.dataBus
io.cellBus := io.dataBus
} .otherwise {
io.cellBus := internalValue
}
}
What I want is for it to output the internalValue when the write bus is logic LOW, and change this value with logic HIGH. My understanding of Chisel is that the register can persist this internalValue between clock cycles, so that this basically acts as a single unit of memory.
I'm doing it in this way as part of a larger project. However when writing a unit test I am finding that the 'read-after-write' scenario fails.
class MemCellTest extends FlatSpec with ChiselScalatestTester with Matchers {
behavior of "MemCell"
it should "read and write" in {
test(new MemCell()) { c =>
c.io.dataBus.poke(5.U)
c.io.write.poke(0.U)
c.io.cellBus.expect(0.U)
// Write
c.io.dataBus.poke(5.U)
c.io.write.poke(1.U)
c.io.cellBus.expect(5.U)
// Verify read-after-write
c.io.dataBus.poke(12.U)
c.io.write.poke(0.U)
c.io.cellBus.expect(5.U)
}
}
}
The first two expectations work just as I would expect. However, when I try to read after writing, the cellBus returns to 0 instead of persisting the 5 that I had written previously.
test MemCell Success: 0 tests passed in 1 cycles in 0.035654 seconds 28.05 Hz
[info] MemCellTest:
[info] MemCell
[info] - should read and write *** FAILED ***
[info] io_cellBus=0 (0x0) did not equal expected=5 (0x5) (lines in MyTest.scala: 10) (MyTest.scala:21)
Clearly the register is not keeping this value, and so internalValue reverts to 0. But why does this happen, and how would I be able to create a value that can persist?
Drakinite's comment is correct. You need to make sure to step the clock in order to see the register latch the value. I tweaked your test to include a couple of steps and it works as expected:
c.io.dataBus.poke(5.U)
c.io.writeBus.poke(0.U)
c.io.cellBus.expect(0.U)
c.clock.step() // Added step
// Write passthrough (same cycle)
c.io.dataBus.poke(5.U)
c.io.writeBus.poke(1.U)
c.io.cellBus.expect(5.U)
c.clock.step() // Added step
// Verify read-after-write
c.io.dataBus.poke(12.U)
c.io.writeBus.poke(0.U)
c.io.cellBus.expect(5.U)
Here's an executable example showing that this works (using chisel3 v3.4.4 and chiseltest v0.3.4): https://scastie.scala-lang.org/5E1rOEsYSzSUrLXZCvoyNA

How to check if an argument of an object is in an ArrayBuffer filled with objects in Scala?

I want to check if an Int typed by an user exists as primary argument/variable(the class has only one argument of type Int) in an ArrayBuffer filled with objects of a class.
It's an assignment for school, and I'm not able to get help of a prof or assistant so I'm asking for your help. I have to code a class "ewallet", which has attributes like "client id"(Int) and "pin code" (random Int between two Numbers). I'm not using "pin code" as an argument of the class as it seems to be implied in the assignment as it says "It has to show the pass when the user creates an account using his client id". I shouldn't be able to create 2 ewallets with the same client id.
So I would have to refuse to create an ewallet when if an ewallet with the same client id exists already in the ArrayBuffer (or array) that stocks ewallets.
Because of the Randomly generated "pin code", it creates different ewallets with same client id and different pin codes. So how to not add an ewallet(class) to a list if an ewallet with some argument exists already?
Thanks for helping a newbie.
I could just created another array list saving the client id's entered by an user and save them to compare with future client id's. But I'd like to learn how to do with an array containing objects.
I tried with for (i <- ListClients) if (NumClient != i.id) but if the list is empty, it doesn't do anything...
class ewallet(clientID: Int){
val id = clientID
val pass = 100 + Random.nextInt((99999-100)+1) //for the class, I didnt include the rest as its not related.
//for the main
def main(args: Array[String]): Unit = {
var ListClients = ArrayBuffer[ewallet]()
var action: Char = " ".charAt(0)
do {
println("[c]-Create ewallet. \n[a]-Access ewallet.\n[q]-Quit")
action = StdIn.readChar()
if (action == 'c') {
val NumClient = StdIn.readLine("Enter your client id :").toInt
var newClient = new ewallet(NumClient)
for (i <- ListClients) if (NumClient == i.id) {
println("Impossible, already exists.")
}
for (i <- ListClients) if (NumClient != i.id){
ListClients += newClient
println("Your pin code is : " + newClient.pass)
}
}
println(ListClients.mkString("\n"))
} while(action != 'q')
I should be able to add newClient to ListClients if NumClient != i.id, but because it's initially empty, it doesn't read those lines… So it does Nothing.
The simplest way would be to assign the result of the presence check to a variable, and add or "not add" the client object based on this variable. In Scala, the idiomatic way to work with collections are their rich set of transformation methods; in your case, exists provides the solution:
val alreadyExists = ListClients.exists(_.id == NumClient)
if (alreadyExists) {
println("Impossible, already exists.")
} else {
ListClients += newClient
println("Your pin code is : " + newClient.pass)
}
If you don't know yet about collection transformations and higher-order functions in general (and you should! they make life a lot easier), then the above piece is actually equivalent to this:
var alreadyExists = false
for (i <- ListClients) {
alreadyExists = alreadyExists || i.id == NumClient
}
(albeit a bit more efficient since it stops the iteration right after the existing client is found, if it is at all present).
As an unrelated comment, in Scala it is conventional to name variables in pascalCase and types in CamelCase. Thus, your class should be called something like EWallet, and variables like ListClients should be called listClients. Also, " ".charAt(0) is the same as ' '.

How to combine the elements of an arbitrary number of dependent Fluxes?

In the non reactive world the following code snippet is nothing special:
interface Enhancer {
Result enhance(Result result);
}
Result result = Result.empty();
result = fooEnhancer.enhance(result);
result = barEnhancer.enhance(result);
result = bazEnhancer.enhance(result);
There are three different Enhancer implementations taking a Result instance, enhancing it and returning the enhanced result. Let's assume the order of the enhancer calls matters.
Now what if these methods are replaced by reactive variants returning a Flux<Result>? Because the methods depend on the result(s) of the preceding method, we cannot use combineLatest here.
A possible solution could be:
Flux.just(Result.empty())
.switchMap(result -> first(result)
.switchMap(result -> second(result)
.switchMap(result -> third(result))))
.subscribe(result -> doSomethingWith(result));
Note that the switchMap calls are nested. As we are only interested in the final result, we let switchMap switch to the next flux as soon as new events are emitted in preceding fluxes.
Now let's try to do it with a dynamic number of fluxes. Non reactive (without fluxes), this would again be nothing special:
List<Enhancer> enhancers = <ordered list of different Enhancer impls>;
Result result = Result.empty();
for (Enhancer enhancer : enhancers) {
result = enhancer.enhance(result);
}
But how can I generalize the above reactive example with three fluxes to deal with an arbitrary number of fluxes?
I found a solution using recursion:
#FunctionalInterface
interface FluxProvider {
Flux<Result> get(Result result);
}
// recursive method creating the final Flux
private Flux<Result> cascadingSwitchMap(Result input, List<FluxProvider> fluxProviders, int idx) {
if (idx < fluxProviders.size()) {
return fluxProviders.get(idx).get(input).switchMap(result -> cascadingSwitchMap(result, fluxProviders, idx + 1));
}
return Flux.just(input);
}
// code using the recursive method
List<FluxProvider> fluxProviders = new ArrayList<>();
fluxProviders.add(fooEnhancer::enhance);
fluxProviders.add(barEnhancer::enhance);
fluxProviders.add(bazEnhancer::enhance);
cascadingSwitchMap(Result.empty(), fluxProviders, 0)
.subscribe(result -> doSomethingWith(result));
But maybe there is a more elegant solution using an operator/feature of project-reactor. Does anybody know such a feature? In fact, the requirement doesn't seem to be such an unusual one, is it?
switchMap feels inappropriate here. If you have a List<Enhancer> by the time the Flux pipeline is declared, why not apply a logic close to what you had in imperative style:
List<Enhancer> enhancers = <ordered list of different Enhancer impls>;
Mono<Result> resultMono = Mono.just(Result.empty)
for (Enhancer enhancer : enhancers) {
resultMono = resultMono.map(enhancer::enhance); //previousValue -> enhancer.enhance(previousValue)
}
return resultMono;
That can even be performed later at subscription time for even more dynamic resolution of the enhancers by wrapping the whole code above in a Mono.defer(() -> {...}) block.

Dependency not resolved correctly when Product (CodeGenerator) used in Rule ([asd] -> [cpp])

when using a Rule inside a Module with a Dependency to a Product, a FileTagger breaks the resolution of dependencies in qbs.
We have a CodeGenerator in our project which is build by the project itself.
This CodeGenerator generates C++-Classes from *.asd-Files.
A Product called "Core" uses that CodeGenerator to generate Classes from Core.asd Files.
I am not sure if this is a bug in qbs, but since qbs 1.8 this part of our project does not work anymore.
I created a small test project that illustrates that problem:
RuleUsesProduct.qbs
import qbs
Project {
minimumQbsVersion: "1.8.0"
references: [
"Core/Core.qbs",
"CodeGenerator/CodeGenerator.qbs"
]
qbsSearchPaths: "QBS"
}
Core.qbs
import qbs
CppApplication {
Depends { name: "Qt.core" }
cpp.cxxLanguageVersion: "c++11"
cpp.defines: [
]
consoleApplication: true
files: [
"main.cpp",
"core.asd"
]
Depends{ name:"CodeGenerator"}
Depends{ name:"CodeGeneration"}
Group { // Properties for the produced executable
fileTagsFilter: product.type
qbs.install: true
}
}
Db2cppModule.qbs
This is indirectly included via the qbsSearchPath from RuleUsesProductTest.qbs
import qbs 1.0
import qbs.Environment
import qbs.FileInfo
import qbs.TextFile
import qbs.Process
import qbs.File
Module {
FileTagger {
patterns: ["*.asd"]
fileTags: ["asd"]
}
Rule {
id: dbRule
inputs: ["asd"]
inputsFromDependencies: ["application"]
multiplex: true
outputFileTags: ["cpp", "hpp"]
outputArtifacts: {
// dummy code that should call the CodeGenerator.exe with some parameters...
var process = new Process();
console.warn("# " + inputs["application"][0].filePath)
var cmdExp = "" + inputs["application"][0].filePath;
process.exec(cmdExp, [], false);
return []
}
prepare: {
console.warn("*" + inputs["application"][0].filePath)
var cmdExp = "" + inputs["application"][0].filePath;
var cmd = new Command(cmdExp, []);
return cmd
}
}
}
CodeGenerator.qbs
import qbs
CppApplication {
Depends { name: "Qt.core" }
cpp.cxxLanguageVersion: "c++11"
cpp.defines: [
]
consoleApplication: true
files: [
"codegenerator.cpp"
]
Group { // Properties for the produced executable
fileTagsFilter: product.type
qbs.install: true
}
}
Any help is much appreciated!
Qbs knows two types of rules: Multiplex and non-multiplex ones. Details can be found here: https://doc.qt.io/qbs/rule-item.html.
The important point for you is that non-multiplex rules invoke their prepare script once for every input. This means that in your example, the prepare script runs twice: Once for the asd input and once for the application input. The respective other inputs are not visible. This does not fit your use case, because you want to see both inputs at the same time. Therefore, you need to make your rule a multiplex rule:
multiplex: true
In the prepare script, you need to create one command for every input of type asd (unless your generator can take several inputs at once).
Rule of thumb (no pun intended): If your rule declares more than one input tag, it probably should be a multiplex rule.

Drools String version of Accumulate

The basis of what I am trying to do is get a concatenated string from an group of objects that meet a certain condition in the same way you get a sum of numbers from an accumulate.
The rule below is attempting to take any given IEP object and find all the services that belong to that IEP, sum up their frequencyduration and then create a report of the Names of each of those services and list the durationfrequency of each.
What is the correct way to do this? Is there something inherently wrong with my approach or am I just missing subtle errors.
dialect "mvel"
import superObjects.IL.*
import objects.*;
import rules.ValidationHelper;
import java.util.List;
import java.util.ArrayList;
function String serviceDesc(ArrayList sers){
String s = "";
for(int i = 0 ; i<sers.size(); i++){
Super_Service ser = (Super_Service) sers.get(i);
s+=ser.getName()+": "+ser.getDuration()*ser.getFrequency()+" Per Week <br>";
}
return s;
}
rule "Calculate Service Minutes"
when
$siep: Super_Iep();
$counter : Number() from accumulate(
$ser: Super_Service(Measurable != "Yes" && ValidationHelper.contains(Documentid, $siep.getIid()+"")),
sum($ser.getFrequency()*$ser.getDuration())
);
$sers: ArrayList() from collect(Super_Service(Measurable != "Yes" && ValidationHelper.contains(Documentid, $siep.getIid()+"")));
then
ValidationHelper.storeCalc($siep, $counter, "Text", "SERVICE_MINUTES");
ValidationHelper.storeCalc($siep, serviceDesc($sers), "Text", "SERVICE_REASON");
end
I cannot easily include ValidationHelper because its thousands of lines of code but the two functions in this block of code are a simple contains alternative that does aditional things like trimming both strings, and storeCalc is just a utility function for saving a field to the database.
What you accumulate and collect here is a single
Map<String,Number> dur2min
i.e., the number of minutes per service (and per week). How that data is to be taken from the facts is rightly part of the business logic and belongs into rules, but the way the "report" is composed and presented (HTML!) should not be spread out here, repeating the selection logic (!) and the iteration of the list of collected facts.
I would move both into a function that takes the list and calculates bot in a single iteration,
function void serviceDesc(Super_Iep iep, ArrayList sers){
int sum = 0;
StringBuilder sb = new StringBuilder();
for( Object obj: sers ){
//... add and append
}
ValidationHelper.storeCalc( iep, sum, "Text", "SERVICE_MINUTES");
ValidationHelper.storeCalc( iep, sb.toString(), "Text", "SERVICE_REASON");
}
Whether this should be here or in ValidationHelper itself remains to be discussed but I'd rather not have it in DRL.
Finally compare the resulting rule:
rule "Calculate Service Minutes"
when
$siep: Super_Iep();
$sers: ArrayList() from collect(Super_Service(Measurable != "Yes" && ValidationHelper.contains(Documentid, $siep.getIid()+"")));
then
serviceDesc($siep, $sers);
end