We have a scenario like delivery the new order and pickup the old order from same visit. For this scenario we have added pickCapacity and deliveryCapacity and also calculating accumulated capacity(weightUsedAtEndOfVisit) at end of visit in ArrivalTimeUpdatingVariableListener class.
This is working fine, But some times weightUsedAtEndOfVisit values are not getting updated in previous chain when visit change one chain to another.
TimeWindowedVisit
#PlanningEntity
public class TimeWindowedVisit{
protected Long dropWeight;
protected Long pickupWeight;
private Long weightUsedAtEndOfVisit;
#CustomShadowVariable(variableListenerRef = #PlanningVariableReference(variableName = "arrivalTime"))
public Long getWeightUsedAtEndOfVisit() {
return weightUsedAtEndOfVisit;
}
public void setWeightUsedAtEndOfVisit(Long weightUsedAtEndOfVisit) {
this.weightUsedAtEndOfVisit = weightUsedAtEndOfVisit;
}
}
ArrivalTimeUpdatingVariableListener
public class ArrivalTimeUpdatingVariableListener implements VariableListener<TimeWindowedPlanningVisit> {
#Override
public void afterEntityAdded(ScoreDirector scoreDirector, TimeWindowedPlanningVisit planningVisit) {
updateUsedCapacityAtVisit(scoreDirector, planningVisit);
}
#Override
public void afterVariableChanged(ScoreDirector scoreDirector, TimeWindowedPlanningVisit planningVisit) {
updateUsedCapacityAtVisit(scoreDirector, planningVisit);
}
...
protected void updateUsedCapacityAtVisit(ScoreDirector<?> scoreDirector, TimeWindowedPlanningVisit sourcePlanningVisit) {
TimeWindowedPlanningVisit shadowPlanningVisit;
if (sourcePlanningVisit.getPreviousStandstill() != null) {
shadowPlanningVisit = sourcePlanningVisit;
while (shadowPlanningVisit != null) {
if (shadowPlanningVisit.getPreviousStandstill() instanceof TimeWindowedPlanningVisit) {
shadowPlanningVisit = (TimeWindowedPlanningVisit) shadowPlanningVisit.getPreviousStandstill();
} else {
break;
}
}
//calculate the first stop in the chain, will be needed to get a running capacity total at each stop
//we broadcast the running total so that we can determine if we ever exceed vehicle capacity
TimeWindowedPlanningVisit firstVisit = shadowPlanningVisit;
// begin at the first stop and evaluate all of the stops to see what capacity we will be leaving the depot with
// the capacity will be a sum of all the items that need to be dropped (delivered) at the location.
long runningCapacity = 0;
while (shadowPlanningVisit != null) {
if (shadowPlanningVisit.isWarehousePickup()) {
runningCapacity += shadowPlanningVisit.getDropWeight();
}
shadowPlanningVisit = shadowPlanningVisit.getNextPlanningVisit();
}
shadowPlanningVisit = firstVisit;
// now that we know the full capcity we will be leaving the depot with, calculate the capacity at each
// stop, accounting for capacity being dropped (delivered) and picked up (returned) along the way
while (shadowPlanningVisit != null) {
runningCapacity -= shadowPlanningVisit.getDropWeight();
runningCapacity += shadowPlanningVisit.getPickupWeight();
if (!Objects.equals(runningCapacity, shadowPlanningVisit.getWeightUsedAtEndOfVisit())) {
scoreDirector.beforeVariableChanged(shadowPlanningVisit, "weightUsedAtEndOfVisit");
shadowPlanningVisit.setWeightUsedAtEndOfVisit(runningCapacity);
scoreDirector.afterVariableChanged(shadowPlanningVisit, "weightUsedAtEndOfVisit");
}
shadowPlanningVisit = shadowPlanningVisit.getNextPlanningVisit();
}
}
}
}
Constraint Class
protected Constraint vehicleCapacityStopLevel(ConstraintFactory factory) {
System.out.println("vehicleCapacityStopLevel...");
return factory.from(TimeWindowedPlanningVisit.class)
.filter(visit -> visit.getWeightUsedAtEndOfVisit() > visit.getVehicle().getWeight())
.penalizeLong("vehicle capacity exceeded at stop",
BendableLongScore.ofHard(2, 1, 0, 1),
visit -> visit.getWeightUsedAtEndOfVisit() - visit.getVehicle().getWeight());
}
Your #CustomShadowVariable should have a sources field, and not a variableListenerRef value. The latter is to piggy-back on another custom shadow variable's variable listener.
This is also an issue in optaplanner, because a variableListenerRef should fail fast if it doesn't lead to a custom shadow variable. We'll confirm and fix that.
i've written a utility to monitor individual business transactions. For example, Alice calls a method which calls more methods and i want info on just Alice's call, separate from Bob's call to the same method.
Right now the entry point creates a Transaction object and it's passed as an argument to each method:
class Example {
public Item getOrderEntryPoint(int orderId) {
Transaction transaction = transactionManager.create();
transaction.trace("getOrderEntryPoint");
Order order = getOrder(orderId, transaction);
transaction.stop();
logger.info(transaction);
return item;
}
private Order getOrder(int orderId, Transaction t) {
t.trace("getOrder");
Order order = getItems(itemId, t);
t.addStat("number of items", order.getItems().size());
for (Item item : order.getItems()) {
SpecialOffer offer = getSpecialOffer(item, t);
if (null != offer) {
t.incrementStat("offers", 1);
}
}
t.stop();
return order;
}
private SpecialOffer getSpecialOffer(Item item, Transaction t) {
t.trace("getSpecialOffer(" + item.id + ")", TraceCategory.Database);
return offerRepository.getByItem(item);
t.stop();
}
}
This will print to the log something like:
Transaction started by Alice at 10:42
Statistics:
number of items : 3
offers : 1
Category Timings (longest first):
DB : 2s 903ms
code : 187ms
Timings (longest first):
getSpecialOffer(1013) : 626ms
getItems : 594ms
Trace:
getOrderEntryPoint (7ms)
getOrder (594ms)
getSpecialOffer(911) (90ms)
getSpecialOffer(1013) (626ms)
getSpecialOffer(2942) (113ms)
It works great but passing the transaction object around is ugly. Someone suggested AOP but i don't see how to pass the transaction created in the first method to all the other methods.
The Transaction object is pretty simple:
public class Transaction {
private String uuid = UUID.createRandom();
private List<TraceEvent> events = new ArrayList<>();
private Map<String,Int> stats = new HashMap<>();
}
class TraceEvent {
private String name;
private long durationInMs;
}
The app that uses it is a Web app, and this multi-threaded, but the individual transactions are on a single thread - no multi-threading, async code, competition for resources, etc.
My attempt at an annotation:
#Around("execution(* *(..)) && #annotation(Trace)")
public Object around(ProceedingJoinPoint point) {
String methodName = MethodSignature.class.cast(point.getSignature()).getMethod().getName();
//--- Where do i get this call's instance of TRANSACTION from?
if (null == transaction) {
transaction = TransactionManager.createTransaction();
}
transaction.trace(methodName);
Object result = point.proceed();
transaction.stop();
return result;
Introduction
Unfortunately, your pseudo code does not compile. It contains several syntactical and logical errors. Furthermore, some helper classes are missing. If I did not have spare time today and was looking for a puzzle to solve, I would not have bothered making my own MCVE out of it, because that would actually have been your job. Please do read the MCVE article and learn to create one next time, otherwise you will not get a lot of qualified help here. This was your free shot because you are new on SO.
Original situation: passing through transaction objects in method calls
Application helper classes:
package de.scrum_master.app;
public class Item {
private int id;
public Item(int id) {
this.id = id;
}
public int getId() {
return id;
}
#Override
public String toString() {
return "Item[id=" + id + "]";
}
}
package de.scrum_master.app;
public class SpecialOffer {}
package de.scrum_master.app;
public class OfferRepository {
public SpecialOffer getByItem(Item item) {
if (item.getId() < 30)
return new SpecialOffer();
return null;
}
}
package de.scrum_master.app;
import java.util.ArrayList;
import java.util.List;
public class Order {
private int id;
public Order(int id) {
this.id = id;
}
public List<Item> getItems() {
List<Item> items = new ArrayList<>();
int offset = id == 12345 ? 0 : 1;
items.add(new Item(11 + offset, this));
items.add(new Item(22 + offset, this));
items.add(new Item(33 + offset, this));
return items;
}
}
Trace classes:
package de.scrum_master.trace;
public enum TraceCategory {
Code, Database
}
package de.scrum_master.trace;
class TraceEvent {
private String name;
private TraceCategory category;
private long durationInMs;
private boolean finished = false;
public TraceEvent(String name, TraceCategory category, long startTime) {
this.name = name;
this.category = category;
this.durationInMs = startTime;
}
public long getDurationInMs() {
return durationInMs;
}
public void setDurationInMs(long durationInMs) {
this.durationInMs = durationInMs;
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
#Override
public String toString() {
return "TraceEvent[name=" + name + ", category=" + category +
", durationInMs=" + durationInMs + ", finished=" + finished + "]";
}
}
Transaction classes:
Here I tried to mimic your own Transaction class with as few as possible changes, but there was a lot I had to add and modify in order to emulate a simplified version of your trace output. This is not thread-safe and the way I am locating the last unfinished TraceEvent is not nice and only works cleanly if there are not exceptions. But you get the idea, I hope. The point is to just make it basically work and subsequently get log output similar to your example. If this was originally my code, I would have solved it differently.
package de.scrum_master.trace;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
public class Transaction {
private String uuid = UUID.randomUUID().toString();
private List<TraceEvent> events = new ArrayList<>();
private Map<String, Integer> stats = new HashMap<>();
public void trace(String message) {
trace(message, TraceCategory.Code);
}
public void trace(String message, TraceCategory category) {
events.add(new TraceEvent(message, category, System.currentTimeMillis()));
}
public void stop() {
TraceEvent event = getLastUnfinishedEvent();
event.setDurationInMs(System.currentTimeMillis() - event.getDurationInMs());
event.setFinished(true);
}
private TraceEvent getLastUnfinishedEvent() {
return events
.stream()
.filter(event -> !event.isFinished())
.reduce((first, second) -> second)
.orElse(null);
}
public void addStat(String text, int size) {
stats.put(text, size);
}
public void incrementStat(String text, int increment) {
Integer currentCount = stats.get(text);
if (currentCount == null)
currentCount = 0;
stats.put(text, currentCount + increment);
}
#Override
public String toString() {
return "Transaction {" +
toStringUUID() +
toStringStats() +
toStringEvents() +
"\n}\n";
}
private String toStringUUID() {
return "\n uuid = " + uuid;
}
private String toStringStats() {
String result = "\n stats = {";
for (Entry<String, Integer> statEntry : stats.entrySet())
result += "\n " + statEntry;
return result + "\n }";
}
private String toStringEvents() {
String result = "\n events = {";
for (TraceEvent event : events)
result += "\n " + event;
return result + "\n }";
}
}
package de.scrum_master.trace;
public class TransactionManager {
public Transaction create() {
return new Transaction();
}
}
Example driver application:
package de.scrum_master.app;
import de.scrum_master.trace.TraceCategory;
import de.scrum_master.trace.Transaction;
import de.scrum_master.trace.TransactionManager;
public class Example {
private TransactionManager transactionManager = new TransactionManager();
private OfferRepository offerRepository = new OfferRepository();
public Order getOrderEntryPoint(int orderId) {
Transaction transaction = transactionManager.create();
transaction.trace("getOrderEntryPoint");
sleep(100);
Order order = getOrder(orderId, transaction);
transaction.stop();
System.out.println(transaction);
return order;
}
private Order getOrder(int orderId, Transaction t) {
t.trace("getOrder");
sleep(200);
Order order = new Order(orderId);
t.addStat("number of items", order.getItems().size());
for (Item item : order.getItems()) {
SpecialOffer offer = getSpecialOffer(item, t);
if (null != offer)
t.incrementStat("special offers", 1);
}
t.stop();
return order;
}
private SpecialOffer getSpecialOffer(Item item, Transaction t) {
t.trace("getSpecialOffer(" + item.getId() + ")", TraceCategory.Database);
sleep(50);
SpecialOffer specialOffer = offerRepository.getByItem(item);
t.stop();
return specialOffer;
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Example().getOrderEntryPoint(12345);
new Example().getOrderEntryPoint(23456);
}
}
If you run this code, the output is as follows:
Transaction {
uuid = 62ec9739-bd32-4a56-b6b3-a8a13624961a
stats = {
special offers=2
number of items=3
}
events = {
TraceEvent[name=getOrderEntryPoint, category=Code, durationInMs=561, finished=true]
TraceEvent[name=getOrder, category=Code, durationInMs=451, finished=true]
TraceEvent[name=getSpecialOffer(11), category=Database, durationInMs=117, finished=true]
TraceEvent[name=getSpecialOffer(22), category=Database, durationInMs=69, finished=true]
TraceEvent[name=getSpecialOffer(33), category=Database, durationInMs=63, finished=true]
}
}
Transaction {
uuid = a420cd70-96e5-44c4-a0a4-87e421d05e87
stats = {
special offers=2
number of items=3
}
events = {
TraceEvent[name=getOrderEntryPoint, category=Code, durationInMs=469, finished=true]
TraceEvent[name=getOrder, category=Code, durationInMs=369, finished=true]
TraceEvent[name=getSpecialOffer(12), category=Database, durationInMs=53, finished=true]
TraceEvent[name=getSpecialOffer(23), category=Database, durationInMs=63, finished=true]
TraceEvent[name=getSpecialOffer(34), category=Database, durationInMs=53, finished=true]
}
}
AOP refactoring
Preface
Please note that I am using AspectJ here because two things about your code would never work with Spring AOP because it works with a delegation pattern based on dynamic proxies:
self-invocation (internally calling a method of the same class or super-class)
intercepting private methods
Because of these Spring AOP limitations I advise you to either refactor your code so as to avoid the two issues above or to configure your Spring applications to use full AspectJ via LTW (load-time weaving) instead.
As you noticed, my sample code does not use Spring at all because AspectJ is completely independent of Spring and works with any Java application (or other JVM languages, too).
Refactoring idea
Now what should you do in order to get rid of passing around tracing information (Transaction objects), polluting your core application code and tangling it with trace calls?
You extract transaction tracing into an aspect taking care of all trace(..) and stop() calls.
Unfortunately your Transaction class contains different types of information and does different things, so you cannot completely get rid of context information about how to trace for each affected method. But at least you can extract that context information from the method bodies and transform it into a declarative form using annotations with parameters.
These annotations can be targeted by an aspect taking care of handling transaction tracing.
Added and updated code, iteration 1
Annotations related to transaction tracing:
package de.scrum_master.trace;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target(METHOD)
public #interface TransactionEntryPoint {}
package de.scrum_master.trace;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target(METHOD)
public #interface TransactionTrace {
String message() default "__METHOD_NAME__";
TraceCategory category() default TraceCategory.Code;
String addStat() default "";
String incrementStat() default "";
}
Refactored application classes with annotations:
package de.scrum_master.app;
import java.util.ArrayList;
import java.util.List;
import de.scrum_master.trace.TransactionTrace;
public class Order {
private int id;
public Order(int id) {
this.id = id;
}
#TransactionTrace(message = "", addStat = "number of items")
public List<Item> getItems() {
List<Item> items = new ArrayList<>();
int offset = id == 12345 ? 0 : 1;
items.add(new Item(11 + offset));
items.add(new Item(22 + offset));
items.add(new Item(33 + offset));
return items;
}
}
Nothing much here, only added an annotation to getItems(). But the sample application class changes massively, getting much cleaner and simpler:
package de.scrum_master.app;
import de.scrum_master.trace.TraceCategory;
import de.scrum_master.trace.TransactionEntryPoint;
import de.scrum_master.trace.TransactionTrace;
public class Example {
private OfferRepository offerRepository = new OfferRepository();
#TransactionEntryPoint
#TransactionTrace
public Order getOrderEntryPoint(int orderId) {
sleep(100);
Order order = getOrder(orderId);
return order;
}
#TransactionTrace
private Order getOrder(int orderId) {
sleep(200);
Order order = new Order(orderId);
for (Item item : order.getItems()) {
SpecialOffer offer = getSpecialOffer(item);
// Do something with special offers
}
return order;
}
#TransactionTrace(category = TraceCategory.Database, incrementStat = "specialOffers")
private SpecialOffer getSpecialOffer(Item item) {
sleep(50);
SpecialOffer specialOffer = offerRepository.getByItem(item);
return specialOffer;
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Example().getOrderEntryPoint(12345);
new Example().getOrderEntryPoint(23456);
}
}
See? Except for a few annotations there is nothing left of the transaction tracing logic, the application code only takes care of its core concern. If you also remove the sleep() method which only makes the application slower for demonstration purposes (because we want some nice statistics with measured times >0 ms), the class gets even more compact.
But of course we need to put the transaction tracing logic somewhere, more precisely modularise it into an AspectJ aspect:
Transaction tracing aspect:
package de.scrum_master.trace;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
#Aspect("percflow(entryPoint())")
public class TransactionTraceAspect {
private static TransactionManager transactionManager = new TransactionManager();
private Transaction transaction = transactionManager.create();
#Pointcut("execution(* *(..)) && #annotation(de.scrum_master.trace.TransactionEntryPoint)")
private static void entryPoint() {}
#Around("execution(* *(..)) && #annotation(transactionTrace)")
public Object doTrace(ProceedingJoinPoint joinPoint, TransactionTrace transactionTrace) throws Throwable {
preTrace(transactionTrace, joinPoint);
Object result = joinPoint.proceed();
postTrace(transactionTrace);
addStat(transactionTrace, result);
incrementStat(transactionTrace, result);
return result;
}
private void preTrace(TransactionTrace transactionTrace, ProceedingJoinPoint joinPoint) {
String traceMessage = transactionTrace.message();
if ("".equals(traceMessage))
return;
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
if ("__METHOD_NAME__".equals(traceMessage)) {
traceMessage = signature.getName() + "(";
traceMessage += Arrays.stream(joinPoint.getArgs()).map(arg -> arg.toString()).collect(Collectors.joining(", "));
traceMessage += ")";
}
transaction.trace(traceMessage, transactionTrace.category());
}
private void postTrace(TransactionTrace transactionTrace) {
if ("".equals(transactionTrace.message()))
return;
transaction.stop();
}
private void addStat(TransactionTrace transactionTrace, Object result) {
if ("".equals(transactionTrace.addStat()) || result == null)
return;
if (result instanceof Collection)
transaction.addStat(transactionTrace.addStat(), ((Collection<?>) result).size());
else if (result.getClass().isArray())
transaction.addStat(transactionTrace.addStat(), Array.getLength(result));
}
private void incrementStat(TransactionTrace transactionTrace, Object result) {
if ("".equals(transactionTrace.incrementStat()) || result == null)
return;
transaction.incrementStat(transactionTrace.incrementStat(), 1);
}
#After("entryPoint()")
public void logFinishedTransaction(JoinPoint joinPoint) {
System.out.println(transaction);
}
}
Let me explain what this aspect does:
#Pointcut(..) entryPoint() says: Find me all methods in the code annotated by #TransactionEntryPoint. This pointcut is used in two places:
#Aspect("percflow(entryPoint())") says: Create one aspect instance for each control flow beginning at a transaction entry point.
#After("entryPoint()") logFinishedTransaction(..) says: Execute this advice (AOP terminology for a method linked to a pointcut) after an entry point methods is finished. The corresponding method just prints the transaction statistics just like in the original code at the end of Example.getOrderEntryPoint(..).
#Around("execution(* *(..)) && #annotation(transactionTrace)") doTrace(..)says: Wrap methods annotated by TransactionTrace and do the following (method body):
add new trace element and start measuring time
execute original (wrapped) method and store result
update trace element with measured time
add one type of statistics (optional)
increment another type of statistics (optional)
return wrapped method's result to its caller
The private methods are just helpers for the #Around advice.
The console log when running the updated Example class and active AspectJ is:
Transaction {
uuid = 4529d325-c604-441d-8997-45ca659abb14
stats = {
specialOffers=2
number of items=3
}
events = {
TraceEvent[name=getOrderEntryPoint(12345), category=Code, durationInMs=468, finished=true]
TraceEvent[name=getOrder(12345), category=Code, durationInMs=366, finished=true]
TraceEvent[name=getSpecialOffer(Item[id=11]), category=Database, durationInMs=59, finished=true]
TraceEvent[name=getSpecialOffer(Item[id=22]), category=Database, durationInMs=50, finished=true]
TraceEvent[name=getSpecialOffer(Item[id=33]), category=Database, durationInMs=51, finished=true]
}
}
Transaction {
uuid = ef76a996-8621-478b-a376-e9f7a729a501
stats = {
specialOffers=2
number of items=3
}
events = {
TraceEvent[name=getOrderEntryPoint(23456), category=Code, durationInMs=452, finished=true]
TraceEvent[name=getOrder(23456), category=Code, durationInMs=351, finished=true]
TraceEvent[name=getSpecialOffer(Item[id=12]), category=Database, durationInMs=50, finished=true]
TraceEvent[name=getSpecialOffer(Item[id=23]), category=Database, durationInMs=50, finished=true]
TraceEvent[name=getSpecialOffer(Item[id=34]), category=Database, durationInMs=50, finished=true]
}
}
You see, it looks almost identical to the original application.
Idea for further simplification, iteration 2
When reading method Example.getOrder(int orderId) I was wondering why you are calling order.getItems(), looping over it and calling getSpecialOffer(item) inside the loop. In your sample code you do not use the results for anything other than updating the transaction trace object. I am assuming that in your real code you do something with the order and with the special offers in that method.
But just in case you really do not need those calls inside that method, I suggest
you factor the calls out right into the aspect, getting rid of the TransactionTrace annotation parameters String addStat() and String incrementStat().
The Example code would get even simpler and
the annotation #TransactionTrace(message = "", addStat = "number of items") in class would go away, too.
I am leaving this refactoring to you if you think it makes sense.
I am using the BLOC pattern for my latest Flutter app and I started out using something like this for my output streams:
class MyBloc {
// Outputs
final Stream<List<Todo>> todos;
factory MyBloc(TodosInteractor interactor) {
final todosController = BehaviorSubject<List<Todo>>()
..addStream(interactor.todos);
return MyBloc._(todosController);
}
MyBloc._(this.todos);
}
but slowly I found myself doing something more like this, using a method (or getter) after awhile:
class MyBloc {
final TodosInteractor _interactor;
// Outputs
Stream<List<Todo>> todos(){
return _interactor.todos;
}
MyBloc(this._interactor) { }
}
For people who want to see... getter for todos in TodosInteractor:
Stream<List<Todo>> get todos {
return repository
.todos()
.map((entities) => entities.map(Todo.fromEntity).toList());
}
When I look at the differing code, I see that the first example uses a field versus a method to expose the stream but I couldn't figure out why I would choose one over the other. It seems to me that creating another controller just to push through the stream is a little much... Is there a benefit to this other than being immutable in my todos stream definition? Or am I just splitting hairs?
Well maybe this will not be a best answer but it is a good practice expose your output stream using get methods. Below a example of a bloc class that i have written to a project using RxDart.
class CityListWidgetBloc {
final _cityInput = PublishSubject<List<Cidade>>();
final _searchInput = new PublishSubject<String>();
final _selectedItemsInput = new PublishSubject<List<Cidade>>();
// exposing stream using get methods
Observable<List<Cidade>> get allCities => _cityInput.stream;
Observable<List<Cidade>> get selectedItems => _selectedItemsInput.stream;
List<Cidade> _searchList = new List();
List<Cidade> _selectedItems = new List();
List<Cidade> _mainDataList;
CityListWidgetBloc() {
//init search stream
_searchInput.stream.listen((searchPattern) {
if (searchPattern.isEmpty) {
_onData(_mainDataList); // resend local data list
} else {
_searchList.clear();
_mainDataList.forEach((city) {
if (city.nome.toLowerCase().contains(searchPattern.toLowerCase())) {
_searchList.add(city);
}
});
_cityInput.sink.add(_searchList);
}
});
}
//getting data from firebase
getCity( {#required String key}) {
FirebaseStateCityHelper.getCitiesFrom(key, _onData);
//_lastKey = key;
}
searchFor(String pattern) {
_searchInput.sink.add(pattern);
}
void _onData(List<Cidade> list) {
_mainDataList = list;
list.sort((a, b) => (a.nome.compareTo(b.nome)));
_cityInput.sink.add(list);
}
bool isSelected(Cidade item) {
return _selectedItems.contains(item);
}
void selectItem(Cidade item) {
_selectedItems.add(item);
_selectedItemsInput.sink.add(_selectedItems);
}
void selectItems(List<Cidade> items){
_selectedItems.addAll( items);
_selectedItemsInput.sink.add( _selectedItems );
}
void removeItem(Cidade item) {
_selectedItems.remove(item);
_selectedItemsInput.sink.add(_selectedItems);
}
dispose() {
_cityInput.close();
_searchInput.close();
_selectedItemsInput.close();
}
}
I am making an application which uses serial communication. In SerialEvent method of that class, I am awaiting for a input from COM port, and then I want to pass it to the controller class of an .fxml screen.
Input will always be 8 bytes, and it works correctly inside that thread (I read the input and by printing it to the output, I see that the String is correct). However, when I try to pass it "in real time" to the controller class, I have a problem.
If I pass it directly, it does receieve it, but I can't invoke anything later (Not on FX Application Thread exception), I know that I can't do it that way, that I need to use Platform.runLater or similair solution, but if I use it that way, my controller class never receives that input, textField which I am trying to update stays blank.
I will copy part of the code here, and I am hoping that someone tell me what I'm doing wrong.
SERIALEVENT METHOD OF ANOTHER CLASS
#Override
public void serialEvent(SerialPortEvent spe) {
if (spe.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
try {
byte singleData = (byte) input.read();
logText = new String(new byte[]{singleData});
bytes.add(logText);
if(bytes.size() == 8) {
for (int i = 0; i < bytes.size(); i++) {
inputText += bytes.get(i);
}
if(inputText.length() == 8) {
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.getInputString(inputText);
}
});
}
bytes.clear();
inputText = "";
}
} catch (Exception e) {
logText = "Failed to read data. (" + e.toString() + ")";
controller.getInputString(logText);
}
}
}
GETINPUT METHOD OF THE CONTROLLER CLASS
#Override
public void getInputString(String input) {
firstSerialNumberField.setText(input);
}
When using it this way, my firstSerialNumberField never gets that input.
---EDIT---
SETCONTROLLER METHOD OF THE SERIALPORTLISTENER CLASS
public void setController(SerialController controller) {
this.controller = controller;
}
INITIALIZE SCREEN IN SCREEN HANDLER CLASS
serialCommunication = new SerialCommunication(this);
loader = new FXMLLoader();
loader.setLocation(getClass().getResource(path));
pane = loader.load(getClass().getResource(path).openStream());
serialController = (SerialController) loader.getController();
serialController.setScreenHandler(this);
serialController.setSerialCommunication(serialCommunication);
serialCommunication.setController(serialController);
parent = loader.getRoot();
stage = new Stage();
stage.setScene(new Scene(parent));
stage.setTitle(title);
stage.setResizable(false);
stage.sizeToScene();
stage.centerOnScreen();
stage.initModality(Modality.APPLICATION_MODAL);
stage.showAndWait();
You are passing a reference to inputText to the (inappropriately-named) getInputText() method in the controller. inputText is presumably a field in the class implementing the port listener. However, as soon as you pass it, you then set it back to an empty string:
if(inputText.length() == 8) {
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.getInputString(inputText);
}
});
}
bytes.clear();
inputText = "";
Since inputText is being accessed from multiple threads, there is no guarantee as to which order things will happen: whether controller.getInputText(inputText) will execute first, or whether inputText = ""; will execute first. So you may end up setting the text field to an empty string.
What I think you intend to do is:
if(inputText.length() == 8) {
final String numberFieldText = inputText ;
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.getInputString(numberFieldText);
}
});
}
or more succinctly:
if(inputText.length() == 8) {
final String numberFieldText = inputText ;
Platform.runLater(() -> controller.getInputString(numberFieldText));
}
for my Wicket Wizard I want to display an information like: "Wizard step 1 of 4". I started by getting the number of steps and I already came across the first problem:
public WizardPanel(String id) {
super(id, false);
// false deactivates the default style.
setDefaultModel(new CompoundPropertyModel<WizardPanel>(this));
WizardModel model = new WizardModel();
model.add(new FirstStep());
model.add(new SecondStep());
model.add(new ThirdStep());
model.add(new ConfirmationStep());
Iterator<IWizardStep> iterator = model.stepIterator();
for(int i = 1; iterator.hasNext(); i ++){
System.out.println(String.valueOf(i));
}
init(model);
}
My Iterator creates an infinite loop. Shouldn't there only be four objects he can iterate through?
Or is there even a basic implementation for this kind of pagination I haven't found yet?
Because my next step would be to get the current index out of model.getActiveStep(); and find which number it is.
I thought I share my solution (as far as it now is)
public class OverviewComponent extends Panel{
private static final long serialVersionUID = 1L;
private List<WizardStep> steps;
private WizardModel model;
public OverviewComponent(String id, WizardModel model) {
super(id);
this.model = model;
steps = fillList();
this.add(new ListView<WizardStep>("steps", steps) {
private static final long serialVersionUID = 1L;
/**
* Wrap a markup container around the item append a css attribute to every item
*/
#Override
protected void populateItem(ListItem<WizardStep> item) {
WebMarkupContainer stepOverviewItem;
item.add(stepOverviewItem = new WebMarkupContainer("stepOverviewItem"));
stepOverviewItem.add(new Label("index", Model.of(item.getIndex()+1)));
stepOverviewItem.add(new AttributeModifier("class", getCSSProperty(item.getModelObject())));
stepOverviewItem.setOutputMarkupId(true);
stepOverviewItem.setOutputMarkupPlaceholderTag(true);
}
});
}
public String getCSSProperty(WizardStep step) {
if (step.equals(model.getActiveStep())) {
return "active";
} else if (!step.isComplete()) {
return "pending";
} else if (step.isComplete()) {
return "completed";
}
return "";
}
public List<WizardStep> fillList() {
List<WizardStep> iSteps = new ArrayList<WizardStep>();
Iterator<IWizardStep> iterator = model.stepIterator();
while (iterator.hasNext()) {
iSteps.add((WizardStep)iterator.next());
}
return iSteps;
}
}