I'm trying to marshal and unmarshal Map to/from value pairs. I can marshal the object successfully, however, I cannot unmarshal it from the xml. The unmarshal result is the key exist in the Map, however, its value is null.
Here's the model I want to marshal and unmarshal:
import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement(name="TestModel")
#XmlAccessorType(XmlAccessType.FIELD)
public class TestModel {
#XmlElement(name="Name")
private String name;
#XmlJavaTypeAdapter(MapAdapter.class)
private Map<String, String> metadata;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}
}
I create a Map Adapter for marshal and unmarshal like below:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.namespace.QName;
import org.w3c.dom.Element;
public class MapAdapter extends XmlAdapter<MapWrapper, Map<String, String>> {
#Override
public MapWrapper marshal(Map<String, String> m) throws Exception {
MapWrapper wrapper = new MapWrapper();
List<JAXBElement<String>> elements = new ArrayList<JAXBElement<String>>();
if (m != null && !m.isEmpty()) {
for (Entry<String, String> property : m.entrySet()) {
elements.add(new JAXBElement<String>(new QName(property.getKey()),
String.class, property.getValue().toString()));
}
}
wrapper.elements = elements;
return wrapper;
}
#Override
public Map<String, String> unmarshal(MapWrapper v) throws Exception {
Map<String, String> map = new HashMap<String, String>();
if (v != null && v.elements != null && !v.elements.isEmpty()) {
for (Object object : v.elements) {
Element element = (Element) object;
map.put(element.getNodeName(), element.getNodeValue());
}
}
return map;
}
}
class MapWrapper {
#XmlAnyElement(lax=true)
protected List<JAXBElement<String>> elements;
}
And the below class can test the marshal and unmarshal of the above model:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class TestEntry {
public static void main(String[] args) throws JAXBException {
testMarshal();
testUnmarshal();
}
public static void testMarshal() throws JAXBException {
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("category", "test");
metadata.put("creation", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
TestModel model = new TestModel();
model.setMetadata(metadata);
model.setName("TESTMODEL");
marshal(model, System.out);
}
public static void testUnmarshal() throws JAXBException {
String model = "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>"
+ "<TestModel>"
+ "<Name>TESTMODEL</Name>"
+ "<metadata>"
+ "<category>test</category>"
+ "<creation>2015-09-29</creation>"
+ "</metadata>"
+ "</TestModel>";
TestModel result = unmarshal(new ByteArrayInputStream(model.getBytes()), TestModel.class);
System.out.println("name=" + result.getName());
for (String key : result.getMetadata().keySet()) {
System.out.println(key + ", " + result.getMetadata().get(key));
}
}
public static <T> void marshal(T cls, OutputStream os) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(cls.getClass());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(cls, os);
}
#SuppressWarnings("unchecked")
public static <T> T unmarshal(InputStream is, Class<T> cls)
throws JAXBException {
JAXBContext context = JAXBContext.newInstance(cls);
Unmarshaller unmarshaller = context.createUnmarshaller();
return (T) unmarshaller.unmarshal(is);
}
}
It seems that, the map only contains the key with 'null' value when invoke 'public Map unmarshal(MapWrapper v) throws Exception'.
And I also find a similar question about this issue "JAXB: how to marshall map into <key>value</key>", however, it didn't solve the unmarshal issue?
A very small correction:
map.put(element.getNodeName(), element.getTextContent());
Related
We need an example on how to test ReactiveKafkaConsumerTemplate and ReactiveKafkaProducerTemplate with an embedded-kafka-broker. Thanks.
CORRECT CODE IS HERE AFTR DISCUSSION
You can have your custom de-serializer accordingly to use custom ReactiveKafkaConsumerTemplate
Custom Serialiser:
import org.apache.kafka.common.serialization.Serializer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class EmployeeSerializer implements Serializer<Employee> {
#Override
public byte[] serialize(String topic, Employee data) {
byte[] rb = null;
ObjectMapper mapper = new ObjectMapper();
try {
rb = mapper.writeValueAsString(data).getBytes();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return rb;
}
}
Use it part of embedded-kfka-reactive test:
import java.util.Map;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.connect.json.JsonSerializer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.kafka.core.reactive.ReactiveKafkaProducerTemplate;
import org.springframework.kafka.support.converter.MessagingMessageConverter;
import org.springframework.kafka.test.condition.EmbeddedKafkaCondition;
import org.springframework.kafka.test.context.EmbeddedKafka;
import org.springframework.kafka.test.utils.KafkaTestUtils;
import reactor.kafka.sender.SenderOptions;
import reactor.kafka.sender.SenderRecord;
import reactor.test.StepVerifier;
#EmbeddedKafka(topics = EmbeddedKafkareactiveTest.REACTIVE_INT_KEY_TOPIC,
brokerProperties = { "transaction.state.log.replication.factor=1", "transaction.state.log.min.isr=1" })
public class EmbeddedKafkareactiveTest {
public static final String REACTIVE_INT_KEY_TOPIC = "reactive_int_key_topic";
private static final Integer DEFAULT_KEY = 1;
private static final String DEFAULT_VERIFY_TIMEOUT = null;
private ReactiveKafkaProducerTemplate<Integer, Employee> reactiveKafkaProducerTemplate;
#BeforeEach
public void setUp() {
reactiveKafkaProducerTemplate = new ReactiveKafkaProducerTemplate<>(setupSenderOptionsWithDefaultTopic(),
new MessagingMessageConverter());
}
private SenderOptions<Integer, Employee> setupSenderOptionsWithDefaultTopic() {
Map<String, Object> senderProps = KafkaTestUtils
.producerProps(EmbeddedKafkaCondition.getBroker().getBrokersAsString());
SenderOptions<Integer, Employee> senderOptions = SenderOptions.create(senderProps);
senderOptions = senderOptions.producerProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "reactive.transaction")
.producerProperty(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true)
.producerProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class.getName())
;
return senderOptions;
}
#Test
public void test_When_Publish() {
Employee employee = new Employee();
ProducerRecord<Integer, Employee> producerRecord = new ProducerRecord<Integer, Employee>(REACTIVE_INT_KEY_TOPIC, DEFAULT_KEY, employee);
StepVerifier.create(reactiveKafkaProducerTemplate.send(producerRecord)
.then())
.expectComplete()
.verify();
}
#AfterEach
public void tearDown() {
reactiveKafkaProducerTemplate.close();
}
}
The tests in the framework use an embedded kafka broker.
https://github.com/spring-projects/spring-kafka/tree/main/spring-kafka/src/test/java/org/springframework/kafka/core/reactive
#EmbeddedKafka(topics = ReactiveKafkaProducerTemplateIntegrationTests.REACTIVE_INT_KEY_TOPIC, partitions = 2)
public class ReactiveKafkaProducerTemplateIntegrationTests {
...
added correct serialised with a non-transactional producer. please see the code on top of this page for the answer.
I have a simple MyLibraryApplication which is having code to invoke POST(TransactionControllerImpl.issueBookToMember) and PATCH(TransactionControllerImpl.returnBookTransaction) methods. I have referred some links on net and tried my best to write code to invoke PATCH method. The code can be found in TransactionControllerTest(testBookReturnUsingRestTemplate and testBookReturnUsingMockMvc methods). The code for invoking POST is working fine but the code for invoking PATCH is not working. Control never reaches returnBookTransaction inside TransactionControllerImpl.
Error: Invalid PATCH method.
I am looking for code snippet for TransactionControllerTest.testBookReturnUsingRestTemplate and testBookReturnUsingMockMvc methods. Can someone help me in getting this code into proper shape?
package com.mycompany.techtrial;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import com.mycompany.techtrial.Transaction;
public interface TransactionController {
public ResponseEntity<Transaction> issueBookToMember(#RequestBody Map<String, String> params);
public ResponseEntity<Transaction> returnBookTransaction(#PathVariable(name="transaction-id") Long transactionId);
}
/**
*
*/
package com.mycompany.techtrial;
import java.time.LocalDateTime;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class TransactionControllerImpl implements TransactionController{
/*
* PLEASE DO NOT CHANGE SIGNATURE OR METHOD TYPE OF END POINTS
* Example Post Request : { "book":"Java8 Primer","member":"Test 1" }
*/
#PostMapping(path = "/api/transaction")
public ResponseEntity<Transaction> issueBookToMember(#RequestBody Map<String, String> params){
String book = params.get("book");
String member = params.get("member");
Transaction transaction = new Transaction();
transaction.setId(1L);
transaction.setBook(book);
transaction.setMember(member);
transaction.setDateOfIssue(LocalDateTime.now());
transaction.setDateOfReturn(Transaction.getDefaultReturnDate());
return ResponseEntity.ok().body(transaction);
}
/*
* PLEASE DO NOT CHANGE SIGNATURE OR METHOD TYPE OF END POINTS
*/
#PatchMapping(path= "/api/transaction/{transaction-id}/return")
public ResponseEntity<Transaction> returnBookTransaction(#PathVariable(name="transaction-id") Long transactionId){
String book = "Java8 Primer";
String member = "Test 1";
Transaction transaction = new Transaction();
transaction.setId(1L);
transaction.setBook(book);
transaction.setMember(member);
transaction.setDateOfIssue(LocalDateTime.now().minusDays(10));
transaction.setDateOfReturn(LocalDateTime.now());
return ResponseEntity.ok().body(transaction);
}
}
package com.mycompany.techtrial;
import java.util.HashMap;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class TransactionControllerTest {
MockMvc mockMvc;
#Mock
private TransactionController transactionController;
#Autowired
private TestRestTemplate template;
#Before
public void setup() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(transactionController).build();
}
#Test
public void testBookIssue() throws Exception {
HttpEntity<Object> transaction = getHttpEntity(
"{\"book\": \"Java8 Primer\", \"member\": \"Test 1\" }");
ResponseEntity<Transaction> response = template.postForEntity(
"/api/transaction", transaction, Transaction.class);
Assert.assertEquals("Java8 Primer", response.getBody().getBook());
Assert.assertEquals("Test 1", response.getBody().getMember());
Assert.assertEquals(200,response.getStatusCode().value());
}
#Test
public void testBookReturnUsingRestTemplate() throws Exception {
Long transactionId = new Long(1);
HashMap<String,Long> uriVariables = new HashMap<String,Long>();
uriVariables.put("transaction-id", transactionId);
Transaction transaction = template.patchForObject(
"/api/transaction/{transaction-id}/return",null, Transaction.class, uriVariables);
Assert.assertEquals(new Long(1), transaction.getId());
//Assert.assertEquals(200,response.getStatusCode().value());
}
#Test
public void testBookReturnUsingMockMvc() throws Exception {
Long transactionId = new Long(1);
HashMap<String,Long> uriVariables = new HashMap<String,Long>();
uriVariables.put("transaction-id", transactionId);
ResultActions obj = mockMvc.perform( MockMvcRequestBuilders
.patch("/api/transaction/{transaction-id}/return",transactionId)
.content("")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON));
System.out.println(obj.getClass());
HttpStatus status = obj.andReturn().getModelAndView().getStatus();
boolean success = status.is2xxSuccessful();
System.out.println("success="+success);
Assert.assertEquals(new Long(1), transactionId);
//Assert.assertEquals(200,response.getStatusCode().value());
}
private HttpEntity<Object> getHttpEntity(Object body) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new HttpEntity<Object>(body, headers);
}
}
package com.mycompany.techtrial;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class Transaction implements Serializable {
private static final long serialVersionUID = 8951221480021840448L;
private static final LocalDateTime defaultReturnDate = LocalDateTime.of(LocalDate.of(2299, 12, 31), LocalTime.of(12, 0, 0));
Long id;
private String book;
private String member;
public String getBook() {
return book;
}
public void setBook(String book) {
this.book = book;
}
public String getMember() {
return member;
}
public void setMember(String member) {
this.member = member;
}
//Date and time of issuance of this book
LocalDateTime dateOfIssue;
//Date and time of return of this book
LocalDateTime dateOfReturn;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public LocalDateTime getDateOfIssue() {
return dateOfIssue;
}
public void setDateOfIssue(LocalDateTime dateOfIssue) {
this.dateOfIssue = dateOfIssue;
}
public LocalDateTime getDateOfReturn() {
return dateOfReturn;
}
public void setDateOfReturn(LocalDateTime dateOfReturn) {
this.dateOfReturn = dateOfReturn;
}
#Override
public String toString() {
return "Transaction [id=" + id + ", book=" + book + ", member=" + member + ", dateOfIssue=" + dateOfIssue + ", dateOfReturn=" + dateOfReturn + "]";
}
//#PrePersist
void preInsert() {
if (this.dateOfReturn == null)
this.dateOfReturn = defaultReturnDate;
}
public static LocalDateTime getDefaultReturnDate() {
return defaultReturnDate;
}
}
package com.mycompany.techtrial;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class MyLibraryApplication {
public static void main(String[] args) {
SpringApplication.run(MyLibraryApplication.class, args);
}
}
It seems to be a known issue with the RestTemplate default Http client.
RestTemplate bug
A workaround for this would be to use the apache httpcomponents httpclient library in the RestTemplateBuilder.setRequestFactory and pass that in the constructor to TestRestTemplate
After that you can use the exchange method on the TestRestTemplate class and do a PATCH request.
Sample code to create TestRestTemplate:
Supplier<ClientHttpRequestFactory> supplier = () -> {
return new HttpComponentsClientHttpRequestFactory();
};
restTemplateBuilder.requestFactory(supplier);
TestRestTemplate testRestTemplate = new TestRestTemplate(restTemplateBuilder);
testRestTemplate.exchange("/api/transaction/{transaction-id}/return",HttpMethod.PATCH,null,Transaction.class,uriVariables);
I am making an implementation of a Notary, and I am encountering an issue caused by net.corda.core.SerialFilter. The simple test in the UniquenessProvider demonstrates the deserialization error despite my Whitelist implementation
My class SerializableCommitContainer
import com.sun.istack.Nullable;
import net.corda.core.contracts.TimeWindow;
import net.corda.core.contracts.StateRef;
import net.corda.core.crypto.SecureHash;
import net.corda.core.flows.NotarisationRequestSignature;
import net.corda.core.identity.Party;
import java.io.Serializable;
import java.util.List;
public class SerializableCommitContainer implements Serializable {
private byte[] inputStates;
private byte[] txID;
private byte[] callerIdentity;
private byte[] requestSignature;
private byte[] timewindow;
public SerializableCommitContainer(byte[] inputStates, byte[] txID, byte[] callerIdentity,
byte[] requestSignature, #Nullable byte[] timewindow) {
this.inputStates = inputStates;
this.txID = txID;
this.callerIdentity = callerIdentity;
this.requestSignature = requestSignature;
this.timewindow = timewindow;
}
public SerializableCommitContainer(List<StateRef> inputStates, SecureHash txID, Party callerIdentity,
NotarisationRequestSignature requestSignature,
#Nullable TimeWindow timeWindow){
this.inputStates = CustomUniquenessProvider.serialize((Serializable)inputStates);
this.txID = CustomUniquenessProvider.serialize(txID);
this.callerIdentity = CustomUniquenessProvider.serialize(callerIdentity);
this.requestSignature = CustomUniquenessProvider.serialize(requestSignature);
this.timewindow = CustomUniquenessProvider.serialize(timeWindow);
}
public byte[] getInputStates() {
return inputStates;
}
}
I have implemented net.corda.core.serialization.SerializationWhitelist
class Whitelist : SerializationWhitelist {
override val whitelist:List<Class<*>> =
listOf(SerializableCommitContainer::class.java)
}
My Notary implementation
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.node.ServiceHub
import net.corda.node.services.transactions.NonValidatingNotaryFlow
import java.security.PublicKey
class CustomNotary(override val services: ServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val uniquenessProvider: CustomUniquenessProvider = CustomUniquenessProvider()
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = NonValidatingNotaryFlow(otherPartySession, this)
override fun start() {
}
override fun stop() {
}
}
My UniquenessProvider
import net.corda.core.contracts.StateRef;
import net.corda.core.contracts.TimeWindow;
import net.corda.core.crypto.SecureHash;
import net.corda.core.flows.NotarisationRequestSignature;
import net.corda.core.identity.Party;
import net.corda.core.internal.notary.UniquenessProvider;
import java.io.Serializable;
import java.util.List;
public class CustomUniquenessProvider implements UniquenessProvider {
private static final Serializable notaryIdentity = "notaryIdentity";
public CustomUniquenessProvider(){
}
#Override
public void commit(List<StateRef> states, SecureHash txId, Party callerIdentity, NotarisationRequestSignature requestSignature, TimeWindow timeWindow) {
SerializableCommitContainer nextCommit = new SerializableCommitContainer(states, txId, callerIdentity, requestSignature, timeWindow);
byte[] serial = serialize(nextCommit);
Serializable deserial = deserialize(serial);
if(deserial == null){
System.out.println("CC DESERIALIZATION FAILED\n");
}
}
public static byte[] serialize(final Serializable serializable)
{
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ObjectOutputStream stream;
try {
stream = new ObjectOutputStream(bytes);
stream.writeObject(serializable);
stream.close();
byte[] ba = bytes.toByteArray();
bytes.close();
return ba;
} catch (IOException e) {
return null;
}
}
public static Serializable deserialize(final byte[] byteArray){
try {
ois = new ObjectInputStream(bais);
Serializable sp = (Serializable)stream.readObject();
stream.close();
return sp;
} catch (Throwable e) {
return null;
}
}
As of Corda 3.2, Java serialisation is disabled on Corda nodes. You need to use Corda's built-in serialisation engine instead.
You should start by basing your notary on an example from the Corda codebase, such as PersistentUniquenessProvider.
This is regarding JFX Table.
I read above link and that was good for my work.
My requirement is - Suppose i click on a table column header and data is sorted in ascending or descending order. I want to persist that sorted data on my table even after restarting my application. Can someone please help me on this issue ? How can i remember the column header name and ascending / descending order and sort it at the time of initialization ?
Sorting order of jfx table clumn
You could simply store the necessary to restore the sorting in a file in your user directory. The following code does this by storing a int for the number of columns to sort by followed by an int and a TableColumn.SortType for each column. The int denotes the initial column index and the SortType specifies, whether the sorting is ascending or descending.
Example
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
public class SaveAndRestore extends Application {
private static final Path CONFIG_FILE;
static {
CONFIG_FILE = Paths.get(System.getProperty("user.home"), "myapp", "config.ser");
Path dir = CONFIG_FILE.getParent();
if (!Files.exists(dir)) {
try {
Files.createDirectory(dir);
} catch (IOException ex) {
throw new IllegalStateException("Could not create settings directory.", ex);
}
}
}
private static final InputStream getConfigReadStream() throws IOException {
return Files.exists(CONFIG_FILE) ? Files.newInputStream(CONFIG_FILE) : null;
}
private static final OutputStream getConfigWriteStream() throws IOException {
return Files.newOutputStream(CONFIG_FILE);
}
#Override
public void stop() throws Exception {
try (ObjectOutputStream oos = new ObjectOutputStream(getConfigWriteStream())) {
oos.writeInt(tableView.getSortOrder().size());
for (TableColumn tc : tableView.getSortOrder()) {
oos.writeInt((Integer) tc.getUserData());
oos.writeObject(tc.getSortType());
}
}
}
public static class Item {
private final IntegerProperty number = new SimpleIntegerProperty();
private final StringProperty string = new SimpleStringProperty();
public Item(int number, String string) {
this.number.set(number);
this.string.set(string);
}
public final int getNumber() {
return this.number.get();
}
public final void setNumber(int value) {
this.number.set(value);
}
public final IntegerProperty numberProperty() {
return this.number;
}
public final String getString() {
return this.string.get();
}
public final void setString(String value) {
this.string.set(value);
}
public final StringProperty stringProperty() {
return this.string;
}
}
private static <T> TableColumn<Item, T> createColumn(String property) {
TableColumn<Item, T> column = new TableColumn<>(property);
column.setCellValueFactory(new PropertyValueFactory(property));
return column;
}
private TableView<Item> tableView;
#Override
public void start(Stage primaryStage) throws IOException, ClassNotFoundException {
tableView = new TableView<>(FXCollections.observableArrayList(
new Item(10, "Hello World"),
new Item(5, "Zyzz"),
new Item(20, "Aaron")
));
tableView.getColumns().addAll(createColumn("number"));
tableView.getColumns().addAll(createColumn("string"));
for (int i = 0, size = tableView.getColumns().size(); i < size; i++) {
tableView.getColumns().get(i).setUserData(i);
}
// restore state from config
InputStream is = getConfigReadStream();
if (is != null) {
try (ObjectInputStream ois = new ObjectInputStream(is)) {
for (int num = ois.readInt(); num > 0; num--) {
TableColumn<Item, ?> column = tableView.getColumns().get(ois.readInt());
column.setSortType((TableColumn.SortType) ois.readObject());
tableView.getSortOrder().add(column);
}
}
}
Scene scene = new Scene(tableView);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I'm trying to use this to select a value from a Custom Combo Box:
import java.util.List;
import javafx.application.Application;
import static javafx.application.Application.launch;
import static javafx.application.Application.launch;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;
public class MainApp extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage)
{
final ComboBox<ListGroupsObj> listGroups = new ComboBox();
listGroups.setButtonCell(new GroupListCell());
listGroups.setCellFactory(new Callback<ListView<ListGroupsObj>, ListCell<ListGroupsObj>>()
{
#Override
public ListCell<ListGroupsObj> call(ListView<ListGroupsObj> p)
{
return new GroupListCell();
}
});
listGroups.setEditable(true);
listGroups.setConverter..............
// Insert Some data
ListGroupsObj ob = ListGroupsObj.newInstance().groupId(12).groupName("Test");
listGroups.getItems().addAll(ob);
ListGroupsObj osb = ListGroupsObj.newInstance().groupId(13).groupName("Test2");
listGroups.getItems().addAll(osb);
listGroups.setValue(ob);
// Display the selected Group
listGroups.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<ListGroupsObj>()
{
#Override
public void changed(ObservableValue<? extends ListGroupsObj> arg0, ListGroupsObj arg1, ListGroupsObj arg2)
{
if (arg2 != null)
{
System.out.println("Selected Group: " + arg1.getGroupId() + " - " + arg2.getGroupName());
}
}
});
final StackPane layout = new StackPane();
layout.getChildren().add(listGroups);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 15;");
stage.setScene(new Scene(layout));
stage.show();
}
class GroupListCell extends ListCell<ListGroupsObj>
{
#Override
protected void updateItem(ListGroupsObj item, boolean empty)
{
super.updateItem(item, empty);
if (item != null)
{
setText(item.getGroupId() + " - " + item.getGroupName());
}
}
}
private List<ListGroupsObj> listGroups;
public static class ListGroupsObj
{
private int groupId;
private String groupName;
public static ListGroupsObj newInstance()
{
return new ListGroupsObj();
}
public ListGroupsObj()
{
}
public ListGroupsObj groupId(int groupId)
{
this.groupId = groupId;
return this;
}
public ListGroupsObj groupName(String groupName)
{
this.groupName = groupName;
return this;
}
public int getGroupId()
{
return groupId;
}
public String getGroupName()
{
return groupName;
}
#Override
public String toString()
{
return groupId + " - " + groupName;
}
}
public class GroupConverter extends StringConverter<ListGroupsObj>
{
#Override
public String toString(ListGroupsObj obj)
{
return obj.getGroupId() + " - " + obj.getGroupName();
}
#Override
public ListGroupsObj fromString(String obj)
{
//TODO when you type for example "45 - NextGroup" you want to take only tyhe number"
return ListGroupsObj.newInstance().groupName(obj);
}
}
}
I get this error when I click outside of the comboBox:
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: java.lang.String cannot be cast to com.selectmenuexample.MainApp$ListGroupsObj
I found that this can be done using convertor but I'm now aware how to use it. Can you help with this implementation?
Here is what is wrong:
You called your ComboBox listGroups and your List of Items listGroups. So in your start code, you were hiding that variable. So I removed that useless variable since you can manipulate Items directly in the ComboBox.
You had basically three methods/variable doing the exact same things. Converting your Object into a String with a "-" between them. So I removed the GroupConverter and the custom cellFactory. You don't need them because you already have your "toString()" method in your ListGroupsObj which is doing the job.
Then you misunderstood how the ComboBox is working. If it's editable, the ComboBox will allow something to be typed inside the TextField. That's where the StringConverter comes. It will allow you to make the conversion between a String and your ListGroupsObj and the way around.
In order to go from a ListGroupsObj to a String, simply call the "toString()" method on your object.
But in the way around, you should either create a new ListGroupsObj, or verify that what's inside the ComboBox is not already one item of yours. For example, if you select an Item in the comboBox, the fromString() will be called. But you don't want to create a new ListGroupsObj, you just want to isolate the ListGroupsObj inside your items List and returns it.
Now, you have the guaranty that a call to getValue() on your ComboBox will always return an ListGroupsObj object since you have provided a custom and valid StringConverter.
Here is a simplified and working version of your code :
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class MainApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
final ComboBox<ListGroupsObj> comboBox = new ComboBox();
comboBox.setEditable(true);
comboBox.setConverter(new StringConverter<ListGroupsObj>() {
#Override
public String toString(ListGroupsObj obj) {
return obj.toString();
}
#Override
public ListGroupsObj fromString(String obj) {
//Here we try to identify if the given String actually represents one item of our list
for(ListGroupsObj tempObj:comboBox.getItems()){
if(tempObj.toString().equals(obj)){
return tempObj;
}
}
//If not we just create a new one
return ListGroupsObj.newInstance().groupName(obj);
}
});
// Insert Some data
ListGroupsObj ob = ListGroupsObj.newInstance().groupId(12).groupName("Test");
comboBox.getItems().addAll(ob);
ListGroupsObj osb = ListGroupsObj.newInstance().groupId(13).groupName("Test2");
comboBox.getItems().addAll(osb);
comboBox.setValue(ob);
final StackPane layout = new StackPane();
layout.getChildren().add(comboBox);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 15;");
stage.setScene(new Scene(layout));
stage.show();
}
public static class ListGroupsObj {
private int groupId;
private String groupName;
public static ListGroupsObj newInstance() {
return new ListGroupsObj();
}
public ListGroupsObj() {
}
public ListGroupsObj groupId(int groupId) {
this.groupId = groupId;
return this;
}
public ListGroupsObj groupName(String groupName) {
this.groupName = groupName;
return this;
}
public int getGroupId() {
return groupId;
}
public String getGroupName() {
return groupName;
}
#Override
public String toString() {
return groupId + " - " + groupName;
}
}
}
PS: The issue was already raised in the official JavaFX issue Tracker, I'll leave the link here since there is another example in the ticket (login required) : https://javafx-jira.kenai.com/browse/RT-29118