I am working on a JavaFX application which facilitates all kind of sport related training sessions. Each session consists of multiple exercises, whereas each exercise is repeated multiple times in a few sets. I created some test data and marshalled it. As it turned out, some fields of the Exercise class objects were written but not all of them. By adding the #XmlElement(name="someTagName") tag to each getter of each field I managed that all fields are marshalled and the xml file looks like expected. However, when I unmarshall the xml file, only those fields, which were written without the #XmlElement tag are read and most of the fields only have the default value from the constructor. What am I missing in order to unmarshall all fields?
Here is the class that I want to marshall/unmarshall
package domain;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Exercise extends SelectableDomainObj {
protected StringProperty name;
protected StringProperty mediaFilePath;
protected IntegerProperty repsPerSet;
protected IntegerProperty numOfSets;
protected IntegerProperty breakBetweenSetsInSecs;
protected IntegerProperty displayTimeInSecs;
protected DoubleProperty startSpeed;
protected DoubleProperty endSpeed;
protected BooleanProperty withMetronom;
protected BooleanProperty showIntro;
/**
* Set some reasonable default values from lifting domain
*/
public Exercise() {
mediaFilePath = new SimpleStringProperty();
name = new SimpleStringProperty();
numOfSets = new SimpleIntegerProperty(3);
repsPerSet = new SimpleIntegerProperty(8);
breakBetweenSetsInSecs = new SimpleIntegerProperty(60);
displayTimeInSecs = new SimpleIntegerProperty(-1);
startSpeed = new SimpleDoubleProperty(1.0);
endSpeed = new SimpleDoubleProperty(1.0);
withMetronom = new SimpleBooleanProperty(false);
showIntro = new SimpleBooleanProperty(false);
}
#XmlElement(name="name")
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
public StringProperty nameProperty() {
return name;
}
#XmlElement(name="mediaFilePath")
public String getMediaFilePath() {
return mediaFilePath.get();
}
public void setMediaFilePath(String mediaFilePath) {
this.mediaFilePath.set(mediaFilePath);
}
public StringProperty mediaFilePathProperty() {
return mediaFilePath;
}
#XmlElement(name="repsPerSet")
public Integer getRepsPerSet() {
return repsPerSet.get();
}
public void setRepsPerSet(int repsPerSet) {
this.repsPerSet.set(repsPerSet);
}
public IntegerProperty repsPerSetProperty() {
return repsPerSet;
}
#XmlElement(name="numOfSets")
public int getNumOfSets() {
return numOfSets.get();
}
public void setNumOfSets(int numOfSets) {
this.numOfSets.set(numOfSets);
}
public IntegerProperty numOfSetsProperty() {
return numOfSets;
}
#XmlElement(name="breakBetweenSetsInSec")
public Integer getBreakBetweenSetsInSecs() {
return breakBetweenSetsInSecs.get();
}
public void setBreakBetweenSetsInSecs(int breakBetweenSetsInSecs) {
this.breakBetweenSetsInSecs.set(breakBetweenSetsInSecs);
}
public IntegerProperty displayTimeInSecsProperty() {
return displayTimeInSecs;
}
#XmlElement(name="displayTimeInSecs")
public Integer getDisplayTimeInSecs() {
return displayTimeInSecs.get();
}
public void setDisplayTimeInSecs(int displayTime) {
this.displayTimeInSecs.set(displayTime);
}
public IntegerProperty breakBetweenSetsInSecsProperty() {
return breakBetweenSetsInSecs;
}
#XmlElement(name="showIntro")
public Boolean isShowIntro() {
return showIntro.getValue();
}
public void setShowIntro(boolean showIntro) {
this.showIntro.set(showIntro);
}
public BooleanProperty showIntroProperty() {
return showIntro;
}
#XmlElement(name="withMetronom")
public Boolean isWithMetronom() {
return withMetronom.getValue();
}
public void setWithMetronom(boolean withMetronom) {
this.withMetronom.set(withMetronom);
}
public BooleanProperty withMetronomProperty() {
return withMetronom;
}
#XmlElement(name="startSpeed")
public Double getStartSpeed() {
return startSpeed.get();
}
public void setStartSpeed(double startDuration) {
this.startSpeed.set(startDuration);
}
public DoubleProperty startSpeedProperty() {
return startSpeed;
}
#XmlElement(name="endSpeed")
public Double getEndSpeed() {
return endSpeed.get();
}
public void setEndSpeed(double endDuration) {
this.endSpeed.set(endDuration);
}
public DoubleProperty endSpeedProperty() {
return endSpeed;
}
public double getSpeed(int rep) {
if(getStartSpeed().equals(getEndSpeed())) {
return getStartSpeed();
}
double min, max;
if(getStartSpeed() > getEndSpeed()) {
max = getStartSpeed();
min = getEndSpeed();
} else {
min = getStartSpeed();
max = getEndSpeed();
}
double diff = max - min;
double increment = diff / (getRepsPerSet()-1);
return min + rep * increment;
}
#Override
public String toString() {
return getName();
}
}
I used this to marshall
public void save(Session session) {
try {
JAXBContext jc = JAXBContext.newInstance(Session.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(session, new File(System.getProperty("user.home"), ".sifuSays/sessions/" + session.getName() + ".xml"));
} catch (JAXBException e) {
System.err.println("Cannot save session " + session.getName());
e.printStackTrace();
}
}
which generates the following xml file
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Session>
<exerciseList>
<exercise>
<selected>true</selected>
<breakBetweenSetsInSec>5</breakBetweenSetsInSec>
<displayTimeInSecs>-1</displayTimeInSecs>
<endSpeed>3.0</endSpeed>
<mediaFilePath>Tan Pak Gan.mp4</mediaFilePath>
<name>Solo Tan Pak Gan Drill</name>
<numOfSets>2</numOfSets>
<repsPerSet>3</repsPerSet>
<showIntro>false</showIntro>
<startSpeed>1.0</startSpeed>
<withMetronom>false</withMetronom>
</exercise>
<exercise>
<selected>true</selected>
<breakBetweenSetsInSec>5</breakBetweenSetsInSec>
<displayTimeInSecs>-1</displayTimeInSecs>
<endSpeed>1.0</endSpeed>
<mediaFilePath>Chain Punches.mp4</mediaFilePath>
<name>Solo Ein-Arm-Zyklus</name>
<numOfSets>2</numOfSets>
<repsPerSet>4</repsPerSet>
<showIntro>true</showIntro>
<startSpeed>1.0</startSpeed>
<withMetronom>true</withMetronom>
</exercise>
<exercise>
<selected>true</selected>
<breakBetweenSetsInSec>5</breakBetweenSetsInSec>
<displayTimeInSecs>10</displayTimeInSecs>
<endSpeed>1.0</endSpeed>
<mediaFilePath>birddogs.jpg</mediaFilePath>
<name>Stetching</name>
<numOfSets>1</numOfSets>
<repsPerSet>1</repsPerSet>
<showIntro>true</showIntro>
<startSpeed>1.0</startSpeed>
<withMetronom>false</withMetronom>
</exercise>
</exerciseList>
<breakBetweenExercisesInSec>10</breakBetweenExercisesInSec>
<name>MMA - Solo</name>
</Session>
and this is the unmarshaller code
public ObservableList<Session> loadSessions() {
sessions = FXCollections.observableArrayList();
try {
List<File> xmlSessionFiles = Stream.of(
new File(System.getProperty("user.home"), ".sifuSays/sessions/").listFiles())
.filter(file -> !file.isDirectory())
.filter(file -> file.getName().endsWith("xml"))
.collect(Collectors.toList());
for(File xmlSessionFile: xmlSessionFiles) {
JAXBContext jc = JAXBContext.newInstance(Session.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Session session = (Session) unmarshaller.unmarshal(xmlSessionFile);
sessions.add(session);
}
} catch (JAXBException e) {
System.err.println("Cannot load sessions");
e.printStackTrace();
}
return sessions;
}
While numOfSets is marshalled, repsPerset is not. Neither startSpeed or stopSpeed are unmarshalled and neither withMetronom or showIntro. But name and mediaFilePath are marshalled. What's wrong?
You are mixing up boxed (Double, Integer) and unboxed (double, int) types. I recommend sticking to boxed types for marshalling since otherwise you'll end up with things set to 0 that you weren't expecting.
Related
This Is Main Fragment
Fragment:
private void getStock() {
dialog.show();
Retrofit retrofit = RetrofitClient.getRetrofitInstance();
apiInterface api = retrofit.create(apiInterface.class);
Call<List<Blocks>>call = api.getVaccineBlocks();
call.enqueue(new Callback<List<Blocks>>() {
#Override
public void onResponse(Call<List<Blocks>>call, Response<List<Blocks>> response) {
if (response.code() == 200) {
block = response.body();
spinnerada();
dialog.cancel();
}else{
dialog.cancel();
}
}
#Override
public void onFailure(Call<List<Blocks>> call, Throwable t) {
dialog.cancel();
}
});
}
private void spinnerada() {
String[] s = new String[block.size()];
for (int i = 0; i < block.size(); i++) {
s[i] = block.get(i).getBlockName();
final ArrayAdapter a = new ArrayAdapter(getContext(), android.R.layout.simple_spinner_item, s);
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
//Setting the ArrayAdapter data on the Spinner
spinner.setAdapter(a);
}
}
This Is Blocks Model
model:
package com.smmtn.book.models;
import java.io.Serializable;
public class Blocks implements Serializable {
public String id;
public String blockName;
public String blockSlug;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBlockName() {
return blockName;
}
public void setBlockName(String blockName) {
this.blockName = blockName;
}
public String getBlockSlug() {
return blockSlug;
}
public void setBlockSlug(String blockSlug) {
this.blockSlug = blockSlug;
}
}
here i need onitemclick with blockslug please any one can help, am new to android so i need some example.when on click i want take blockslug and load another method with that blockslug,like will get data from u "http://example.com/block/"+blockslug
i want to get blockslug from selected block
i hope guys i will get help
and sorry for my bad English,
First of all, you need to implement setOnItemSelectedListener. Refer to this https://stackoverflow.com/a/20151596/9346054
Once you selected the item, you can call them by making a new method. Example like below
public void onItemSelected(AdapterView<?> parent, View view, int pos,long id) {
Toast.makeText(parent.getContext(),
"OnItemSelectedListener : " + parent.getItemAtPosition(pos).toString(),
Toast.LENGTH_SHORT).show();
final String itemSelected = parent.getItemAtPosition(pos).toString();
showBlockSlug(itemSelected);
}
And then, at the method showBlockSlug() , you can call Retrofit.
private void showBlockSlug(final String blockslug){
final String url = "http://example.com/block/"+ blockslug;
//Do your stuff...
}
It took me way too long to set up a listener on one property of the objects in my Observablelist and add a listener to it.
ObservableList<Track> observableResult = FXCollections.observableArrayList((Track tr)-> new Observable[]{tr.selectedProperty()});
observableResult.addListener(new ListChangeListener<Track>() {
#Override
public void onChanged(Change<? extends Track> c) {
c.next();
for(Track k : c.getAddedSubList()){
System.out.println(k.getTrackName());
}
}
});
But I can't seem to be able to locate the actual object the change has been made to. The Change class only appears to support added and removed members, which don't get triggered by the actual changes inside them.
I have a workaround for this, just calling another method that would loop trough the entire ObservableArrayList and get for example, only the selected items, but that gets pretty expensive after I have a couple of thousand objects. Finding the source members that got changed would allow me to just push them to another array and save a bunch of overhead.
You can call getFrom() on the change to get the index of the changed item. I don't think there's a way to actually figure out which property changed (if you have more than one property listed in the extractor) or get the old value, but maybe this is enough.
If you need more, you could consider registering your own listeners with the list to track, which would be tricky but not impossible.
Here's an SSCCE demonstrating the getFrom() call:
import java.util.Random;
import java.util.stream.IntStream;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
public class ListChangeListenerTest {
public static void main(String[] args) {
ObservableList<Item> itemList = FXCollections.observableArrayList(item -> new Observable[]{item.valueProperty()});
itemList.addListener((Change<? extends Item> c) -> {
while (c.next()) {
if (c.wasUpdated()) {
int index = c.getFrom();
System.out.println("Updated item at "+index+" new value is "+itemList.get(index).getValue());
}
}
});
IntStream.rangeClosed(1, 1000).mapToObj(Item::new).forEach(itemList::add);
Random rng = new Random();
itemList.get(rng.nextInt(itemList.size())).setValue(rng.nextInt(10000));
}
public static class Item {
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(int value) {
setValue(value);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
}
}
Here's a version that manages the listeners on the property manually. Note that
This doesn't use an extractor on the list
The property in the Item bean is constructed passing a reference to the bean that owns the property. This allows the listener on the property to get a reference to the Item (via a bit of ugly downcasting)
This gives a bit more flexibility; e.g. if you wanted to check modifications on multiple properties and perform different actions, this would allow this. As you can see, the listener can also access the old value.
import java.util.Random;
import java.util.stream.IntStream;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
public class ListChangeListenerTest {
public static void main(String[] args) {
ChangeListener<Number> valueListener = (obs, oldValue, newValue) -> {
Item item = (Item) ((Property<?>) obs).getBean();
System.out.println("Value for "+item+" changed from " + oldValue + " to "+newValue);
};
ObservableList<Item> itemList = FXCollections.observableArrayList();
itemList.addListener((Change<? extends Item> change) -> {
while (change.next()) {
if (change.wasAdded()) {
for (Item item : change.getAddedSubList()) {
item.valueProperty().addListener(valueListener);
}
}
if (change.wasRemoved()) {
for (Item item : change.getRemoved()) {
item.valueProperty().removeListener(valueListener);
}
}
}
});
IntStream.rangeClosed(1, 1000).mapToObj(Item::new).forEach(itemList::add);
Random rng = new Random();
itemList.get(rng.nextInt(itemList.size())).setValue(rng.nextInt(10000));
}
public static class Item {
private final IntegerProperty value = new SimpleIntegerProperty(this, "value");
private final String id ;
public Item(int value) {
id = "Item "+value ;
setValue(value);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
#Override
public String toString() {
return id ;
}
}
}
Finally, if you want to account for "bulk" updates, you need to implement ObservableList yourself. You can do this by subclassing ModifiableObservableListBase, and the basic idea is pretty straightforward. The implementation is made a bit tedious by having to create the Change object representing the update, but it's not too bad. Here's an example that allows updating a contiguous range:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ModifiableObservableListBase;
public class UpdatingObservableList<T> extends ModifiableObservableListBase<T> {
private final List<T> list ;
public UpdatingObservableList(List<T> list) {
this.list = list ;
}
public UpdatingObservableList() {
this(new ArrayList<>());
}
public void updateSublist(int start, int end, Consumer<T> updater) {
if (start < 0) throw new ArrayIndexOutOfBoundsException("Start ("+start+") cannot be < 0");
if (end < start) throw new IllegalArgumentException("End ("+end+") cannot be less than start ("+start+")");
if (end > size()) throw new ArrayIndexOutOfBoundsException("End ("+end+") cannot be greater than list size ("+size()+")");
for (T element : list.subList(start, end)) {
updater.accept(element);
}
fireChange(createUpdate(start, end));
}
#Override
public T get(int index) {
return list.get(index);
}
#Override
public int size() {
return list.size();
}
#Override
protected void doAdd(int index, T element) {
list.add(index, element);
}
#Override
protected T doSet(int index, T element) {
return list.set(index, element);
}
#Override
protected T doRemove(int index) {
return list.remove(index);
}
private Change<T> createUpdate(int start, int end) {
return new Change<T>(this) {
private boolean initialState = true ;
#Override
public boolean next() {
if (initialState) {
initialState = false ;
return true ;
}
return false ;
}
#Override
public void reset() {
initialState = true ;
}
#Override
public int getFrom() {
checkState();
return start ;
}
#Override
public int getTo() {
checkState();
return end ;
}
#Override
public List<T> getAddedSubList() {
checkState();
return Collections.emptyList();
}
#Override
public List<T> getRemoved() {
checkState();
return Collections.emptyList();
}
#Override
protected int[] getPermutation() {
checkState();
return new int[0];
}
#Override
public boolean wasAdded() {
checkState();
return false ;
}
#Override
public boolean wasRemoved() {
checkState();
return false ;
}
#Override
public boolean wasUpdated() {
return true ;
}
#Override
public boolean wasPermutated() {
checkState();
return false ;
}
#Override
public int getRemovedSize() {
checkState();
return 0 ;
}
#Override
public int getAddedSize() {
checkState();
return 0 ;
}
private void checkState() {
if (initialState) {
throw new IllegalStateException("Must call Change.next()");
}
}
};
}
}
and here's a version of the test class that uses this. Note that the update is performed via the list:
import java.util.Random;
import java.util.stream.IntStream;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.ListChangeListener.Change;
public class ListChangeListenerTest {
public static void main(String[] args) {
UpdatingObservableList<Item> itemList = new UpdatingObservableList<Item>();
itemList.addListener((Change<? extends Item> change) -> {
while (change.next()) {
if (change.wasUpdated()) {
for (int i = change.getFrom() ; i < change.getTo() ; i++) {
System.out.println(itemList.get(i) + " updated - new value: "+itemList.get(i).getValue());
}
}
}
});
IntStream.rangeClosed(1, 1000).mapToObj(Item::new).forEach(itemList::add);
Random rng = new Random();
int start = rng.nextInt(itemList.size());
int end = Math.min(itemList.size(), start + 1 + rng.nextInt(15));
itemList.updateSublist(start, end, item -> item.setValue(rng.nextInt(10000)));
}
public static class Item {
private final IntegerProperty value = new SimpleIntegerProperty(this, "value");
private final String id ;
public Item(int value) {
id = "Item "+value ;
setValue(value);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
#Override
public String toString() {
return id ;
}
}
}
I am all new to JAXB and having wrecked my brains for a week over this I would like to ask the following question. How can I obtain default namespace declarations like these using a generic list wrapper as Blaise Doughan indroduced:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<departments xmlns="urn:example.org/departments">
<department>
<iddepartment>1</iddepartment>
<department>Deparment A</department>
<v_iddepartment>1</v_iddepartment>
</department>
<department>
<iddepartment>2</iddepartment>
<department>Department B</department>
<v_iddepartment>1</v_iddepartment>
</department>
</departments>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<employees xmlns="urn:example.org/employees">
<employee>
<firstname>Tom</firstname>
<lastname>Jones</lastname>
<praefix>Dr.</praefix>
<birthdate>1970-01-01</birthdate>
<ipnumber>1234</ipnumber>
</employee>
<employee>
<firstname>Elvis</firstname>
<lastname>Knoxville</lastname>
<praefix/>
<birthdate>1970-01-02</birthdate>
<ipnumber>4567</ipnumber>
</employee>
</employees>
I have several annotated classes like these:
package org.bp24.server.table;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlRootElement(name = "department", namespace = "urn:example.org/departments")
#XmlType(propOrder = {"iddepartment", "department", "v_iddepartment"})
public class Department {
private int iddepartment;
private String department;
private int v_iddepartment;
public int getIddepartment() {
return iddepartment;
}
public void setIddepartment(int iddepartment) {
this.iddepartment = iddepartment;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public int getV_iddepartment() {
return v_iddepartment;
}
public void setV_iddepartment(int v_iddepartment) {
this.v_iddepartment = v_iddepartment;
}
}
package org.bp24.server.table;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
#XmlRootElement(name = "employee", namespace = "urn:example.org/employees")
#XmlType(propOrder = {"firstname", "lastname", "praefix", "suffix", "birthdate", "ipnumber"})
public class Employee {
private int idemployee;
private String firstname;
private String lastname;
private String praefix;
private String suffix;
private String birthdate;
private String ipnumber;
private int v_iduser;
#XmlTransient
public int getIdemployee() {
return idemployee;
}
public void setIdemployee(int idemployee) {
this.idemployee = idemployee;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getPraefix() {
return praefix;
}
public void setPraefix(String praefix) {
this.praefix = praefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String getBirthdate() {
return birthdate;
}
public void setBirthdate(String birthdate) {
this.birthdate = birthdate;
}
public String getIpnumber() {
return ipnumber;
}
public void setIpnumber(String ipnumber) {
this.ipnumber = ipnumber;
}
#XmlTransient
public int getV_iduser() {
return v_iduser;
}
public void setV_iduser(int v_iduser) {
this.v_iduser = v_iduser;
}
}
Here is the list wrapper I use:
package org.bp24.server.xml.copy;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
public class GenericList <T> {
private List<T> list = new ArrayList<T>();
public GenericList() {
}
public GenericList(List<T> list) {
this.list = list;
}
public void add (T element){
list.add(element);
}
public boolean isEmpty(){
if(list.isEmpty()) return true;
return false;
}
public T get (int pos){
return list.get(pos);
}
#XmlAnyElement(lax=true)
public List<T> getList(){
return list;
}
}
And here is the marshalling code:
package org.bp24.server.xml.copy;
import java.util.HashMap;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import org.bp24.server.xml.GenericList;
public class MarshallMatters<T> {
private static volatile HashMap<Class, JAXBContext> contextStore = new HashMap<Class, JAXBContext>();
//synchronized to ensure orderly concurrent contextStore access
private synchronized JAXBContext getContext (Class clazz) throws JAXBException{
if(contextStore.containsKey(clazz)) return contextStore.get(clazz);
JAXBContext context = JAXBContext.newInstance(GenericList.class, clazz);
contextStore.put(clazz, context);
return context;
}
private Class getClassFromList(List<T> list){
if(!list.isEmpty()) return list.get(0).getClass();
return null;
}
public void writeXml(List<T> list) throws JAXBException{
Class clazz = getClassFromList(list);
if(clazz==null){
System.out.println("Error message");
return;
}
JAXBContext jc = getContext(clazz);
GenericList<T> genList = new GenericList<T>(list);
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
QName qn = new QName(clazz.getSimpleName().toLowerCase() + "s");
JAXBElement<GenericList> jaxbe = new JAXBElement<GenericList>(qn, GenericList.class, genList);
m.marshal(jaxbe, System.out);
}
public void readXml(List<T> list) throws JAXBException{
...
}
}
Thank you very much in advance for your help.
I'm creating a TableView to show information regarding a list of custom objects (EntityEvents).
The table view must have 2 columns.
First column to show the corresponding EntityEvent's name.
The second column would display a button. The button text deppends on a property of the EntityEvent. If the property is ZERO, it would be "Create", otherwise "Edit".
I managed to do it all just fine, except that I can't find a way to update the TableView line when the corresponding EntityEvent object is changed.
Very Important: I can't change the EntityEvent class to use JavaFX properties, since they are not under my control. This class uses PropertyChangeSupport to notify listeners when the monitored property is changed.
Note:
I realize that adding new elements to the List would PROBABLY cause the TableView to repaint itself, but that is not what I need. I say PROBABLY because I've read about some bugs that affect this behavior.
I tried using this approach to force the repaint, by I couldn't make it work.
Does anyone knows how to do it?
Thanks very much.
Here is a reduced code example that illustrates the scenario:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application {
//=============================================================================================
public class EntityEvent {
private String m_Name;
private PropertyChangeSupport m_NamePCS = new PropertyChangeSupport(this);
private int m_ActionCounter;
private PropertyChangeSupport m_ActionCounterPCS = new PropertyChangeSupport(this);
public EntityEvent(String name, int actionCounter) {
m_Name = name;
m_ActionCounter = actionCounter;
}
public String getName() {
return m_Name;
}
public void setName(String name) {
String lastName = m_Name;
m_Name = name;
System.out.println("Name changed: " + lastName + " -> " + m_Name);
m_NamePCS.firePropertyChange("Name", lastName, m_Name);
}
public void addNameChangeListener(PropertyChangeListener listener) {
m_NamePCS.addPropertyChangeListener(listener);
}
public int getActionCounter() {
return m_ActionCounter;
}
public void setActionCounter(int actionCounter) {
int lastActionCounter = m_ActionCounter;
m_ActionCounter = actionCounter;
System.out.println(m_Name + ": ActionCounter changed: " + lastActionCounter + " -> " + m_ActionCounter);
m_ActionCounterPCS.firePropertyChange("ActionCounter", lastActionCounter, m_ActionCounter);
}
public void addActionCounterChangeListener(PropertyChangeListener listener) {
m_ActionCounterPCS.addPropertyChangeListener(listener);
}
}
//=============================================================================================
private class AddPersonCell extends TableCell<EntityEvent, String> {
Button m_Button = new Button("Undefined");
StackPane m_Padded = new StackPane();
AddPersonCell(final TableView<EntityEvent> table) {
m_Padded.setPadding(new Insets(3));
m_Padded.getChildren().add(m_Button);
m_Button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
// Do something
}
});
}
#Override protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(m_Padded);
m_Button.setText(item);
}
}
}
//=============================================================================================
private ObservableList<EntityEvent> m_EventList;
//=============================================================================================
#SuppressWarnings("unchecked")
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Table View test.");
VBox container = new VBox();
m_EventList = FXCollections.observableArrayList(
new EntityEvent("Event 1", -1),
new EntityEvent("Event 2", 0),
new EntityEvent("Event 3", 1)
);
final TableView<EntityEvent> table = new TableView<EntityEvent>();
table.setItems(m_EventList);
TableColumn<EntityEvent, String> eventsColumn = new TableColumn<>("Events");
TableColumn<EntityEvent, String> actionCol = new TableColumn<>("Actions");
actionCol.setSortable(false);
eventsColumn.setCellValueFactory(new Callback<CellDataFeatures<EntityEvent, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<EntityEvent, String> p) {
EntityEvent event = p.getValue();
event.addActionCounterChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent event) {
// TODO: I'd like to update the table cell information.
}
});
return new ReadOnlyStringWrapper(event.getName());
}
});
actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<EntityEvent, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<EntityEvent, String> ev) {
String text = "NONE";
if(ev.getValue() != null) {
text = (ev.getValue().getActionCounter() != 0) ? "Edit" : "Create";
}
return new ReadOnlyStringWrapper(text);
}
});
// create a cell value factory with an add button for each row in the table.
actionCol.setCellFactory(new Callback<TableColumn<EntityEvent, String>, TableCell<EntityEvent, String>>() {
#Override
public TableCell<EntityEvent, String> call(TableColumn<EntityEvent, String> personBooleanTableColumn) {
return new AddPersonCell(table);
}
});
table.getColumns().setAll(eventsColumn, actionCol);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
// Add Resources Button
Button btnInc = new Button("+");
btnInc.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ev) {
System.out.println("+ clicked.");
EntityEvent entityEvent = table.getSelectionModel().getSelectedItem();
if (entityEvent == null) {
System.out.println("No Event selected.");
return;
}
entityEvent.setActionCounter(entityEvent.getActionCounter() + 1);
// TODO: I expected the TableView to be updated since I modified the object.
}
});
// Add Resources Button
Button btnDec = new Button("-");
btnDec.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ev) {
System.out.println("- clicked.");
EntityEvent entityEvent = table.getSelectionModel().getSelectedItem();
if (entityEvent == null) {
System.out.println("No Event selected.");
return;
}
entityEvent.setActionCounter(entityEvent.getActionCounter() - 1);
// TODO: I expected the TableView to be updated since I modified the object.
}
});
container.getChildren().add(table);
container.getChildren().add(btnInc);
container.getChildren().add(btnDec);
Scene scene = new Scene(container, 300, 600, Color.WHITE);
primaryStage.setScene(scene);
primaryStage.show();
}
//=============================================================================================
public Main() {
}
//=============================================================================================
public static void main(String[] args) {
launch(Main.class, args);
}
}
Try the javafx.beans.property.adapter classes, particularly JavaBeanStringProperty and JavaBeanIntegerProperty. I haven't used these, but I think you can do something like
TableColumn<EntityEvent, Integer> actionCol = new TableColumn<>("Actions");
actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<EntityEvent, Integer> ev) {
return new JavaBeanIntegerPropertyBuilder().bean(ev.getValue()).name("actionCounter").build();
});
// ...
public class AddPersonCell extends TableCell<EntityEvent, Integer>() {
final Button button = new Button();
public AddPersonCell() {
setPadding(new Insets(3));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
button.setOnAction(...);
}
#Override
public void updateItem(Integer actionCounter, boolean empty) {
if (empty) {
setGraphic(null);
} else {
if (actionCounter.intValue()==0) {
button.setText("Create");
} else {
button.setText("Add");
}
setGraphic(button);
}
}
}
As I said, I haven't used the Java bean property adapter classes, but the idea is that they "translate" property change events to JavaFX change events. I just typed this in here without testing, but it should at least give you something to start with.
UPDATE: After a little experimenting, I don't think this approach will work if your EntityEvent is really set up the way you showed it in your code example. The standard Java beans bound properties pattern (which the JavaFX property adapters rely on) has a single property change listener and an addPropertyChangeListener(...) method. (The listeners can query the event to see which property changed.)
I think if you do
public class EntityEvent {
private String m_Name;
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private int m_ActionCounter;
public EntityEvent(String name, int actionCounter) {
m_Name = name;
m_ActionCounter = actionCounter;
}
public String getName() {
return m_Name;
}
public void setName(String name) {
String lastName = m_Name;
m_Name = name;
System.out.println("Name changed: " + lastName + " -> " + m_Name);
pcs.firePropertyChange("name", lastName, m_Name);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
public int getActionCounter() {
return m_ActionCounter;
}
public void setActionCounter(int actionCounter) {
int lastActionCounter = m_ActionCounter;
m_ActionCounter = actionCounter;
System.out.println(m_Name + ": ActionCounter changed: " + lastActionCounter + " -> " + m_ActionCounter);
pcs.firePropertyChange("ActionCounter", lastActionCounter, m_ActionCounter);
}
}
it will work with the adapter classes above. Obviously, if you have existing code calling the addActionChangeListener and addNameChangeListener methods you would want to keep those existing methods and the existing property change listeners, but I see no reason you can't have both.
My JAXB parser suddenly stopped working today. It was working for several weeks. I get the following message. I haven't changed this code for several weeks. Wondering if this set up is good.
EDIT 2: Please could somebody help me! I can't figure this out.
EDIT 1:
My acceptance tests running the same code below are working fine. I believe this is a
classloading issue. I am using the JAXB and StAX in the JDK. However, when I deploy to jboss 5.1, I get the error below. Using 1.6.0_26 (locally) and 1.6.0_30 (dev server). Still puzzling over a solution.
unexpected element (uri:"", local:"lineEquipmentRecord"). Expected
elements are
<{}switchType>,<{}leSwitchId>,<{}nodeAddress>,<{}leId>,<{}telephoneSuffix>,<{}leFormatCode>,<{}groupIdentifier>,<{}telephoneNpa>,<{}telephoneLine>,<{}telephoneNxx>
Here is my unmarshalling class:
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
public class PartialUnmarshaller<T> {
XMLStreamReader reader;
Class<T> clazz;
Unmarshaller unmarshaller;
public PartialUnmarshaller(InputStream stream, Class<T> clazz) throws XMLStreamException, FactoryConfigurationError, JAXBException {
this.clazz = clazz;
this.unmarshaller = JAXBContext.newInstance(clazz).createUnmarshaller();
unmarshaller.setEventHandler(new ValidationEventHandler() {
#Override
public boolean handleEvent(ValidationEvent event) {
System.out.println(event.getMessage());
return true;
}
});
this.reader = XMLInputFactory.newInstance().createXMLStreamReader(stream);
/* ignore headers */
skipElements(XMLStreamConstants.START_DOCUMENT);
/* ignore root element */
reader.nextTag();
/* if there's no tag, ignore root element's end */
skipElements(XMLStreamConstants.END_ELEMENT);
}
public T next() throws XMLStreamException, JAXBException {
if (!hasNext())
throw new NoSuchElementException();
T value = unmarshaller.unmarshal(reader, clazz).getValue();
skipElements(XMLStreamConstants.CHARACTERS, XMLStreamConstants.END_ELEMENT);
return value;
}
public boolean hasNext() throws XMLStreamException {
return reader.hasNext();
}
public void close() throws XMLStreamException {
reader.close();
}
private void skipElements(Integer... elements) throws XMLStreamException {
int eventType = reader.getEventType();
List<Integer> types = new ArrayList<Integer>(Arrays.asList(elements));
while (types.contains(eventType))
eventType = reader.next();
}
}
This class is used as follows:
List<MyClass> lenList = new ArrayList<MyClass>();
PartialUnmarshaller<MyClass> pu = new PartialUnmarshaller<MyClass>(
is, MyClass.class);
while (pu.hasNext()) {
lenList.add(pu.next());
}
The XML being unmarshalled:
<?xml version="1.0" encoding="UTF-8"?>
<lineEquipment>
<lineEquipmentRecord>
<telephoneNpa>333</telephoneNpa>
<telephoneNxx>333</telephoneNxx>
<telephoneLine>4444</telephoneLine>
<telephoneSuffix>1</telephoneSuffix>
<nodeAddress>xxxx</nodeAddress>
<groupIdentifier>LEN</groupIdentifier>
</lineEquipmentRecord>
<lineEquipmentRecord>
<telephoneNpa>111</telephoneNpa>
<telephoneNxx>111</telephoneNxx>
<telephoneLine>2222</telephoneLine>
<telephoneSuffix>0</telephoneSuffix>
<nodeAddress>xxxx</nodeAddress>
<groupIdentifier>LEN</groupIdentifier>
</lineEquipmentRecord>
</lineEquipment>
Finally, here is MyClass:
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* This class is used as an envelope to hold Martens
* line equipment information.
* #author spgezf
*
*/
#XmlRootElement(name="lineEquipmentRecord")
public class MyClass {
private String telephoneNpa;
private String telephoneNxx;
private String telephoneLine;
private String telephoneSuffix;
private String nodeAddress;
private String groupIdentifier;
public MyClass(){
}
// Getters and Setters.
#XmlElement(name="telephoneNpa")
public String getTelephoneNpa() {
return telephoneNpa;
}
public void setTelephoneNpa(String telephoneNpa) {
this.telephoneNpa = telephoneNpa;
}
#XmlElement(name="telephoneNxx")
public String getTelephoneNxx() {
return telephoneNxx;
}
public void setTelephoneNxx(String telephoneNxx) {
this.telephoneNxx = telephoneNxx;
}
#XmlElement(name="telephoneLine")
public String getTelephoneLine() {
return telephoneLine;
}
public void setTelephoneLine(String telephoneLine) {
this.telephoneLine = telephoneLine;
}
#XmlElement(name="telephoneSuffix")
public String getTelephoneSuffix() {
return telephoneSuffix;
}
public void setTelephoneSuffix(String telephoneSuffix) {
this.telephoneSuffix = telephoneSuffix;
}
#XmlElement(name="nodeAddress")
public String getNodeAddress() {
return nodeAddress;
}
public void setNodeAddress(String nodeAddress) {
this.nodeAddress = nodeAddress;
}
#XmlElement(name="groupIdentifier")
public String getGroupIdentifier() {
return groupIdentifier;
}
public void setGroupIdentifier(String groupIdentifier) {
this.groupIdentifier = groupIdentifier;
}
}
Thanks, this is classloading issue I couldn't overcome so I abandoned JAXB.
Your PartialUnmarshaller code worked for me. Below is an alternate version that changes the skipElements method that may work better.
import java.io.InputStream;
import java.util.NoSuchElementException;
import javax.xml.bind.*;
import javax.xml.stream.*;
public class PartialUnmarshaller<T> {
XMLStreamReader reader;
Class<T> clazz;
Unmarshaller unmarshaller;
public PartialUnmarshaller(InputStream stream, Class<T> clazz) throws XMLStreamException, FactoryConfigurationError, JAXBException {
this.clazz = clazz;
this.unmarshaller = JAXBContext.newInstance(clazz).createUnmarshaller();
unmarshaller.setEventHandler(new ValidationEventHandler() {
#Override
public boolean handleEvent(ValidationEvent event) {
System.out.println(event.getMessage());
return true;
}
});
this.reader = XMLInputFactory.newInstance().createXMLStreamReader(stream);
/* ignore headers */
skipElements();
/* ignore root element */
reader.nextTag();
/* if there's no tag, ignore root element's end */
skipElements();
}
public T next() throws XMLStreamException, JAXBException {
if (!hasNext())
throw new NoSuchElementException();
T value = unmarshaller.unmarshal(reader, clazz).getValue();
skipElements();
return value;
}
public boolean hasNext() throws XMLStreamException {
return reader.hasNext();
}
public void close() throws XMLStreamException {
reader.close();
}
private void skipElements() throws XMLStreamException {
while(reader.hasNext() && !reader.isStartElement()) {
reader.next();
}
}
}