I'm trying to copy an object while passing in a parameter to the method I'm using to copy with. In the constructor for the object after I try to copy it the parameter becomes null.
This is the class that I'm trying to copy:
/**
* Represents the extra health perk
*/
public class ArrowRegen implements Perk
{
private int id = 0;
private double chance = 0.15;
private int level = 0;
private int price = 1000;
private int arrowGain = 0;
private GamePlayer ourPlayer;
public ArrowRegen(){}
public ArrowRegen(GamePlayer ourPlayer)
{
this.ourPlayer = ourPlayer;
if(this.ourPlayer == null)
aa.debug("its null");
}
#Override
public Perk getThisPerk(GamePlayer player)
{
aa.debug("returning an arrow regen");
if(player == null)
aa.debug("player is null? somehow?");
return new ArrowRegen(ourPlayer);
}
}
nothing is null up until the point in the constructor at the "its null" comment. *
This is where I call the copy from:
player.addActivePerks(PerkEngine.getPerk(1).getThisPerk(player));
(player is a GamePlayer object)
Here is the PerkEngine bit:
/**
* Gets a perk by it's ID
* #param id The Perk's ID
* #return The Perk with the specified ID
*/
public static Perk getPerk(int id)
{
for(Perk perk : perks)
{
if(perk.getID() == id)
{
return perk;
}
}
// This will never return null
return null;
}
EDIT:
I just realized my mistake was in the "getThisPerk" method. When returning the new object, I pass in the wrong variable.
I just realized my mistake was in the "getThisPerk" method. When returning the new object, I pass in the wrong variable.
Related
In this project (using BlueJ as I am a beginner) I am looking to add Climbers to an ArrayList with their name, gender and age. The Climbers can add what mountain they have climbed. With the mountain name and height.
I need to add a method into the Climber class to return which is the highest mountain a certain Climber has climbed.
To define the method do I define it using
public ArrayList<Mountain> getHighestMountain(Mountain mountainHeight)
I am unsure as how to check the objects in the mountain class to compare. FYI I haven't been learned the comparative keyword yet so would like to refrain from using this.
Club class:
import java.util.ArrayList;
import java.util.Scanner;
/**
* Write a description of class Club here.
*
* #author (your name)
* #version (a version number or a date)
*/
public class Club
{
// An ArrayList for storing climber details.
private ArrayList<Climber> climbers;
/**
* Constructor for objects of class Club
*/
public Club()
{
// Initialise instance variables.
climbers = new ArrayList<Climber>();
}
public void addClimber(Climber newName)
{
climbers.add(newName);
}
public Climber getClimber(String name)
{
Climber foundClimber = null;
int index = 0;
boolean searching = true;
while(searching && index < climbers.size()) {
Climber climber = climbers.get(index);
if(climber.getName().equals(name)) {
searching = false;
foundClimber = climber;
}
else {
System.out.println(name + " not found");
index++;
}
}
return foundClimber;
}
public void displayClimberList()
{
for (int item = 0; item<climbers.size();
item++) {
Climber climber = climbers.get(item);
System.out.println(climber.getName() + (" ") + climber.getAge() + (" ")
+ climber.getGender());
}
}
}
Climber class:
import java.util.ArrayList;
/**
* Write a description of class Climber here.
*
* #author (your name)
* #version (a version number or a date)
*/
public class Climber
{
// Instance variables.
// The climber name.
private String name;
// The climber age
private int age;
// The climber gender.
private String gender;
private ArrayList<Mountain> mountains;
/**
* Constructor for objects of class Climber
*/
public Climber (String newName, int newAge, String newGender)
{
// Initialise instance variables.
name = newName;
age = newAge;
gender = newGender;
mountains = new ArrayList<Mountain>();
}
/**
* Accessor method for climber's name.
*/
public String getName()
{
return name;
}
/**
* Set the climber's name.
*/
public void setName(String newName)
{
name = newName;
}
/**
* Accessor method for climber's age.
*/
public int getAge()
{
return age;
}
/**
* Set the climber's age.
*/
public void setAge(int newAge)
{
age = newAge;
}
/**
* Set the climer's gender.
*/
public String getGender()
{
return gender;
}
/**
* Accessor method for climber's gender.
*/
public void getGender(String newGender)
{
gender = newGender;
}
public Mountain addMountain(Mountain mountain)
{
return mountain;
}
public ArrayList<Mountain> getHighestMountain(Mountain mountainHeight)
{
double maxHeight = 1;
int index = 1;
for(int i = 0; i < mountainHeight.length; i++) {
if(mountainHeight[i].getHeight()>maxHeight) {
index = i;
}
}
}
}
Mountain class:
/**
* Write a description of class Mountain here.
*
* #author (your name)
* #version (a version number or a date)
*/
public class Mountain
{
// Instance variables.
private double height;
private String name;
/**
* Constructor for objects of class Mountain
*/
public Mountain(String mountainName, double mountainHeight)
{
// Initialise instance variables
name = mountainName;
height = mountainHeight;
}
/**
* Accessor method for mountain name.
*/
public String getName()
{
return name;
}
/**
* Set the mountain name.
*/
public void setName(String mountainName)
{
name = mountainName;
}
/**
* Accessor method for mountain height.
*/
public double getHeight()
{
// put your code here
return height;
}
/**
* Set the mountain height.
*/
public void setHeight(double newHeight)
{
height = mountainHeight;
}
}
I've attempted to return this value but as you can see it is incomplete.
Thanks in advance!
Change:
//This does absolutely nothing. It takes a parameter and returns it.
public Mountain addMountain(Mountain mountain) {
return mountain;
}
public ArrayList<Mountain> getHighestMountain(Mountain mountainHeight) {
double maxHeight = 1;
int index = 1;
for(int i = 0; i < mountainHeight.length; i++) {
if(mountainHeight[i].getHeight()>maxHeight) {
index = i;
}
}
}
To:
//This adds the parameter mountain to the list of mountains for this climber
public void addMountain(Mountain mountain) {
mountains.add(mountain);
}
//This loops over the mountains for this climber and returns the highest one.
public Mountain getHighestMountain() {
Mountain highestMountain = null;
for(int i = 0; i < mountains.size(); i++) {
if (highestMountain == null) {
highestMountain = mountains.get(i);
}
else if(mountains.get(i).getHeight() > highestMountain.getHeight()) {
highestMountain = mounts.get(i);
}
}
return highestMountain;
}
Your code example shows a lack of understanding of basic concepts such as fields and variables, parameters and return types and so on. I would suggest reading up on the language basics here.
Comments
Get rid of your comments, they are not adding any value. Don't put or leave default comments in your code, they reduce readability. Clarifications should also be avoided, try to create a separate variable or method with a meaningful name instead. For example:
// check if the user is on the last page
if (!((count - i) < pageSize)) {
addNextButton();
}
Could become:
boolean userIsOnTheLastPage = (count - i) < pageSize;
if (!userIsOnTheLastPage) {
addNextButton();
}
Interfaces
Java collections (like ArrayList) implement interfaces (e.g. List). Usually you will want to declare and pass around the interface, not the implementation you used. Other parts of your code should only care about using the List capabilities, not how they are implemented.
So replace;
private ArrayList<Climber> climbers;
with
private List<Climber> climbers;
Also use the interface as parameter or return type in methods. A result of this approach is that if you want to use a different List implementation later on, you only have to change it in one place in your code. This is a form of decoupling in your code, which is a good thing to have. ('low coupling, high cohesion')
getClimber
Your search implementation in getClimber is a bit convoluted. I'd do it like this:
for (Climber climber : climbers) {
if(climber.getName().equals(name)) {
return climber;
}
}
System.out.println(name + " not found");
I understand you might be trying to avoid returning from an iteration but in my opinion the code gets much less readable as a result.
BTW your code would print 'not found' for every non-matching climber. I assumed that's not what you want so my code only prints it once if no matching climber is found.
displayClimberList
public void displayClimbers() {
for (Climber climber : climbers) {
System.out.println(climber);
}
}
System.out.println calls toString() on its argument, so we can leave the formating of the display String to the Climber class:
public class Climber {
private String name;
private int age;
private String gender;
// ...
public String toString() {
return String.format("%s %s %s", name, age, gender);
}
Don't mention types in your variable/method names if not needed. The fact that a List is returned is already apparent from the method signature.
foreach is more readable an iteration with an index so use it when you can. Sometimes you cannot avoid using an index, for example when iterating through two lists simultaneously but that's not the case here.
printf (or String.format) makes formatting an output string much easier than String concatenation. See the documentation of java.util.Formatter
Gender
I would change the gender field from String to enum:
public class Climber {
private Gender gender;
The enum could look like this:
public enum Gender {
MALE, FEMALE;
}
(that's a separate top-level class, so in a file called Gender.java). See also the Oracle tutorial about enums.
addMountain
Fix the addMountain method so it actually adds a mountain:
public void addMountain(Mountain mountain) {
mountains.add(mountain);
}
getHighestMountain
The logic of getHighestMountain is flawed (this was your question), try this:
public List<Mountain> getHighestMountain() {
Mountain highest = null;
for(Mountain mountain : mountains) {
if(highest == null || mountain.getHeight() > highest.getHeight()) {
highest = mountain;
}
}
return highest;
}
This is how my class definition goes:
public class ArrayBag implements Bag {
/**
* The array used to store the items in the bag.
*/
private Object[] items;
/**
* The number of items in the bag.
*/
private int numItems;
... and so on...
This is a method in the class definition, where a new object of this class is created inside the method:
//creates and returns an Arraybag that is the union of the called Arraybag and the parameter bag
public bag unionWith(Bag other) {
if (other == null)
throw new IllegalArgumentException();
int cap = this.capacity() + other.capacity();
//new object created here
ArrayBag newbag = new ArrayBag(cap);
for (int i = 0; i < numItems; i++) {
if (other.contains(items[i]))
newbag.add(items[i]);
}
for (int i = 0; i < newbag.numItems(); i++)
//Can I use "newbag.items[i]"?
if (numOccur(newbag.items[i]))
}
My question is, can I access the Object[] items of the newbag object from inside this method definition? Like this: newbag.items[i]
You can access it.
It is feasable to do:
public class AClass {
private int privateInteger;
public AClass() {
privateInteger = 5;
}
// First way
public void accessFromLocalInstance() {
AClass localInstanceOfClass = new AClass()
int valueOfLocalInstance = localInstanceOfClass.privateInteger;
}
// Second way
public void accessFromInstance(AClass instance) {
int valueOfInstance = instance.privateInteger;
}
}
because
"private" means restricted to this class, not restricted to this object.
See Access private field of another object in same class
I am relatively new to Java Generics and the following two Generic classes represent the Vertex and the Connector involved in Graph data structure.
Connector.java class
package ac.lk.iit.algorithmscomplexities.coursework2.datastructure;
public class Connector<E,F> {
private Vertex<E, F> start, end; // starting Vertex instance and ending Vertex instance of the Connector
private F element; // the data of generic type F to be held by the Vertex connector
private double value; // a descriptive value of the connector relative to other connectors depending on the scenario
/**
* a protected constructor which creates an instance of Connector class
* #param start starting Vertex instance of the Connector
* #param end ending Vertex instance of the Connector
* #param element data of generic type F to be held by the Vertex connector
* #param cost descriptive value of the connector relative to other connectors depending on the scenario
*/
protected Connector(Vertex<E,F> start, Vertex<E,F> end, F element, double cost) {
this.setStart(start);
this.setEnd(end);
this.setElement(element);
this.setValue(cost);
}
/**
* returns the starting Vertex instance of the Connector
* #return the starting Vertex instance of the Connector
*/
protected Vertex<E,F> getStart() {
return start;
}
/**
* sets the Vertex argument provided to the starting Vertex instance field of the Connector instance
* #param start the starting Vertex instance of the Connector
*/
private void setStart(Vertex<E,F> start) {
if(start != null) {
this.start = start;
}
}
/**
* returns the ending Vertex instance of the Connector
* #return the ending Vertex instance of the Connector
*/
protected Vertex<E,F> getEnd() {
return end;
}
/**
* sets the Vertex argument provided to the ending Vertex instance field of the Connector instance
* #param end the ending Vertex instance of the Connector
*/
private void setEnd(Vertex<E,F> end) {
if(end != null) {
this.end = end;
}
}
/**
* returns the data of generic type F held by the Vertex connector
* #return the data of generic type F held by the Vertex connector
*/
protected F getElement() {
return element;
}
/**
* sets the data of generic type F to the element instance field of the Vertex connector
* #param element data of generic type F to be held by the Vertex connector
*/
private void setElement(F element) {
if(element != null) {
this.element = element;
}
}
/**
* returns a descriptive value of the connector relative to other connectors depending on the scenario
* #return descriptive value of the connector relative to other connectors depending on the scenario
*/
protected double getValue() {
return value;
}
/**
* sets a descriptive value of the connector relative to other connectors depending on the scenario to value instance field of Connector instance
* #param value a descriptive value of the connector relative to other connectors depending on the scenario
*/
private void setValue(double value) {
if(value >= 0) {
this.value = value;
}
}
public String toString() {
return this.element.toString();
}
#SuppressWarnings("unchecked")
#Override
public boolean equals(Object object) {
if(object instanceof Connector) {
Connector<E,F> newConnector = (Connector<E, F>)object;
// since it is a directed graph the start, end of each connector and the data element should be unique
return ((this.getStart().equals(newConnector.getStart())) && (this.getEnd().equals(newConnector.getEnd())) && (this.getElement().equals(newConnector.getElement())));
}
else {
return false;
}
}
}
Vertex.java class
package ac.lk.iit.algorithmscomplexities.coursework2.datastructure;
import java.util.LinkedList;
public class Vertex<E,F> {
private int id; // a unique id value for each vertex created
private E dataElement; // data to be held within a vertex
private LinkedList<Connector<E, F>> pointers; // list of references to other Vertices connected
// keeps track of the number of vertices created during the runtime
protected static int NUMBER_OF_VERTICES = 0;
/**
* a protected constructor which creates an instance of Vertex class with the generic E argument provided
* #param element the element of generic type E to be assigned to dataElement instance field
*/
protected Vertex(E element) {
this.setId(Vertex.NUMBER_OF_VERTICES);
Vertex.NUMBER_OF_VERTICES++;
this.setDataElement(element);
this.pointers = new LinkedList<Connector<E, F>>();
}
/**
* returns the unique Integer id value of the Vertex instance
* #return the unique Integer id value of the Vertex instance
*/
protected int getId() {
return id;
}
/**
* sets the Integer argument provided to the id instance field of the Vertex instance
* #param id the Integer argument provided to be set to the id instance field of the Vertex instance
*/
private void setId(int id) {
if(!(id < 0)) {
this.id = id;
}
}
/**
* returns the content of the dataElement instance field of the Vertex instance
* #return the content of the dataElement instance field of the Vertex instance
*/
protected E getDataElement() {
return dataElement;
}
/**
* sets the argument of generic type E to the dataElement instance field of the Vertex instance
* #param dataElement the element of generic type E to be assigned to dataElement instance field
*/
protected void setDataElement(E dataElement) {
if(dataElement != null) {
this.dataElement = dataElement;
}
}
/**
* returns the list of Connector instances associated with a Vertex instance
* #return the list of Connector instances associated with a Vertex instance
*/
protected LinkedList<Connector<E, F>> getPointers() {
return pointers;
}
/**
* adds a new Connector instance starting from this Vertex and ending in the specified Vertex instance
* #param another the ending Vertex of the Connector
* #param element the data element of generic type F held by the Connector
* #param value the list of Connector instances associated with a Vertex instance
*/
protected void connectTo(Vertex<E,F> another, F element, double value) {
Connector<E,F> newConnector = new Connector<E,F>(this, another, element, value);
if(!(this.pointers.contains(newConnector))) {
this.pointers.add(newConnector);
}
LinkedList<Connector<E, F>> anotherList = another.getPointers();
if(!(anotherList.contains(newConnector))) {
anotherList.add(newConnector);
}
System.out.println("[this vertex]:" + this.pointers);
System.out.println("[that vertex]:" + another.pointers);
}
#SuppressWarnings("unchecked")
#Override
public boolean equals(Object object) {
if(object instanceof Vertex) {
Vertex<E,F> newVertex = (Vertex<E,F>) object;
if(this.pointers.size() != newVertex.getPointers().size()) {
return false;
}
if(!(this.getDataElement().equals(newVertex.getDataElement()))) {
return false;
}
for(int i = 0 ; i < this.pointers.size() ; i++) {
if(!(this.pointers.get(i).equals(newVertex.getPointers().get(i)))) {
return false;
}
}
}
else {
return false;
}
return true;
}
public static void main(String[] args) {
Vertex<String, String> vertex1 = new Vertex<String, String>("Chiranga");
Vertex<String, String> vertex2 = new Vertex<String, String>("Robin");
Vertex<String, String> vertex3 = new Vertex<String, String>("Sunethra");
Vertex<String, String> vertex4 = new Vertex<String, String>("Ananda");
vertex1.connectTo(vertex2, "John", 0);
//vertex1.connectTo(vertex3, "Mark", 0);
//vertex1.connectTo(vertex4, "Rob", 0);
vertex2.connectTo(vertex3, "James", 0);
vertex4.connectTo(vertex2, "John", 0);
vertex4.connectTo(vertex3, "Sean", 0);
//System.out.println(vertex1.equals(vertex4));
//System.out.println(vertex1.equals(vertex2));
}
}
The above classes give out a stackoverflowexception when executing the following code segment.
vertex4.connectTo(vertex3, "Sean", 0);
It is hardly possible to understand the real reason behind the above exception as I am not involving any recursive code sample and because it occurs when I make connections between certain Vertex instances only. Most of the code problems related to the above exception type speaks about recursion but the above one seems to be different.
Why do I always get the mentioned stackoverflow exception?
Please note that the above code sample involving main method was coded for testing purposes.
Your equals method...
When you do the contains method in connectTo it calls equals on the Connectors to ascertain whether the connector is in the list or not.
The equals method for Connector:
#SuppressWarnings("unchecked")
#Override
public boolean equals(Object object) {
if (object instanceof Connector) {
Connector<E, F> newConnector = (Connector<E, F>) object;
// since it is a directed graph the start, end of each connector and the data element should be unique
return ((this.getStart().equals(newConnector.getStart())) && (this.getEnd().equals(newConnector.getEnd())) && (this.getElement().equals(newConnector.getElement())));
} else {
return false;
}
}
Notice how it does equals comparision on getStart() - which is a Vertex. Then Vertex equals:
#SuppressWarnings("unchecked")
#Override
public boolean equals(Object object) {
if (object instanceof Vertex) {
Vertex<E, F> newVertex = (Vertex<E, F>) object;
if (this.pointers.size() != newVertex.getPointers().size()) {
return false;
}
if (!(this.getDataElement().equals(newVertex.getDataElement()))) {
return false;
}
for (int i = 0; i < this.pointers.size(); i++) {
if (!(this.pointers.get(i).equals(newVertex.getPointers().get(i)))) {
return false;
}
}
} else {
return false;
}
return true;
}
So Vertex Equals: calls equals on this.pointers (Connectors).
So in other words your equals methods have a cyclic dependency - they each call the other equals method and thus you get a stack overflow exception.
Looking at the stack trace, it's easy to understand where the problem is:
Exception in thread "main" java.lang.StackOverflowError
at ac.lk.iit.algorithmscomplexities.coursework2.datastructure.Connector.equals(Connector.java:123)
at ac.lk.iit.algorithmscomplexities.coursework2.datastructure.Vertex.equals(Vertex.java:117)
at ac.lk.iit.algorithmscomplexities.coursework2.datastructure.Connector.equals(Connector.java:123)
at ac.lk.iit.algorithmscomplexities.coursework2.datastructure.Vertex.equals(Vertex.java:117)
at ac.lk.iit.algorithmscomplexities.coursework2.datastructure.Connector.equals(Connector.java:123)
...
Your Connector equals method calls Vertex equals method, which calls Connector's equals methods,...
equals method of Vertex class contains :
if(!(this.pointers.get(i).equals(newVertex.getPointers().get(i))))
where this.pointers.get(i) is a Connector, so equals of Vertex calls equals of Connector.
equals of the Connector class contains :
return ((this.getStart().equals(newConnector.getStart())) && (this.getEnd().equals(newConnector.getEnd())) && (this.getElement().equals(newConnector.getElement())));
And since getStart() and getEnd() are of type Vertex, this means equals of Connector is calling equals of Vertex.
Therefore a call to equals of either Vertex or Connector may lead to infinite recursion.
This question already has answers here:
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException
(2 answers)
Closed 5 years ago.
I was trying to figure out why my code is throwing a "java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: core.FlightOrder$FlightTicket" exception.
I have class declared as:
public class FlightOrder implements Serializable
that contains a private Set of FlightTickets.
And an inner class declared as:
public class FlightTicket
The solution i read about, was to make the inner class "FlightTicket" a static class, but im not sure thats what I'm supose to do to make my code work properly.
can someone help me figure out whats the right way to approach this problem?
public class FlightTicket
{
/**
* The passenger for this ticket
*/
private Customer passenger;
/**
* passenger's seat
*/
private int seat;
/**
* passenger's row
*/
private int row;
/**
* The passenger's class in the plane
*/
private E_ClassType classType;
/**
* Full constructor
* #param passenger
* #param seat
* #param row
* #param classType
*/
public FlightTicket(Customer passenger, int seat, int row , String classType)
{
this.passenger = passenger;
setSeat(seat);
setRow(row);
setClassType(classType);
}
/**
* Partial constructor
* #param seat
* #param row
*/
public FlightTicket(int seat, int row)
{
setSeat(seat);
setRow(row);
}
//-------------------------------Getters And Setters------------------------------
/**
* seat has to be positive number
* #param seat
*/
public void setSeat(int seat) {
if(seat>0)
this.seat = seat;
}
/**
* row has to be positive number
* #param row
*/
public void setRow(int row) {
if(row>0)
this.row = row;
}
/**
*
* #return classType
*/
public E_ClassType getClassType() {
return classType;
}
public int getSeat(){
return seat;
}
public int getRow(){
return row;
}
/**
* set the class type from the array classType located in Constants.
* if the classType not exists, the default value is Economy.
* #param classType
*/
public void setClassType(String classType) {
for(E_ClassType c : E_ClassType.values())
{
if(c.toString().equals(classType))
{
this.classType = E_ClassType.valueOf(classType);
return;
}
}
this.classType = E_ClassType.Tourists;
}
public FlightOrder getOuterType() {
return FlightOrder.this;
}
/* (non-Javadoc)
* #see java.lang.Object#hashCode()
*/
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + row;
result = prime * result + seat;
return result;
}
/* (non-Javadoc)
* #see java.lang.Object#equals(java.lang.Object)
*/
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
FlightTicket other = (FlightTicket) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (row != other.row)
return false;
if (seat != other.seat)
return false;
return true;
}
/* (non-Javadoc)
* #see java.lang.Object#toString()
*/
#Override
public String toString() {
return "FlightTicket [passenger=" + passenger + ", seat=" + seat
+ ", row=" + row + ", flight number=" + getFlight().getFlightNumber() + "]";
}
}// ~ END OF Inner Class FlightTicket
Making inner class Serializable will work and this is exactly that you are supposed to do if you want to serialize it together with the outer class. The following code demonstrates:
public class Outer implements Serializable {
class Inner implements Serializable {
int value = 17;
}
Inner inner = new Inner();
public static void main(String[] args) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(out);
Outer obj = new Outer();
obj.inner.value = 22;
oout.writeObject(obj);
Outer r = (Outer) new ObjectInputStream(new ByteArrayInputStream(
out.toByteArray())).readObject();
System.out.println(r.inner.value);
}
}
The output is 22, the value that has been correctly serialized and deserialized from the inner class field.
You just need to implement Serializable interface by all the classes which will be serialized. I mean all classes of instance variables declared in your serializable class should also implement Serializable.
In your case FlightTicket and Customer need implement Serializable.
I am using a CellTable with also DatePickerCell and I'd like to be able to display also dates that are not set, e.g. "empty" date. But in implementing getValue method I have to return something:
Column<EventProxy, Date> startTimeColumn = new Column<EventProxy, Date>(
new DatePickerCell()) {
#Override
public Date getValue(EventProxy object) {
Date ret = object.getStartTime();
return ret != null ? ret : new Date();
}
};
If object.getStartTime() is null, it means that it is not set and I want to display it as such. Or with empty label or with text "empty". As I've said, method getValue has to return something. If I return null, I get exceptions later, if I return concrete date it displays it as valid date. Is there any other option? Some special date tag or object that DatePickerCell would recognize as empty or unset value?
You are wondering about displaying dates, right? Then shift your focus away from getValue() and look at overriding the render() method (found in the Column class) instead. The render() method has an object parameter, just like getValue(), and a parameter for a SafeHtmlBuilder to which you append your representation of the object's value. Test getStartTime(), and if it is null, append "[unset]" (or whatever) to that SafeHtmlBuilder. You could even append an image of a clock face with a red strikeout running through it (using the HTML img tag), or anything you care to, since render is just appending HTML that will be put into the cell.
Not directly related, but perhaps useful in another context for you, is a new class delivered in Release 10 of Guava. It is called Optional. It is a generic that wraps around the class you're using, in this case, Date. It provides a way to explicitly distinguish between a null value, and unset value, and so on, using the methods provided. Give it a quick read -- since you are dealing with empty dates, this might be useful elsewhere in your design.
DatePickerCell does not support null values. Overriding render is not enough as NPE is thrown from method onEnterKeyDown when you click on the rendered cell.
You have to implement your own cell with null protection in "onEnterKeyDown" :
if (date != null) {
datePicker.setCurrentMonth(date);
}
Complete class :
public class DatePickerCell extends AbstractEditableCell<Date, Date> {
private static final int ESCAPE = 27;
private final DatePicker datePicker;
private final DateTimeFormat format;
private int offsetX = 10;
private int offsetY = 10;
private Object lastKey;
private Element lastParent;
private int lastIndex;
private int lastColumn;
private Date lastValue;
private PopupPanel panel;
private final SafeHtmlRenderer<String> renderer;
private ValueUpdater<Date> valueUpdater;
/**
* Constructs a new DatePickerCell that uses the date/time format given by
* {#link DateTimeFormat#getFullDateFormat}.
*/
#SuppressWarnings("deprecation")
public DatePickerCell() {
this(DateTimeFormat.getFullDateFormat(), SimpleSafeHtmlRenderer.getInstance());
}
/**
* Constructs a new DatePickerCell that uses the given date/time format and
* a {#link SimpleSafeHtmlRenderer}.
* #param format a {#link DateTimeFormat} instance
*/
public DatePickerCell(DateTimeFormat format) {
this(format, SimpleSafeHtmlRenderer.getInstance());
}
/**
* Constructs a new DatePickerCell that uses the date/time format given by
* {#link DateTimeFormat#getFullDateFormat} and the given
* {#link SafeHtmlRenderer}.
* #param renderer a {#link SafeHtmlRenderer SafeHtmlRenderer<String>}
* instance
*/
public DatePickerCell(SafeHtmlRenderer<String> renderer) {
this(DateTimeFormat.getFormat(PredefinedFormat.DATE_FULL), renderer);
}
/**
* Constructs a new DatePickerCell that uses the given date/time format and
* {#link SafeHtmlRenderer}.
* #param format a {#link DateTimeFormat} instance
* #param renderer a {#link SafeHtmlRenderer SafeHtmlRenderer<String>}
* instance
*/
public DatePickerCell(DateTimeFormat format, SafeHtmlRenderer<String> renderer) {
super(CLICK, KEYDOWN);
if (format == null) {
throw new IllegalArgumentException("format == null");
}
if (renderer == null) {
throw new IllegalArgumentException("renderer == null");
}
this.format = format;
this.renderer = renderer;
this.datePicker = new DatePicker();
this.panel = new PopupPanel(true, true) {
#Override
protected void onPreviewNativeEvent(NativePreviewEvent event) {
if (Event.ONKEYUP == event.getTypeInt()) {
if (event.getNativeEvent().getKeyCode() == ESCAPE) {
// Dismiss when escape is pressed
panel.hide();
}
}
}
};
panel.addCloseHandler(new CloseHandler<PopupPanel>() {
public void onClose(CloseEvent<PopupPanel> event) {
lastKey = null;
lastValue = null;
lastIndex = -1;
lastColumn = -1;
if (lastParent != null && !event.isAutoClosed()) {
// Refocus on the containing cell after the user selects a
// value, but
// not if the popup is auto closed.
lastParent.focus();
}
lastParent = null;
}
});
panel.add(datePicker);
// Hide the panel and call valueUpdater.update when a date is selected
datePicker.addValueChangeHandler(new ValueChangeHandler<Date>() {
public void onValueChange(ValueChangeEvent<Date> event) {
// Remember the values before hiding the popup.
Element cellParent = lastParent;
Date oldValue = lastValue;
Object key = lastKey;
int index = lastIndex;
int column = lastColumn;
panel.hide();
// Update the cell and value updater.
Date date = event.getValue();
setViewData(key, date);
setValue(new Context(index, column, key), cellParent, oldValue);
if (valueUpdater != null) {
valueUpdater.update(date);
}
}
});
}
#Override
public boolean isEditing(Context context, Element parent, Date value) {
return lastKey != null && lastKey.equals(context.getKey());
}
#Override
public void onBrowserEvent(Context context, Element parent, Date value, NativeEvent event, ValueUpdater<Date> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if (CLICK.equals(event.getType())) {
onEnterKeyDown(context, parent, value, event, valueUpdater);
}
}
#Override
public void render(Context context, Date value, SafeHtmlBuilder sb) {
// Get the view data.
Object key = context.getKey();
Date viewData = getViewData(key);
if (viewData != null && viewData.equals(value)) {
clearViewData(key);
viewData = null;
}
String s = null;
if (viewData != null) {
s = format.format(viewData);
} else if (value != null) {
s = format.format(value);
}
if (s != null) {
sb.append(renderer.render(s));
}
}
#Override
protected void onEnterKeyDown(Context context, Element parent, Date value, NativeEvent event, ValueUpdater<Date> valueUpdater) {
this.lastKey = context.getKey();
this.lastParent = parent;
this.lastValue = value;
this.lastIndex = context.getIndex();
this.lastColumn = context.getColumn();
this.valueUpdater = valueUpdater;
Date viewData = getViewData(lastKey);
Date date = (viewData == null) ? lastValue : viewData;
if (date != null) {
datePicker.setCurrentMonth(date);
}
datePicker.setValue(date);
panel.setPopupPositionAndShow(new PositionCallback() {
public void setPosition(int offsetWidth, int offsetHeight) {
panel.setPopupPosition(lastParent.getAbsoluteLeft() + offsetX, lastParent.getAbsoluteTop() + offsetY);
}
});
}
}