I was playing around with the custom shadow variables and custom variable listeners. I found that the "Exam timetabling" example uses custom shadow variables to update "Following exams" when the "Leading Exam" changes.
So what i did to assert asking in this question was true Custom VariableListener updating more than one shadow variables i added a second shadow variable in the Following Exam class which references the variable "period" , and then in the variable listener of the LeadingExam planning entity(PeriodUpdatingVariableListener) i update both the variables.
Here is the code for the "FollowingExam" class
#PlanningEntity
#XStreamAlias("FollowingExam")
public class FollowingExam extends Exam {
protected LeadingExam leadingExam;
// Shadow variables
protected Period period;
protected Integer var;
#CustomShadowVariable(variableListenerClass = PeriodUpdatingVariableListener.class, sources = {
#CustomShadowVariable.Source(entityClass = LeadingExam.class, variableName = "period") })
public Period getPeriod() {
return period;
}
public void setPeriod(Period period) {
this.period = period;
}
#CustomShadowVariable(variableListenerRef = #PlanningVariableReference(variableName = "period"))
public Integer getVar() {
return var;
}
public void setVar(Integer var) {
this.var = var;
}
public LeadingExam getLeadingExam() {
return leadingExam;
}
public void setLeadingExam(LeadingExam leadingExam) {
this.leadingExam = leadingExam;
}
}
And in the "PeriodUpdatingVariableListener" i have the next code
public class PeriodUpdatingVariableListener implements VariableListener<LeadingExam> {
public void beforeEntityAdded(ScoreDirector scoreDirector, LeadingExam leadingExam) {
// Do nothing
}
public void afterEntityAdded(ScoreDirector scoreDirector, LeadingExam leadingExam) {
updatePeriod(scoreDirector, leadingExam);
}
public void beforeVariableChanged(ScoreDirector scoreDirector, LeadingExam leadingExam) {
// Do nothing
}
public void afterVariableChanged(ScoreDirector scoreDirector, LeadingExam leadingExam) {
updatePeriod(scoreDirector, leadingExam);
}
public void beforeEntityRemoved(ScoreDirector scoreDirector, LeadingExam leadingExam) {
// Do nothing
}
public void afterEntityRemoved(ScoreDirector scoreDirector, LeadingExam leadingExam) {
// Do nothing
}
protected void updatePeriod(ScoreDirector scoreDirector, LeadingExam leadingExam) {
Period period = leadingExam.getPeriod();
for (FollowingExam followingExam : leadingExam.getFollowingExamList()) {
scoreDirector.beforeVariableChanged(followingExam, "period");
followingExam.setPeriod(period);
scoreDirector.afterVariableChanged(followingExam, "period");
//additional lines of code to update the "var" variable
Integer var = followingExam.getVar();
if(var == null){
var = new Integer(1);
}
else var++;
scoreDirector.beforeVariableChanged(followingExam, "var");
followingExam.setVar(var);
scoreDirector.afterVariableChanged(followingExam, "var");
}
}
}
It also seem like that there is no problem for OptaPlanner registering that this is another shadow variable because i get this message when i run the application
2016-04-11 15:26:15,309 [AWT-EventQueue-0] TRACE Model annotations parsed for Solution Examination:
2016-04-11 15:26:15,309 [AWT-EventQueue-0] TRACE Entity Exam:
2016-04-11 15:26:15,309 [AWT-EventQueue-0] TRACE Variable room (genuine)
2016-04-11 15:26:15,309 [AWT-EventQueue-0] TRACE Entity LeadingExam:
2016-04-11 15:26:15,309 [AWT-EventQueue-0] TRACE Variable period (genuine)
2016-04-11 15:26:15,309 [AWT-EventQueue-0] TRACE Entity FollowingExam:
2016-04-11 15:26:15,309 [AWT-EventQueue-0] TRACE Variable period (shadow)
2016-04-11 15:26:15,309 [AWT-EventQueue-0] TRACE Variable var (shadow)
The error that i get is the next error , this error only appears when i change the environment mode to FULL_ASSERT ,but when it is left to the default REPRODUCIBLE it runs without any errors.
Caused by: java.lang.IllegalStateException: VariableListener corruption: the entity (426)'s shadow variable (FollowingExam.var)'s corrupted value (null) changed to uncorrupted value (1) after all VariableListeners were triggered without changes to the genuine variables.
Probably the VariableListener class for that shadow variable (FollowingExam.var) forgot to update it when one of its sources changed after completedAction (Initial score calculated).
at org.optaplanner.core.impl.score.director.AbstractScoreDirector.assertShadowVariablesAreNotStale(AbstractScoreDirector.java:349)
at org.optaplanner.core.impl.solver.recaller.BestSolutionRecaller.solvingStarted(BestSolutionRecaller.java:84)
at org.optaplanner.core.impl.solver.DefaultSolver.solvingStarted(DefaultSolver.java:196)
at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:175)
at org.optaplanner.examples.common.business.SolutionBusiness.solve(SolutionBusiness.java:307)
at org.optaplanner.examples.common.swingui.SolverAndPersistenceFrame$SolveWorker.doInBackground(SolverAndPersistenceFrame.java:287)
at org.optaplanner.examples.common.swingui.SolverAndPersistenceFrame$SolveWorker.doInBackground(SolverAndPersistenceFrame.java:1)
at javax.swing.SwingWorker$1.call(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at javax.swing.SwingWorker.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
This code in a variable listener is bad:
Integer var = followingExam.getVar();
...
var++;
...
followingExam.setVar(var);
...
If its called multiple times, the var changes, even if the genuine var hasn't changed. If that var is used in score rules, the solution's score isn't always the same for the same solution.
A shadow variable is the caching of the result of a formula based on at least 1 genuine variable (directly or indirectly). For example shadow C = genuine A + problem property B. So if B is 10 and ranges A from 1 to 5, then C will be 11 if A is 1, 12 if A is 2, etc.
Related
I am working on Cucumber Java. And trying to transform a Parameter.
Here are the steps I followed.
Below Step I am using, so that to transform the parameter before consuming,
#When("^I format \"([^\"]*)\" and save by key \"([^\"]*)\"$")
public void i_format_something_and_save_by_key_something(StringValue value, String key) throws Throwable {
String createdValue = value.value;
}
Here StringValue is a class for holding a string,
public class StringValue {
public String value;
public StringValue(String value) {
this.value = value;
}
}
I have a code written to transform the parameter. Which is used from Cucumber Type Registry,
public class StepTrasform {
#ParameterType(".*")
public StringValue TransfromParam(String functionExpression) {
return new StringValue(TransformFunction.Transform(functionExpression));
}
}
Here is the error I am getting when I run the scenario,
io.cucumber.core.exception.CucumberException: Could not convert arguments for step [^I format "([^"]*)" and save by key "([^"]*)"$] defined at 'stepdefins.CommonPageSteps.i_format_something_and_save_by_key_something(utils.StringValue,java.lang.String)'.
at io.cucumber.core.runner.PickleStepDefinitionMatch.couldNotConvertArguments(PickleStepDefinitionMatch.java:112)
at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:56)
at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10)
at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:86)
at io.cucumber.core.runner.TestStep.run(TestStep.java:57)
at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:51)
at io.cucumber.core.runner.TestCase.run(TestCase.java:95)
at io.cucumber.core.runner.Runner.runPickle(Runner.java:75)
at io.cucumber.testng.TestNGCucumberRunner.lambda$runScenario$1(TestNGCucumberRunner.java:132)
at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$3(CucumberExecutionContext.java:151)
at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23)
at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:151)
at io.cucumber.testng.TestNGCucumberRunner.runScenario(TestNGCucumberRunner.java:129)
at io.cucumber.testng.AbstractTestNGCucumberTests.runScenario(AbstractTestNGCucumberTests.java:35)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:577)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:100)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:646)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:811)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1129)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
at org.testng.TestRunner.privateRun(TestRunner.java:746)
at org.testng.TestRunner.run(TestRunner.java:600)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:366)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:361)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:319)
at org.testng.SuiteRunner.run(SuiteRunner.java:268)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1264)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1189)
at org.testng.TestNG.runSuites(TestNG.java:1104)
at org.testng.TestNG.run(TestNG.java:1076)
at org.testng.remote.AbstractRemoteTestNG.run(AbstractRemoteTestNG.java:126)
at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:152)
at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:57)
Caused by: io.cucumber.cucumberexpressions.CucumberExpressionException: ParameterType {anonymous} failed to transform [#GenerateRandomString(10)] to class utils.StringValue
at io.cucumber.cucumberexpressions.ParameterType.transform(ParameterType.java:264)
at io.cucumber.cucumberexpressions.Argument.getValue(Argument.java:42)
at io.cucumber.core.stepexpression.ExpressionArgument.getValue(ExpressionArgument.java:17)
at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:47)
... 35 more
Caused by: java.lang.IllegalArgumentException: Can't transform '#GenerateRandomString(10)' to class utils.StringValue
BuiltInParameterTransformer only supports a limited number of class types
Consider using a different object mapper or register a parameter type for class utils.StringValue
at io.cucumber.cucumberexpressions.BuiltInParameterTransformer.createIllegalArgumentException(BuiltInParameterTransformer.java:120)
at io.cucumber.cucumberexpressions.BuiltInParameterTransformer.doTransform(BuiltInParameterTransformer.java:99)
at io.cucumber.cucumberexpressions.BuiltInParameterTransformer.transform(BuiltInParameterTransformer.java:22)
at io.cucumber.cucumberexpressions.RegularExpression.lambda$match$0(RegularExpression.java:66)
at io.cucumber.cucumberexpressions.ParameterType$TransformerAdaptor.transform(ParameterType.java:299)
at io.cucumber.cucumberexpressions.ParameterType.transform(ParameterType.java:259)
... 38 more
I suppose that you have a scenario like this.
Scenario: cucumber parameter
Given: ...
When I format "#GenerateRandom(10)" and save by key "someKey"
Latest versions of cucumber allow you use Cucumber Expressions like {string}, {int}, {customExpression}.
#When("I format {stringValue} and save by key {string}")
#ParameterType(".*") // method name is stringValue
public StringValue stringValue(String functionExpression) { /* ... */ }
// or with a different method name
#ParameterType(value = ".*", name = "stringValue")
public StringValue transfromParam(String functionExpression) { /* ... */ }
Otherwise, you can change ParameterType regex to "[^\"]*".
// #ParameterType(".*")
#ParameterType("[^\"]*")
public StringValue TransfromParam(String functionExpression) {
return new StringValue(TransformFunction.Transform(functionExpression));
}
#When("^I format \"([^\"]*)\" and save by key \"([^\"]*)\"$")
public void i_format_something_and_save_by_key_something(StringValue value, String key) throws Throwable {
String createdValue = value.value;
}
I'm trying to add a mutator for an existing private final field. I can transform the field modifiers to remove the final specification and add an accessor method:
// accessor interface
public interface UniqueIdAccessor {
Serializable getUniqueId();
}
// mutator interface
public interface UniqueIdMutator {
void setUniqueId(Serializable uniqueId);
}
...
// fragment of Java agent implementation
return new AgentBuilder.Default()
.type(hasSuperType(named("org.junit.runner.Description")))
.transform(new Transformer() {
#Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule module) {
return builder.field(named("fUniqueId")).transform(ForField.withModifiers(FieldManifestation.PLAIN))
.implement(UniqueIdAccessor.class).intercept(FieldAccessor.ofField("fUniqueId"))
// .implement(UniqueIdMutator.class).intercept(FieldAccessor.ofField("fUniqueId"))
.implement(Hooked.class);
}
})
.installOn(instrumentation);
...
Here's a method that uses reflection to check the modifiers of the target field and calls the accessor to get the value of the field.
private static void injectProxy(Description description) {
try {
Field bar = Description.class.getDeclaredField("fUniqueId");
System.out.println("isFinal: " + ((bar.getModifiers() & Modifier.FINAL) != 0));
} catch (NoSuchFieldException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Serializable uniqueId = ((UniqueIdAccessor) description).getUniqueId();
System.out.println("uniqueId: " + uniqueId);
}
// isFinal: false
// uniqueId: <description-unique-id>
... but if I uncomment the second "implement" expression to add the mutator, the transform blows up:
// isFinal: true
// java.lang.ClassCastException:
// class org.junit.runner.Description cannot be cast to class com.nordstrom.automation.junit.UniqueIdAccessor
// (org.junit.runner.Description and com.nordstrom.automation.junit.UniqueIdAccessor
// are in unnamed module of loader 'app')
I could set the field value with reflection, but that defeats the purpose of using Byte Buddy in the first place!
The problem with this approach is that the field accessor considers the input type prior to the modification. Byte Buddy prohibits this as it does not consider the mutation to be legal, not knowing about the removed modifier. As a result, the transformation fails in its entirety and you get the error you are seeing. (Register a listener to see this error.)
To avoid this, you can implement a custom Implementation using FieldAccess (without or). You can have a look at the more convenient FieldAccessor to see how this is implemented, only that you need to drop the validity checks.
Thanks for pointing me in the right direction! I assemble the StackManipulation object that defines the mutator method with this:
final TypeDescription description = TypePool.Default.ofSystemLoader().describe("org.junit.runner.Description").resolve();
final Generic _void_ = TypeDescription.VOID.asGenericType();
final Generic serializable = TypePool.Default.ofSystemLoader().describe("java.io.Serializable").resolve().asGenericType();
final MethodDescription.Token setUniqueIdToken = new MethodDescription.Token("setUniqueId", Modifier.PUBLIC, _void_, Arrays.asList(serializable));
final MethodDescription setUniqueId = new MethodDescription.Latent(description, setUniqueIdToken);
final Token fUniqueIdToken = new FieldDescription.Token("fUniqueId", Modifier.PRIVATE, serializable);
final FieldDescription fUniqueId = new FieldDescription.Latent(description, fUniqueIdToken);
final StackManipulation setUniqueIdImpl = new StackManipulation.Compound(
MethodVariableAccess.loadThis(),
MethodVariableAccess.load(setUniqueId.getParameters().get(0)),
Assigner.DEFAULT.assign(serializable, serializable, Typing.STATIC),
FieldAccess.forField(fUniqueId).write(),
MethodReturn.VOID
);
... and I transform the target class with this:
return new AgentBuilder.Default()
.type(hasSuperType(named("org.junit.runner.Description")))
.transform(new Transformer() {
#Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule module) {
return builder.field(named("fUniqueId")).transform(ForField.withModifiers(FieldManifestation.PLAIN))
.implement(AnnotationsAccessor.class).intercept(FieldAccessor.ofField("fAnnotations"))
.implement(UniqueIdAccessor.class).intercept(FieldAccessor.ofField("fUniqueId"))
.implement(UniqueIdMutator.class).intercept(new Implementation.Simple(setUniqueIdImpl));
}
})
.installOn(instrumentation);
Here are the definitions of the three interfaces used in the transform:
// annotations accessor interface
public interface AnnotationsAccessor {
Annotation[] annotations();
}
// unique ID accessor interface
public interface UniqueIdAccessor {
Serializable getUniqueId();
}
// unique ID mutator interface
public interface UniqueIdMutator {
void setUniqueId(Serializable uniqueId);
}
I'm facing a weird behaviour while using AOP with AspectJ.
Basically the #Around method its called either once either twice and while trying to debugging I can't find the reason why it's being executing twice (I mean what triggers the second execution of the method)
here is some code :
#Aspect
#Slf4j
public class ReactiveRedisCacheAspect {
#Pointcut("#annotation(com.xxx.xxx.cache.aop.annotations.ReactiveRedisCacheable)")
public void cacheablePointCut() {}
#Around("cacheablePointCut()")
public Object cacheableAround(final ProceedingJoinPoint proceedingJoinPoint) {
log.debug("ReactiveRedisCacheAspect cacheableAround.... - {}", proceedingJoinPoint);
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
Class<?> returnTypeName = method.getReturnType();
Duration duration = Duration.ofHours(getDuration(method));
String redisKey = getKey(method, proceedingJoinPoint);
if (returnTypeName.isAssignableFrom(Flux.class)) {
log.debug("returning Flux");
return cacheRepository.hasKey(redisKey)
.filter(found -> found)
.flatMapMany(found -> cacheRepository.findByKey(redisKey))
.flatMap(found -> saveFlux(proceedingJoinPoint, redisKey, duration));
} else if (returnTypeName.isAssignableFrom(Mono.class)) {
log.debug("Returning Mono");
return cacheRepository.hasKey(redisKey)
.flatMap(found -> {
if (found) {
return cacheRepository.findByKey(redisKey);
} else {
return saveMono(proceedingJoinPoint, redisKey, duration);
}
});
} else {
throw new RuntimeException("non reactive object supported (Mono,Flux)");
}
}
private String getKey(final Method method, final ProceedingJoinPoint proceedingJoinPoint) {
ReactiveRedisCacheable annotation = method.getAnnotation(ReactiveRedisCacheable.class);
String cacheName = annotation.cacheName();
String key = annotation.key();
cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName);
key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key);
return cacheName + "_" + key;
}
}
public class AspectSupportUtils {
private static final ExpressionEvaluator evaluator = new ExpressionEvaluator();
public static Object getKeyValue(JoinPoint joinPoint, String keyExpression) {
if (keyExpression.contains("#") || keyExpression.contains("'")) {
return getKeyValue(joinPoint.getTarget(), joinPoint.getArgs(), joinPoint.getTarget().getClass(),
((MethodSignature) joinPoint.getSignature()).getMethod(), keyExpression);
}
return keyExpression;
}
private static Object getKeyValue(Object object, Object[] args, Class<?> clazz, Method method, String keyExpression) {
if (StringUtils.hasText(keyExpression)) {
EvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
return evaluator.key(keyExpression, methodKey, evaluationContext);
}
return SimpleKeyGenerator.generateKey(args);
}
}
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ReactiveRedisCacheable {
String key();
String cacheName();
long duration() default 1L;
}
#RestController
#RequestMapping("api/pub/v1")
public class TestRestController{
#ReactiveRedisCacheable(cacheName = "test-cache", key = "#name", duration = 1L)
#GetMapping(value = "test")
public Mono<String> getName(#RequestParam(value = "name") String name){
return Mono.just(name);
}
}
#Configuration
public class Config {
#Bean
public ReactiveRedisCacheAspect reactiveRedisCache (ReactiveRedisCacheAspect reactiveRedisCacheAspect) {
return reactiveRedisCacheAspect;
}
}
logs:
ReactiveRedisCacheAspect cacheableAround.... - {}execution(Mono com.abc.def.xxx.rest.TestRestcontroller.getName(String))
2021-06-04 15:36:23.096 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ctor-http-nio-3] c.m.s.c.a.i.ReactiveRedisCacheAspect : Returning Mono
2021-06-04 15:36:23.097 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ctor-http-nio-3] c.m.s.c.repository.CacheRepositoryImpl : searching key: (bff_pippo)
ReactiveRedisCacheAspect cacheableAround.... - {}execution(Mono com.abc.def.xxx.rest.TestRestcontroller.getName(String))
2021-06-04 15:36:23.236 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.a.i.ReactiveRedisCacheAspect : Returning Mono
2021-06-04 15:36:23.236 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : searching key: (bff_pippo)
2021-06-04 15:36:23.250 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : saving obj: (key:bff_pippo) (expiresIn:3600s)
2021-06-04 15:36:23.275 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : saving obj: (key:bff_pippo) (expiresIn:3600s)
So far I would have expected the cacheableAround would be executed only once, but what happens its a bit weird, if the object is present on redis the method is executed only once but if is not present the method is executed twice which it doesn't make sense, moreover it should be the business logic to manage what to do inside the method.
Thanks in advance!
You did not mention whether you use native AspectJ via load- or compile-time weaving or simply Spring AOP. Because I see not #Component annotation on your aspect, it might as well be native AspectJ, unless you configure your beans via #Bean factory methods in a configuration class or XML.
Assuming that you are using full AspectJ, a common problem newbies coming from Spring AOP have, is that they are not used to the fact that AspectJ not only intercepts execution joinpoints, but also call ones. This leads to the superficial perception that the same joinpoint is intercepted twice. But in reality, it is once the method call (in the class from which the call is made) and once the method execution (in the class where the target method resides). This is easy to determine if at the beginning of your advice method you simply log the joinpoint. In your case:
System.out.println(proceedingJoinPoint);
If then on the console you see something like
call(public void org.acme.MyClass.myMethod())
execution(public void org.acme.MyClass.myMethod())
then you know what is happening.
In case you use Spring AOP, probably it is an issue with the aspect or the Redis caching behaviour that is different from your expectation.
When using toList() ConstraintCollector in optaplanner 8.1 like:
factory.from(Lesson.class)
.groupBy(Lesson::getCourse, ConstraintCollectors.toList()).penalize(...);
I run into:
Exception executing consequence for rule "foo" in model: java.lang.ClassCastException: class model.Lesson cannot be cast to class java.util.List (model.Lesson is in unnamed module of loader 'app'; java.util.List is in module java.base of loader 'bootstrap')
at org.drools.core.runtime.rule.impl.DefaultConsequenceExceptionHandler.handleException(DefaultConsequenceExceptionHandler.java:39)
Question: Is this a optaplanner bug, or is my code wrong?
The most simple complete reproducer I find is:
#PlanningSolution
public class OptaplannerIssue2 implements ConstraintProvider {
#Override
public Constraint[] defineConstraints(ConstraintFactory factory) {
return new Constraint[] {factory.from(IssueEntity.class)
.groupBy(IssueEntity::getValue, ConstraintCollectors.toList())
.penalize("x", HardSoftScore.ofHard(1), (entity, enityList) -> 2)};
}
#PlanningScore
private HardSoftScore score = HardSoftScore.ZERO;
#PlanningEntityCollectionProperty
private final List<IssueEntity> entities = new ArrayList<IssueEntity>();
public List<IssueEntity> getEntities() {
return entities;
}
#ValueRangeProvider(id = "valueRange")
public CountableValueRange<Integer> getValueRange() {
return ValueRangeFactory.createIntValueRange(0, 4);
}
public static void main() {
// create Entity
OptaplannerIssue2 issue = new OptaplannerIssue2();
IssueEntity e1 = new IssueEntity();
issue.entities.add(e1);
// solve
SolverFactory<OptaplannerIssue2> solverFactory = SolverFactory.create(new SolverConfig()
.withEnvironmentMode(EnvironmentMode.FULL_ASSERT).withSolutionClass(OptaplannerIssue2.class)
.withEntityClasses(IssueEntity.class)
.withScoreDirectorFactory(
new ScoreDirectorFactoryConfig().withConstraintProviderClass(OptaplannerIssue2.class))
.withTerminationConfig(new TerminationConfig().withSecondsSpentLimit(5L)).withPhases(
new ConstructionHeuristicPhaseConfig()
.withConstructionHeuristicType(ConstructionHeuristicType.FIRST_FIT),
new LocalSearchPhaseConfig().withLocalSearchType(LocalSearchType.LATE_ACCEPTANCE)));
Solver<OptaplannerIssue2> solver = solverFactory.buildSolver();
solver.solve(issue);
}
}
With the following entity-class:
#PlanningEntity
public class IssueEntity {
#PlanningVariable(valueRangeProviderRefs = {"valueRange"})
Integer value;
public Integer getValue() {
return value;
}
}
In the related thread: Optaplanner GroupBy with toList not working as expected the questioner didn't provide all information to commentators trying to help and when I provided reproducer there I got deleted, so I had to ask new question.
The behavior you describe is a bug in OptaPlanner, which we have now fixed. Please upgrade to the next release of OptaPlanner, which at the time of writing this answer will be OptaPlanner 8.2.0.
For details, see PLANNER-2305.
This method is to get the inputs that includes three EditText boxes and one spinner which is populated from string-array.The click of the button is the trigger for undertaking the calculations. While the OnClick method is pressed the application crashes. The code is as follows
package biz.avksons.siddharth.hydro_basic_calc;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
public class UoE extends AppCompatActivity {
// Define variables for Spinner here
Spinner spinner_1;
String spin_select;
boolean dbug=true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_uoe);
}
// ************************************************************************************
// Start the method to calculate the units of electricity here
public void onUoECalc_ButtonClick(View v) {
/* Define Custom method for calculating the Units of electricity on clicking the Calculate button*/
//Processing variable for count
boolean flag = false; // Used to check if the inputs done in the EditText are valid or not, else there will an exception which would lead to application crash
/* Initiate the Variables here*/
//-----------------------------------------------------
//Allocate the ids of EditText to the variables for further use
EditText et_capacity = (EditText) findViewById(R.id.et_uoe_capacity);
EditText et_plf = (EditText) findViewById(R.id.et_uoe_plf);
EditText et_availability = (EditText) findViewById(R.id.et_uoe_availability);
EditText et_energy = (EditText) findViewById(R.id.et_uoe_mus);
//Variable for deciding Timeperiod
Double time_period = 365.00;
//---DEBUG <code></code>
if (dbug)
{
Toast.makeText(this, "Spiner selection : " + spin_select, Toast.LENGTH_SHORT).show();
}
//Set flag to true only if the inputs in the two edittext, discharge and head are sensible inputs
if (checkedittext(et_capacity) && checkedittext(et_plf) && checkedittext(et_availability)) {
flag = true;
} else {
//If the inputs in discharge are not sensible then display the message to input a sensible input before moving further
Toast.makeText(this, "Please enter valid inputs", Toast.LENGTH_LONG).show();
// Set the EditText box of the output to blank if the inputs are not valid as tested in the above statements
et_capacity.setText("");
}
// do this operation only if the flag is set to true which are tested above
if (flag == true) {
//-------Initiating Spinner here
addListenerOnSpinnerItemSelection();
// Set selection default for Spinner here
// spinner_1.setSelection(1);
//--------Allocation of Spin Button Here-------------
if (dbug)
{
Toast.makeText(this, "Spin_select"+ spin_select, Toast.LENGTH_SHORT).show();
}
switch (spin_select) {
case "1 yr":
time_period = 365.00;
case "6 mon":
time_period = 180.00;
case "3 mon":
time_period = 90.00;
}
//-------End of Spin Button--------------
//Assign the values here
Double d_capacity = Double.parseDouble(et_capacity.getText().toString());
Double d_plf = Double.parseDouble(et_plf.getText().toString());
Double d_availability = Double.parseDouble(et_availability.getText().toString());
//-DEBUG Code----
if(dbug)
{
Toast.makeText(this, "Time Period selected" + time_period.toString(), Toast.LENGTH_SHORT).show();
}
Double d_energy = Double.valueOf((d_capacity * 1000 * 24 * time_period * d_plf * d_availability) / 1000000);
et_energy.setText(Double.toString(Math.round(d_energy))); // In MW
// capacity.setText(String.format(".2%f",Double.toString(C)));
}
}
//Method to check for validity of contents in edittext
public boolean checkedittext(EditText edtxt) {
if (edtxt.getText().toString().matches("")) {
return false;
} else {
return true;
}
}
//--------Method to get the selected option from Spinner
public void addListenerOnSpinnerItemSelection() {
//------Declare a variable on Spinner and allocate the id here
spinner_1 = (Spinner) findViewById(R.id.spin_uoe_period);
spinner_1.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
spin_select = parent.getItemAtPosition(position).toString();
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
public void onHelp_UoE(View v)
{
Toast.makeText(this,"Please enter valid figures in the boxes mentioned and select the period you are interested in; from the dropdown menu before pressing calculate",Toast.LENGTH_LONG).show();
}
}
The error in Logcat is as follows
E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.IllegalStateException: Could not execute method for android:onClick
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
at android.view.View.performClick(View.java:4240)
at android.view.View$PerformClick.run(View.java:17721)
at android.os.Handler.handleCallback(Handler.java:730)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5103)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
at android.view.View.performClick(View.java:4240)
at android.view.View$PerformClick.run(View.java:17721)
at android.os.Handler.handleCallback(Handler.java:730)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5103)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at biz.avksons.siddharth.hydro_basic_calc.UoE.onUoECalc_ButtonClick(UoE.java:84)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
at android.view.View.performClick(View.java:4240)
at android.view.View$PerformClick.run(View.java:17721)
at android.os.Handler.handleCallback(Handler.java:730)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5103)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
I tried to find the NullPointerException error which as per the code suggests that the spinner_select variable is not initialized. My understanding tells me that the spinner_select variable should be initialized every time the method is called.
Request for assistance..Thank You
I hope you have missed
break;
in switch case statement