X

Geertjan's Blog

Car Designer on the NetBeans Platform (Part 3)

Geertjan Wielenga
Product Manager
Now we'll synchronize the Node with the TopComponent, enable the Save functionality, and serialize/deserialize. Many thanks to Urs Bill and Tom Wheeler for their input in the last days.

Here's a pic to capture the concept of what I'd like to achieve:

Regardless of whether the Node in the explorer view or the TopComponent is selected, the Save functionality should be enabled or disabled, depending on the state of the underlying object.

Here's the Car object:

public class Car {
String type;
String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

The above is serialized/deserialized to/from files with this structure (at the moment I'm only handling the type and color, the rest is just here to give some context of what the file is for):

<?xml version="1.0" encoding="UTF-8"?>
<car color="blue" type="Honda">
<shape_ref file="wheel.xml"/>
<shape_ref file="door.xml"/>
</car>

One of the questions from Urs was about who has responsibility for what. His point is that the DataObject should have responsibility for persistence, which makes sense, since the DataObject is the object with knowledge about the content of files. Hence, let's start by getting some ugly code out of the way. Here it is, my code for serializing and deserializing, both methods below are in my DataObject:

public void serialize(Car car) {
try {
//Get the InputStream of the file:
InputStream is = getPrimaryFile().getInputStream();
//Use the NetBeans org.openide.xml.XMLUtil class to create an org.w3c.dom.Document:
Document doc = XMLUtil.parse(new InputSource(is), true, true, null, null);
//Find the car node:
NodeList list = doc.getElementsByTagName("car");
NamedNodeMap map = list.item(0).getAttributes();
//Iterate through the map of attributes:
for (int j = 0; j < map.getLength(); j++) {
//Each iteration, create a new Node:
org.w3c.dom.Node attrNode = map.item(j);
//Change the values of the relevant attributes:
String attrName = attrNode.getNodeName();
if (attrName.equals("type")) {
attrNode.setNodeValue(car.getType());
}
if (attrName.equals("color")) {
attrNode.setNodeValue(car.getColor());
}
}
is.close();
//Write the changed document to the underlying file:
FileOutputStream fos = null;
try {
fos = new FileOutputStream(FileUtil.toFile(getPrimaryFile()));
XMLUtil.write(doc, fos, "UTF-8"); // NOI18N
} catch (Exception e) {
Exceptions.printStackTrace(e);
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (Exception e) {
Exceptions.printStackTrace(e);
}
}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
} catch (SAXException ex) {
Exceptions.printStackTrace(ex);
}
}

And here is the deserialization code, a car being returned based on the content of the underlying file, which is retrieved via "getPrimaryFile" on the DataObject:

//Create a new car, using the attributes from the underlying file:
public Car deserialize() {
Car car = new Car();
try {
//Get the InputStream of the file:
InputStream is = getPrimaryFile().getInputStream();
//Use the NetBeans org.openide.xml.XMLUtil class to create an org.w3c.dom.Document:
Document doc = XMLUtil.parse(new InputSource(is), true, true, null, null);
//Find the car node:
NodeList list = doc.getElementsByTagName("car");
NamedNodeMap map = list.item(0).getAttributes();
//Iterate through the map of attributes:
for (int j = 0; j < map.getLength(); j++) {
//Each iteration, create a new Node:
org.w3c.dom.Node attrNode = map.item(j);
//Set the car, using the attributes in the file:
String attrName = attrNode.getNodeName();
if (attrName.equals("color")) {
String color = attrNode.getNodeValue();
car.setColor(color);
}
if (attrName.equals("type")) {
String type = attrNode.getNodeValue();
car.setType(type);
}
}
is.close();
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
} catch (SAXException ex) {
Exceptions.printStackTrace(ex);
}
return car;
}

OK, now that that unpleasantness is out of the way, let's look at the SaveCookie-related functionality. This is defined in the DataObject, as follows:

public class CarSaveCookie implements SaveCookie {
@Override
public void save() throws IOException {
Car car = getLookup().lookup(Car.class);
Confirmation message = new NotifyDescriptor.Confirmation("Do you want to save \\""
+ car.getType() + "?",
NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.QUESTION_MESSAGE);
Object result = DialogDisplayer.getDefault().notify(message);
//When user clicks "Yes", indicating they really want to save,
//we need to disable the Save action,
//so that it will only be usable when the next change is made
//to the JTextField:
if (NotifyDescriptor.YES_OPTION.equals(result)) {
setModified(false);
//Save the car to disk:
serialize(car);
//Change the display name of the Node:
carDataNode.setDisplayName("Car: " + car.getType());
}
}
}
//Combine the SaveAll action with the Save action,
//thanks to Tom Wheeler:
@Override
public void setModified(boolean isModified) {
if (isModified) {
if (getCookie(SaveCookie.class) == null) {
getCookieSet().add(saveCookie);
}
} else {
SaveCookie cookie = (SaveCookie) getCookie(SaveCookie.class);
if (cookie != null) {
getCookieSet().remove(cookie);
}
}
super.setModified(isModified);
}

The CarSaveCookie is defined as a class variable, initialized in the constructor of the CarDataObject.

Finally, I have a class variable, in the DataObject, holding my Node, and here is how it is created:

@Override
protected Node createNodeDelegate() {
//Create a new Node for the file:
carDataNode = new DataNode(this, Children.LEAF, getLookup());
//Read the underlying file and create the car:
Car car = deserialize();
//Add the car to the Lookup of the DataObject:
getCookieSet().assign(Car.class, car);
//Use the car info to create the display name:
carDataNode.setDisplayName("Car: " + car.getType());
return carDataNode;
}

Next, let's turn to the TopComponent. In its constructor, we set listeners on the text fields and call the "setModified" in the DataObject, via a class variable holding the DataObject. We also update the underlying car object with the new type or color:

typeField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
Car car = cdo.getLookup().lookup(Car.class);
car.setType(typeField.getText());
cdo.setModified(true);
}
@Override
public void removeUpdate(DocumentEvent e) {
Car car = cdo.getLookup().lookup(Car.class);
car.setType(typeField.getText());
cdo.setModified(true);
}
@Override
public void changedUpdate(DocumentEvent e) {
Car car = cdo.getLookup().lookup(Car.class);
car.setType(typeField.getText());
cdo.setModified(true);
}
});
colorField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
Car car = cdo.getLookup().lookup(Car.class);
car.setColor(colorField.getText());
cdo.setModified(true);
}
@Override
public void removeUpdate(DocumentEvent e) {
Car car = cdo.getLookup().lookup(Car.class);
car.setColor(colorField.getText());
cdo.setModified(true);
}
@Override
public void changedUpdate(DocumentEvent e) {
Car car = cdo.getLookup().lookup(Car.class);
car.setColor(colorField.getText());
cdo.setModified(true);
}
});

