Issue: I've got a priority queue to process actions. When I instantiate the actions and then add them to the queue it works, however when I instantiate them directly as I add them to the queue it no longer retains the priority.
This works - Executes by priority
public static void main(String[] args) {
final Action<Void> low = new LowAction();
final Action<Void> med = new MedAction();
final Action<Integer> high = new HighAction();
final Action<Boolean> walk = new WalkAction();
final ActionScheduler scheduler = new ActionScheduler(1,10);
scheduler.queue(high);
scheduler.queue(walk);
scheduler.queue(low);
scheduler.queue(med);
}
This does not work - Executes in the order I called them
public static void main(String[] args) {
final ActionScheduler scheduler = new ActionScheduler(1,10);
scheduler.queue(new HighAction());
scheduler.queue(new WalkAction());
scheduler.queue(new LowAction());
scheduler.queue(new MedAction());
}
ActionScheduler class
public class ActionScheduler {
private ExecutorService priorityJobPoolExecutor;
private ExecutorService priorityJobScheduler
= Executors.newSingleThreadExecutor();
private PriorityBlockingQueue<Action<?>> priorityQueue;
private Future<?> result;
public ActionScheduler(Integer poolSize, Integer queueSize) {
priorityJobPoolExecutor = Executors.newFixedThreadPool(poolSize);
priorityQueue = new PriorityBlockingQueue<>(queueSize);
priorityJobScheduler.submit(() -> {
while (true) {
try {
result = priorityJobPoolExecutor.submit(priorityQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
});
}
public void schedule(Action<?> action) {
priorityQueue.offer(action);
}
public <T> Future<T> queue(Action<?> action) {
this.schedule(action);
return (Future<T>) result;
}
}
This also works correctly
public static void main(String[] args) {
final Action<Void> low = new LowAction();
final Action<Void> med = new MedAction();
final Action<Integer> high = new HighAction();
final Action<Boolean> walk = new WalkAction();
final ActionScheduler scheduler = new ActionScheduler(1,10);
scheduler.queue(new HighAction());
scheduler.queue(new WalkAction());
scheduler.queue(new LowAction());
scheduler.queue(new MedAction());
}
If anyone could offer any insight on why this is happening and how I can get it to execute by priority in both examples posted it would be greatly appreciated.
EDIT
Action Class
public abstract class Action<T> implements Callable<T>, Comparable<Action<?>> {
private final ActionContext context;
public Action(ActionContext context) {
this.context = context;
}
#Override
public int compareTo(Action action) {
if (action.getContext().getPriority() == this.getContext().getPriority()) {
return 0;
} else if (this.getContext().getPriority().ordinal() > action.getContext().getPriority().ordinal()) {
return -1;
} else {
return 1;
}
}
public ActionContext getContext() {
return context;
}
}
LowAction class
public class LowAction extends Action<Void> {
public LowAction() {
super(new ActionContext("low", Priority.LOW, true, false));
}
#Override
public Void call() throws Exception {
System.out.println("LOW");
return null;
}
}
There is one difference which I can see in the first approach you are binding your object with an actual type like final Action<**Void**> low = new LowAction(); on the other hand new LowAction() will be treat as a raw creation.
The internal working of the PriorityQueue is based on the Binary Heap.The elements of the priority queue are ordered according to the natural ordering, or by a comparator provided at construction time of the queue, depending on which constructor is used.
Related
Trying to use test driven development and ran into a NPE that I can't resolve and obviously because of that one of my tests fail.
In a method to fetch items I pass in a limit int and then instantiate a callback.
this is exactly where it falis the listener returns null I think.
[![enter image description here][1]][1]
Test class
package com.techyourchance.testdrivendevelopment.example11;
#RunWith(MockitoJUnitRunner.class)
public class FetchCartItemsUseCaseTest {
public static final int LIMIT = 10;
public static final int PRICE = 5;
public static final String ID = "id";
public static final String TITLE = "title";
public static final String DESCRIPTION = "description";
FetchCartItemsUseCase SUT;
#Mock
FetchCartItemsUseCase.Listener mListnerMock1;
FetchCartItemsUseCase.Listener mListnerMock2;
#Mock
GetCartItemsHttpEndpoint mGetCartItemsHttpEndpointMock;
#Captor
ArgumentCaptor<List<CartItem>> mAcListCartItem;
#Before
public void setup() throws Exception {
SUT = new FetchCartItemsUseCase(mGetCartItemsHttpEndpointMock);
success();
}
private void success() {
doAnswer(new Answer() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
Callback callback = (Callback) args[1];
callback.onGetCartItemsSucceeded(getCartItemSchemes());
return null;
}
}).when(mGetCartItemsHttpEndpointMock).getCartItems(anyInt(), any(Callback.class));
}
private List<CartItemSchema> getCartItemSchemes() {
List<CartItemSchema> schemas = new ArrayList<>();
schemas.add(new CartItemSchema(ID, TITLE, DESCRIPTION, PRICE));
return schemas;
}
#Test
public void fetchCartItems_correctLimitPassedToEndPoint() throws Exception {
ArgumentCaptor<Integer> acInt = ArgumentCaptor.forClass(Integer.class);
SUT.fetchCartItemsAndNotify(LIMIT);
verify(mGetCartItemsHttpEndpointMock).getCartItems(acInt.capture(), any(GetCartItemsHttpEndpoint.Callback.class));
assertThat(acInt.getValue(), is(LIMIT));
}
#Test
public void fetchCartItems_success_observersNotifiedWithCorrectData() throws Exception {
SUT.registerListener(mListnerMock1);
SUT.registerListener(mListnerMock2);
SUT.fetchCartItemsAndNotify(LIMIT);
verify(mListnerMock1).onCartItemsFetched(mAcListCartItem.capture());
verify(mListnerMock2).onCartItemsFetched(mAcListCartItem.capture());
List<List<CartItem>> captures = mAcListCartItem.getAllValues();
List<CartItem> capture1 = captures.get(0);
List<CartItem> capture2 = captures.get(1);
assertThat(capture1, is(getCartItems()));
assertThat(capture2, is(getCartItems()));
}
private List<CartItem> getCartItems() {
List<CartItem> cartItems = new ArrayList<>();
cartItems.add(new CartItem(ID, TITLE, DESCRIPTION, PRICE));
return cartItems;
}
//correct limit passed to the endpoint
//success - all observers notified with correct data
//success - unsubscribed observers not notified
//general error - observers notified of failure
//network error - observers notified of failure
}
public class FetchCartItemsUseCase {
private final List<Listener> mListeners = new ArrayList<>();
private final GetCartItemsHttpEndpoint mGetCartItemsHttpEndpoint;
public FetchCartItemsUseCase(GetCartItemsHttpEndpoint mGetCartItemsHttpEndpoint) {
this.mGetCartItemsHttpEndpoint = mGetCartItemsHttpEndpoint;
}
public void fetchCartItemsAndNotify(int limit) {
mGetCartItemsHttpEndpoint.getCartItems(limit, new GetCartItemsHttpEndpoint.Callback() {
#Override
public void onGetCartItemsSucceeded(List<CartItemSchema> cartItems) {
for(Listener listener : mListeners) {
listener.onCartItemsFetched(cartItemsFromSchemas(cartItems));
}
}
#Override
public void onGetCartItemsFailed(GetCartItemsHttpEndpoint.FailReason failReason) {
}
}) ;
}
private List<CartItem> cartItemsFromSchemas(List<CartItemSchema> cartItemSchemas) {
List<CartItem> cartItems = new ArrayList<>();
for(CartItemSchema schema : cartItemSchemas) {
cartItems.add(new CartItem(schema.getId(), schema.getTitle(),
schema.getDescription(), schema.getPrice()));
}
return cartItems;
}
public void registerListener(Listener listener) {
mListeners.add(listener);
}
public interface Listener {
Void onCartItemsFetched(List<CartItem> capture);
}
}
[1]: https://i.stack.imgur.com/r6Sea.png
really lost would appreciate any help
public class FetchReputationUseCaseSync {
private GetReputationHttpEndpointSync mGetReputationHttpEndpointSync;
public FetchReputationUseCaseSync(GetReputationHttpEndpointSync getReputationHttpEndpointSync) {
this.mGetReputationHttpEndpointSync = getReputationHttpEndpointSync;
}
public UseCaseResult fetchReputation() {
GetReputationHttpEndpointSync.EndpointResult result;
try{
result = mGetReputationHttpEndpointSync.getReputationSync();
}catch (NetworkErrorException e) {
e.printStackTrace();
return UseCaseResult.FAILURE;
}
switch(result.getStatus()) {
case SUCCESS:
return UseCaseResult.SUCCESS;
case GENERAL_ERROR:
return UseCaseResult.FAILURE;
default:
throw new RuntimeException("invalid status: " + result);
}
}
public enum UseCaseResult{
SUCCESS, FAILURE
}
}
I want to create an method that fires up every time a new message is added to the groupchat arraylist.
Pseudo code:
public void listenForChange(){
while(true){
if(new message is added to the groupchat){
System.out.println(print the last added message);
}
}
}
What I have tried, but doesn't work:
public class Groupe{
ArrayList<String> groupechat;
int liveChange;
public void listenForChange() {
while(true){
if (groupchat.size() > liveChange){
liveChange= gruppenchat.size();
System.out.println(gruppenchat.get(liveChange-1));
}
}
}
Test class:
public class testGruppen extends Thread {
Gruppe gruppe;
public TestGroup(){
gruppe= new Gruppe("Monday");
}
public void run() {
System.out.println("listen");
gruppe.listenForChange();
}
public static void main(String[] args) {
testGruppen test = new testGruppen();
test.start();
test.gruppe.write("1"); // write just adds a new String to groupchat
test.gruppe.write("2");
test.gruppe.write("3");
test.gruppe.write("4");
}
}
Output: 4 instead of 1\n 2\n 3\n 4\n
What about using decorator:
public static void main(String... args) {
List<Integer> group = new FireEventListDecorator<>(new ArrayList<>());
for (int i = 0; i < 3; i++)
group.add(i);
}
public static class FireEventListDecorator<E> extends AbstractList<E> {
private final List<E> delegate;
public FireEventListDecorator(List<E> delegate) {
this.delegate = delegate;
}
#Override
public void add(int index, E element) {
delegate.add(index, element);
fireEvent(element);
}
#Override
public E get(int index) {
return delegate.get(index);
}
#Override
public int size() {
return delegate.size();
}
private void fireEvent(E element) {
System.out.println("add: " + element);
}
}
To avoid a CPU wasteful while (true) loop with polling, use a call-back method via an observer/listener pattern. One way to do this is to give your class that holds the ArrayList a PropertyChangeSupport instance, allow it to accept listeners, and then in the method that changes the ArrayList, notify listeners.
e.g.,
public class Group {
// property listened to: ADD_TEXT
public static final String ADD_TEXT = "add text";
// the support object
private PropertyChangeSupport support = new PropertyChangeSupport(this);
private List<String> chatText = new ArrayList<>();
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(ADD_TEXT, listener);
}
public void addText(String text) {
String oldValue = "";
String newValue = text;
chatText.add(text + "\n");
// notify listeners
support.firePropertyChange(ADD_TEXT, oldValue, newValue);
}
}
And then it can be used like so:
public class TestGroupChat {
public static void main(String[] args) {
final Group group = new Group();
group.addPropertyChangeListener(new GroupListener());
final String[] texts = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
new Thread(() -> {
for (String text : texts) {
group.addText(text);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {}
}
}) .start();
}
private static class GroupListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
// call back method that is called any time the listened-to
// property has been changed
System.out.println("Notification: "+ evt.getNewValue());
}
}
}
You should take a look at LinkedBlockingQueue class.
This class is useful when you want to wake up a thread when a new element is added to a queue. In the example below, everytime you add a new message to the queue, the thread will print the message and wait for the next message.
public class Foo extends Thread {
LinkedBlockingQueue<String> messagesQueue;
public Foo(LinkedBlockingQueue<String> messagesQueue) {
this.messagesQueue = messagesQueue;
}
#Override
public voir run() {
while(true) {
String message = messagesQueue.take();
//The thread will sleep until there is a new message in the queue.
System.out.println(message);
}
}
}
I have a JavaFX table which is ultimately with data received on a network thread.
Using Platform.runLater() to update the view-model is simple, but it does not fit into our architecture.
The current architecture separates applications into "view" and "network/comms" parts.
View listens to models and updates corresponding components. No knowledge of network.
Network listens to network updates and writes data into models accordingly. No knowledge of JavaFX.
So I'm in a dilemma.
To be true to the architecture and "separation of concerns" - the network reader class should not be calling Platform.runLater()
To keep it simple and have the network reader class call Platform.runLater() - just works - no additional code.
I've attempted to illustrate this in code
The simple approach
Just call Platform.runLater() from network reader
public class SimpleUpdate extends Application {
private int clock;
public class Item {
private IntegerProperty x = new SimpleIntegerProperty(0);
public final IntegerProperty xProperty() {
return this.x;
}
public final int getX() {
return this.xProperty().get();
}
public final void setX(final int x) {
this.xProperty().set(x);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<Item> viewModel = FXCollections.observableArrayList();
TableView<Item> table = new TableView<Item>(viewModel);
TableColumn<Item, Integer> colX = new TableColumn<>();
colX.setCellValueFactory(new PropertyValueFactory<Item, Integer>("x"));
table.getColumns().add(colX);
primaryStage.setScene(new Scene(table));
primaryStage.show();
new Thread(() -> {
while (true) {
Platform.runLater(() -> { // update on JavaFX thread
if (clock % 2 == 0) {
viewModel.add(new Item());
viewModel.add(new Item());
} else {
viewModel.remove(1);
}
for (Item each : viewModel) {
each.setX(each.getX() + 1);
}
clock++;
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Network update").start();
}
public static void main(String[] args) {
launch(args);
}
}
Pure approach
Network reader thread writes into its own model
View listener listens to network model and syncs its own view model using JavaFX thread
A lot more complicated
Code
public class PureUpdate extends Application {
private int clock;
public class Item {
private IntegerProperty x = new SimpleIntegerProperty(0);
public final IntegerProperty xProperty() {
return this.x;
}
public final int getX() {
return this.xProperty().get();
}
public final void setX(final int x) {
this.xProperty().set(x);
}
}
public class ViewItem extends Item {
private Item original;
public ViewItem(Item original) {
super();
this.original = original;
sync();
}
public void sync() {
setX(original.getX());
}
public Item getOriginal() {
return original;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<ViewItem> viewModel = FXCollections.observableArrayList();
TableView<ViewItem> table = new TableView<ViewItem>(viewModel);
TableColumn<ViewItem, Integer> colX = new TableColumn<>();
colX.setCellValueFactory(new PropertyValueFactory<ViewItem, Integer>("x"));
table.getColumns().add(colX);
primaryStage.setScene(new Scene(table));
primaryStage.show();
ObservableList<Item> networkModel = FXCollections
.synchronizedObservableList(FXCollections.observableArrayList());
networkModel.addListener((Observable obs) -> {
Platform.runLater(() -> {
List<Item> alreadyKnown = new ArrayList<>();
for (Iterator<ViewItem> it = viewModel.iterator(); it.hasNext();) {
ViewItem each = it.next();
alreadyKnown.add(each.getOriginal());
if (networkModel.contains(each.getOriginal())) {
each.sync();
} else {
it.remove();
}
}
for (Item each : networkModel.toArray(new Item[0])) {
if (!alreadyKnown.contains(each)) {
viewModel.add(new ViewItem(each));
}
}
});
});
new Thread(() -> {
while (true) {
if (clock % 2 == 0) {
networkModel.add(new Item());
networkModel.add(new Item());
} else {
networkModel.remove(1);
}
for (Item each : networkModel) {
each.setX(each.getX() + 1);
}
clock++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Network update").start();
}
public static void main(String[] args) {
launch(args);
}
}
Question. Can I achieve the pure approach without writing additional code?
Have the processor post notifications to a Runnable. or a Consumer<T>, or define a custom #FunctionalInterface.
This will make testing easier to as you design out threading and the dependency on JavaFX or any other framework needing synchronization on a specific thread.
Example using consumer:
public class NetworkReader {
private final Consumer<? super Data> consumer;
public NetworkReader(Consumer<? super Data> consumer) {
this.consumer = Objects.requireNonNull(consumer);
}
public void readStuff() {
while (...) {
Data data = ...;
consumer.accept(data);
}
}
}
The NetworkReader would be constructed with e.g. new NetworkReader(d -> Platform.runLater(() -> updateModel(d)));
When you want to test you could pass do as follows:
NetworkReader reader = new NetworkReader(d -> this.actuals = d);
reader.readStuff();
assertEquals(expecteds, actuals);
A smart consumer could coelesc updates until it has actually been processed.
I'm getting stack trace for following code,
public interface SequenceDAO {
public Sequence getSequence(String sequenceId);
public int getNextValue(String sequenceId);
}
``````````````````````````````````````````
public class Sequence {
private int initial;
private String prefix;
private String suffix;
public Sequence(int initial, String prefix, String suffix) {
this.initial = initial;
this.prefix = prefix;
this.suffix = suffix;
}
````````````````````````````````````````````````
#Component("SequenceDAO")
public class SequenceDAOImpl implements SequenceDAO {
private Map<String, Sequence> sequences;
private Map<String, Integer> values;
public SequenceDAOImpl() {
sequences = new HashMap<>();
sequences.put("IT", new Sequence(30, "IT", "A"));
values = new HashMap<>();
values.put("IT", 10000);
}
#Override
public Sequence getSequence(String sequenceId) {
return sequences.get(sequenceId);
}
#Override
public int getNextValue(String sequenceId) {
int value = values.get(sequenceId);
values.put(sequenceId, value + 1);
return value;
}
}
``````````````````````````````````````````````
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("com.example");
SequenceDAO obj = context.getBean("SequenceDAO", SequenceDAO.class);
System.out.println(obj.getNextValue("IT"));
System.out.println(obj.getSequence("IT"));
}
`````````````````````````````````````````````````````````
Exception in thread "main" java.lang.IllegalStateException: org.springframework.context.annotation.AnnotationConfigApplicationContext#4c0bc4 has not been refreshed yet
at org.springframework.context.support.AbstractApplicationContext.assertBeanFactoryActive(AbstractApplicationContext.java:1041)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1059)
at com.example.SpringAnnotationsSequenceGeneratorWithDaoIntroductionApplication.main(SpringAnnotationsSequenceGeneratorWithDaoIntroductionApplication.java:14)
Iam new to spring and I am learning spring without annotations , so if anyone can tell me what happend wrong here
any help is appericiated.
Beat Regards
Your context init should like this:
ApplicationContext aContext = new AnnotationConfigApplicationContext(ConcertConfig.class);
#Configuration
#EnableAspectJAutoProxy
#ComponentScan
public class ConcertConfig {
}
public class keyClientHandler extends ChannelInboundHandlerAdapter{
public static ConcurrentHashMap<String, String> keyTable = new ConcurrentHashMap<>();
String information;
String hashMapKey;
String hashMapValue;
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// do something
keyTable.put(hashMapKey, hashMapValue);
System.out.println(keyTable.size()); //size = 268
}
public static ConcurrentHashMap<String,String> getKeyTable() {
return keyTable;
}
Another class use:
public static void main(String[] args) {
ConcurrentHashMap<String, String> map = keyClientHandler.getKeyTable();
System.out.println(map.size()); //size=0
}
When i try to use stuffed concurrentMap on another class or in the main method, it returns empty.
How can i use Concurentmap from another classes?
How we interpreted your problem?
public static ConcurrentHashMap<String, String> keyTable = new ConcurrentHashMap<>();
static concurrentHashMap has been defined in class KeyClientHandler. You intended to retrieve the map object and print the size of the map from main method of another class. Now as you said, your program runs and it prints 0 as the output. This means you are alright in terms of accessing the map. You should have got compilation errors, if your concurrentHashMap was not accessible from the said main method of another class.
What can be a possible way to demonstrate that this better?
I think the following improvements are required. Firstly, you don't need to use static map or static methods here. We can demonstrate this without static'ness as well. Try running this example which is a slight modification of your code.
import java.util.concurrent.ConcurrentHashMap;
class ChannelHandlerContext {
// some class
}
class KeyClientHandler{
public ConcurrentHashMap<String, String> keyTable = new ConcurrentHashMap<>();
String information;
String hashMapKey;
String hashMapValue;
KeyClientHandler() {
}
public void setKeyValue(String key, String value){
hashMapKey = key;
hashMapValue = value;
}
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// do something
keyTable.put(hashMapKey, hashMapValue);
System.out.println(keyTable.size()); //size = 268
}
public ConcurrentHashMap<String,String> getKeyTable() {
return keyTable;
}
}
public class TestConcurrentHashMap {
public static void main(String[] args) {
KeyClientHandler keyClientHandler = new KeyClientHandler();
keyClientHandler.setKeyValue("apples", "fruit");
ConcurrentHashMap<String, String> map = keyClientHandler.getKeyTable();
try {
keyClientHandler.channelRead(null, null); // not the best thing
System.out.println(map.size()); //size=1
} catch (Exception e) {
e.printStackTrace();
}
}
}
Output:
1
My project is socket programming. I use Netty Framework. I send TCP client and received message send other client.
Server :
public class KeyClient {
static final String HOST = System.getProperty("host", "...");
static final int PORT = Integer.parseInt(System.getProperty("port", "..."));
public static void keyClientStart() throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new keyClientInitializer());
ChannelFuture future = bootstrap.connect(HOST, PORT).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
KeyClientInitializer :
public class keyClientInitializer extends ChannelInitializer<SocketChannel> {
#Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LoggingHandler(LogLevel.INFO));
pipeline.addLast(new FixedLengthFrameDecoder(32));
pipeline.addLast(new keyClientHandler());
}
}
KeyClientHandler :
public class keyClientHandler extends ChannelInboundHandlerAdapter{
public static ConcurrentHashMap<String, String> keyTable = new ConcurrentHashMap<>();
String information;
String hashMapKey;
String hashMapValue;
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buffer = (ByteBuf) msg;
byte[] receivedKey = byteBufToByteArray(buffer);
information = DatatypeConverter.printHexBinary(receivedKey);
// do something
// ...
keyTable.put(hashMapKey, hashMapValue); //map has elements
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
}
public static ConcurrentHashMap<String,String> getKeyTable() {
return keyTable;
}
private byte[] byteBufToByteArray(ByteBuf buffer) {
byte[] receivedKey;
int offset;
int length = buffer.readableBytes();
if (buffer.hasArray()) {
receivedKey = buffer.array();
offset = buffer.arrayOffset();
} else {
receivedKey = new byte[length];
buffer.getBytes(buffer.readerIndex(), receivedKey);
offset = 0;
}
return receivedKey;
}
}
Test class:
public class AppMain {
public static void main(String[] args) throws InterruptedException {
KeyClient.keyClientStart();
ConcurrentHashMap<String, String> map = keyClientHandler.getKeyTable(); // is empty
}
When i add 'System.out.println(keyTable);' in keyClientHandler, i see map values.
Output
On my case it's OK to hold the CHM object on other class, you can check:
Is the System.out.println(keyTable.size()); called after channelRead(...) ? you print the key on which class? if the next channel handler, should you call ctx.fireChannelRead(msg); ?
Other way you can print the CHM hashCode(), if they are the same, that means same object.