And then, of course, the LookupListener in the TopComponent, which we use to listen for the CarDataObject, made available automatically whenever a new CarNode is selected in the explorer view. Define the related code like this:

private Result<CarDataObject> result = null;
@Override
public void resultChanged(LookupEvent le) {
Collection<? extends CarDataObject> allCarNodes = result.allInstances();
if (!allCarNodes.isEmpty()) {
//Get the DataObject from the Lookup:
cdo = result.allInstances().iterator().next();
//Nice trick! Set the DataObject's Node as activated
//which will synchronize the TopComponent with the SaveCookie:
setActivatedNodes(new Node[]{cdo.getNodeDelegate()});
//Get the Car from the Lookup of the Node:
Car car = cdo.getLookup().lookup(Car.class);
typeField.setText(car.getType());
colorField.setText(car.getColor());
}
}
@Override
public void componentOpened() {
//We listen to a specific window, i.e., the Projects window,
//this could be handled differently:
result = WindowManager.getDefault().findTopComponent("projectTabLogical_tc").
getLookup().lookupResult(CarDataObject.class);
result.addLookupListener(this);
resultChanged(new LookupEvent(result));
}
@Override
public void componentClosed() {
result.removeLookupListener(this);
}

And bob's your mother's brother.

Join the discussion

Comments ( 1 )
  • Gualtiero Wednesday, July 6, 2011

    Hi,

    where I can get the complete project files ?

    Thanks,

    Gualtiero


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha
Oracle

Integrated Cloud Applications & Platform Services