so when i clicking the Messages tabPane containing the Jtree, this is the preview in my java swing which seems fine.
pict 1 (loading the message)
pict 2. (done)
when i click any of the checkboxes in the JTree it should be either loading(checking) or unloading(unchecking) the messages in the message list with the swingworker running to see the progress. But what happen is after i click the checkboxes (of any condition), yes the swingworker running and giving the loading/unloading progress, but after that, i get this:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException:
Cannot invoke "model.Message.getContents()" because "message" is null
and make the message lists is unclickable, which were clickable before i attempted to click the checkboxes in the JTree.
at the moment i dont need JTree in my purpose for learning swing, so I'm not really taking into account about this JTree lesson, but i need this to be fixed so i can keep go along with the tutorial. that's why i'm not quite sure which code are problematic and needed to put in this thread. So i'm very sorry if my question is not clear. if there still anything i have to put at this thread, please ask me i'll be happy to put it here.
this the class that mentioned in exception
public class MessagePanel extends JPanel implements ProgressDialogListener{
public MessagePanel(JFrame parent) {
messageListModel = new DefaultListModel();
messageList = new JList(messageListModel);
messageList.setCellRenderer(new MessageListRenderer());
messageList.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
Message message = (Message)messageList.getSelectedValue();
textPanel.setText(message.getContents());
}
});
}
this is the class and method that related with the above class
public class MessageListRenderer implements ListCellRenderer {
private JPanel panel;
private JLabel label;
private Color selectedColor,normalColor;
public MessageListRenderer() {
//some ui settings
}
#Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
Message message = (Message)value;
label.setText(message.getTitle());
panel.setBackground(cellHasFocus ? selectedColor: normalColor);
return panel;
}
}
===================
public class TextPanel extends JPanel{
public void setText(String text) {
textArea.setText(text);
}
}
===================
public class Message {
private String title,contents;
public Message(String title, String contents) {
super();
this.title = title;
this.contents = contents;
}
public String getTitle() {return title;}
public void setTitle(String title) {this.title = title;}
public String getContents() {return contents;}
public void setContents(String contents) {this.contents = contents;}
}
Your Message class constructor requires two parameters (of: String, String) in order to create an instance of Message. I have no clue what you are currently using to create you Message instances nor do I know what is storing those instances. You do need to keep track of them otherwise you will loose them to JVM Garbage Collection.
I think perhaps you may want to modify your Message Class a little so that you can internally (or externally) store your Message instances and easily access any one of those instances when required, for example:
public class Message {
// A List Interface object to hold Message instances.
private static java.util.List<Message> messageInstances = new java.util.ArrayList<>();
// The OS System's New-Line character to use for console writing.
private final static String ls = System.lineSeparator();
// Instance member variables
private String title;
private String contents;
/**
* Constructor #1
* Does Nothing but adds the instance to the messageInstances List!
* Relies on Setters to fill instance member variables.
*/
public Message() {
messageInstances.add((this));
}
/**
* Constructor #2
* Contains parameters of which the arguments will fill instance member
* variables listed within the Parameters list below.
*
* #param title (String) The Message Title.<br>
*
* #param contents (String) The message content related to the above title.
*/
public Message(String title, String contents) {
super();
this.title = title;
this.contents = contents;
messageInstances.add((this));
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContents() {
return contents;
}
public void setContents(String contents) {
this.contents = contents;
}
public static java.util.List<Message> getMessageInstances() {
return messageInstances;
}
/**
* Removes one (or more) Message instances from the messageInstances List.
* This method must be supplied at least one integer index value of the
* Message instance to remove otherwise a Warning is displayed within the
* console window. Several index values can be supplied providing they are
* delimited with a comma or all desired Message Instance index values to
* remove are supplied within a Single Dimensional int[] Array.<br><br>
*
* <b>Valid uses of this class method:</b><pre>
*
* removeMessageInstance(0, 4, 2, 16);
*
* OR
*
* int[] indexes = {0, 4, 2, 16};
* removeMessageInstance(indexes);</pre>
*
* #param instanceIndexes
*/
public static void removeMessageInstance(int... instanceIndexes) {
int[] iIndex = null;
if (instanceIndexes.length == 0) {
System.err.println("Message.removeMessageInstance() method Warning!" + ls
+ "Require an index value of the Message Instance to remove!" + ls
+ "Ignoring Removal call!" );
return;
}
iIndex = new int[instanceIndexes.length];
System.arraycopy(instanceIndexes, 0, iIndex, 0, instanceIndexes.length);
for (int i = 0; i < iIndex.length; i++) {
if(iIndex[i] < 0 || iIndex[i] > messageInstances.size()) {
System.err.println("Message.removeMessageInstance() method Warning!" + ls
+ "The supplied Message Instance index value (" + iIndex[i] + ") is invalid!" + ls
+ "Ignoring Removal call for Message Instance at Index " + iIndex[i] + "!");
continue;
}
messageInstances.remove(iIndex[i]);
}
}
#Override
public String toString() {
return new StringBuilder("").append(title).append(" | ")
.append(contents).toString();
}
}
Do whatever it is you do to create Message instances.
Now, in your MessagePanel class within the ListSelectionListener:
public void valueChanged(ListSelectionEvent e) {
String title = messageList.getSelectedValue().toString(); // toString() may not be required.
List<Message> messages = Message.getMessageInstances();
for (Message msg : messages) {
if (msg.getTitle().equalsIgnoreCase(title)) {
textPanel.setText(msg.getContents());
break;
}
}
}
Related
I made a post earlier about a similar topic. However, I thought I would clarify something and change my question.
So this project I am doing is confusing me. I am only 5 weeks in and the question is asking me to create a method that returns a title of a photo in an array of photos. each photo has a title. This is the code:
public class Album {
private String albumtitle;
private ArrayList<Photo> photos;
/**
* This constructor should initialize the
* instance variables of the class.
*/
public Album(String title) {
this.albumtitle = title;
photos = new ArrayList<>();
}
/** When passed a title, this method should
* return the first Photo object in the album with
* a matching title. If there is no such object, it
* should return null.
*
* #param title A title to search for
* #return A Photo object, or null
*/
public Photo searchByTitle(String title) {
//TODO enter code here
}
}
Now, my lecturer said not to use for loops as the project is from chapter 1 to 4 (chapter 5 is for loops/iterations)
https://lms.uwa.edu.au/bbcswebdav/pid-1134902-dt-content-rid-16529804_1/courses/CITS1001_SEM-2_2018/lectures/BooksReadJournal.java.pdf
This is an example of what the lecturer did with a program about books without using for loops. However, notice it has (int index) as a parameter and then uses String title = bookTitles.get(index)
My point is, how do I do it without using for loop? I don't want them to feel as I have copied off the internet something we haven't learned.
Thanks,
If you are limited to avoid use the for-loop and use the if-else only, the recursive call is an alternative:
public Photo searchByTitle(String title) {
return searchByIndex(title, 0);
}
private Photo searchByIndex(String title, int index) {
if (index < photos.size()) { // Has next? If yes ...
Photo next = photos.get(index); // Get next
if (!title.equals(next.getPhotoName())) { // Does the title match? If not...
return searchByIndex(title, ++index); // Check the next one
} else return next; // Otherwise you found it
} return null; // ... if no next, return null
}
I assume the Photo class has a field String photoName accessible with a getter which is about to be compared with the String title.
Implement Comparable on Photo that returns true if the title is the same.
Construct a temporary Photo object with the given type.
Leverage the indexOf method on ArrayList to find index of Album with the Photo title.
Use get(int) to get the Album.
I've implemented the code from the comments above.
The idea here is to build a temporary object in the searchByTitle method and passing it to the List.indexOf method having the Photo class that overrides Object.equals.
public class Album {
class Photo {
private String title;
public Photo(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
#Override
public boolean equals(Object anObject) {
return title.equals(((Photo)anObject).getTitle());
}
}
private String albumtitle;
private ArrayList<Photo> photos;
/**
* This constructor should initialize the
* instance variables of the class.
*/
public Album(String title) {
this.albumtitle = title;
photos = new ArrayList<>();
}
/** When passed a title, this method should
* return the first Photo object in the album with
* a matching title. If there is no such object, it
* should return null.
*
* #param title A title to search for
* #return A Photo object, or null
*/
public Photo searchByTitle(String title) {
Photo tmp = new Photo(title);
int index = photos.indexOf(tmp);
if (index >= 0)
return photos.get(index);
return null;
}
}
This is a very basical implementation of equals which doesn't take into account null argument and its type.
You can bring another ArrayList of String which keeps names of photo titles. Then in search function search with photo title in String arrayList. If find an index then return the Photo object of that index from photoTitles, as you are inserting in both arrayList in same order.
public class Album {
private String albumtitle;
private ArrayList<Photo> photos;
private ArrayList<String> photoTitles;
public Album(String title) {
this.albumtitle = title;
photos = new ArrayList<>();
photoTitles = new ArrayList<>();
}
public Photo searchByTitle(String title) {
int index = photoTitles.indexOf(title);
if(index >= 0) {
return photos.get(index);
}
return null;
}
}
Just because I like the recursion idea but using it with an index search on a List is not optimal, let's use an iterator instead. The recursive method will simply check if there is one value to take, check and call again until we find the value or reach the end.
//Just hide the used of an iterator
public static Photo getFirstWithTitle(List<Photo> list, String value){
return getFirstWithTitle(list.iterator(), value);
}
//recursive method
private static Photo getFirstWithTitle(Iterator<Photo> it, String value){
if(it.hasNext()){
Photo p = it.next();
return p.getTitle().equals(value)?
p
: getFirstWithTitle(it, value);
} else
return null;
}
You can use built in binarySearch and a comparator. Probably the most elegant way and how I usually do it
public Photo searchByTitle(String title) {
Photo item = null
Comparator<Photo> comparator = new Comparator<Photo>() {
public int compare(Photo it1, Photo it2) {
return it1.getTitle().compareTo(it2.getTitle());
}
};
Collections.sort(photos, comparator); //This should go after you set the items, ie. you sort it once
int index = Collections.binarySearch(photos, comparator);
if (index >= 0) {
item = photos.get(index);
}
return item
}
Using Java you don't even need a 'if' statement. This can be achieved through this:
public Photo searchByTitle(String title) {
return photos.stream().filter(photo -> title.
equals(photo.getTitle())).findAny().get();
}
p.s: I did not have access to your enunciate question (the link provided)
I am making a program that returns the output in a long string variable. The data in the string is constantly changing based on what the user enters in the GUI. My question is, how do I take this and store it inside of my linked list? I have looked at a few examples, but the class I was provided with for my class is a little different, and I haven't been able to find something to specifically fix my problem.
Controller Code:
public class RentGameDialogController extends RentalStoreGUIController implements Initializable{
/** TextField Objects **/
#FXML private TextField nameField, rentedOnField, dueBackField;
/** String for NameField **/
String name, rentedOn, dueBack;
/** Game ComboBox ID's **/
#FXML private ObservableList<GameType> cbGameOptions;
#FXML private ComboBox<GameType> cbGame;
/** Console ComboBox ID's **/
#FXML private ObservableList<PlayerType> cbConsoleOptions;
#FXML private ComboBox<PlayerType> cbConsole;
/** GameType object **/
private GameType game;
/** PlayerType Object **/
private PlayerType console;
/** Button ID's **/
#FXML Button cancel, addToCart;
/** Counter for calculating total **/
int gameCounter;
/** Stage for closing GUI **/
private Stage currentStage;
private MyLinkedList list = new MyLinkedList();
#Override
public void initialize(URL location, ResourceBundle resources) {
/** Select Console **/
cbConsoleOptions = FXCollections.observableArrayList();
for (PlayerType p : PlayerType.values()) { cbConsoleOptions.addAll(p); }
cbConsole.getItems().addAll(cbConsoleOptions);
/** Select Game **/
cbGameOptions = FXCollections.observableArrayList();
for (GameType g : GameType.values()){ cbGameOptions.addAll(g); }
cbGame.getItems().addAll(cbGameOptions);
}
public String getName(){
name = nameField.getText();
try {
String[] firstLast = name.split(" ");
String firstName = firstLast[0];
String lastName = firstLast[1];
} catch (Exception e){
e.printStackTrace();
}
return name;
}
public String getGame() {
return cbGame.getSelectionModel().getSelectedItem().toString();
}
public String getConsole() {
return cbConsole.getSelectionModel().getSelectedItem().toString();
}
public String getRentedOn() throws ParseException {
rentedOn = rentedOnField.getText();
DateFormat format = new SimpleDateFormat("dd/MM/yyyy");
Date rentedOnDate = format.parse(rentedOn);
Calendar cal = Calendar.getInstance();
cal.setLenient(false);
cal.setTime(rentedOnDate);
try {
cal.getTime();
} catch (Exception e) {
rentedOnField.setText("ERROR");
}
return rentedOn;
}
public String getDueBack() throws ParseException {
dueBack = dueBackField.getText();
DateFormat format = new SimpleDateFormat("dd/MM/yyyy");
Date dueBackDate = format.parse(dueBack);
Calendar cal = Calendar.getInstance();
cal.setLenient(false);
cal.setTime(dueBackDate);
try {
cal.getTime();
} catch (Exception e) {
dueBackField.setText("ERROR");
}
return dueBack;
}
/*************************************
* This is the method to call the other
* String methods so their output can be
* put into my main GUI
*
*
* #return
* #throws ParseException
*************************************/
public String storePurchaseData() throws ParseException {
gameCounter++;
String toList = getName() + " | " + getGame() + " | " + getConsole() + " | " +
getRentedOn() + " | " + getDueBack();
//Add 'toList' to the linked list here if possible
return toList;
}
#FXML
public void handleCancelButtonAction () {
currentStage = (Stage) cancel.getScene().getWindow();
currentStage.close();
}
#FXML
public void addToCartButton () throws ParseException {
appendTextArea(storePurchaseData());
currentStage = (Stage) cancel.getScene().getWindow();
currentStage.close();
}}
This code is for my controller. It launches a basic GUI, then I can pull the data from all of the fields I made, convert them to Strings and can then print them in one long chain of text. I would like to store the string into my linked list class.
Linked List code:
public class MyLinkedList<E> implements Serializable {
private DNode<E> top;
public int size;
public MyLinkedList() {
top = null;
size = 0;
}
}
I am very new to linked lists and I am trying to understand them, does this code make sense? Do I need to add anything to, say, save the String that I am storing into a text file?
Thank you in advance
Without getting into your game code at all, it looks like your MyLinkedList class takes a type parameter E - You haven't shown the code for DNode but it also takes the E type. If you can specify this to be a String then the nodes of MyLinkedList can be populated with Strings as you desire.
DNode<String> myFirstNode = new DNode<>(null, null, "nodeData");
MyLinkedList<String> list = new MyLinkedList<>(myFirstNode);
This assumes that the MyLinkedList class also has a constructor that takes a DNode to initialize its head, and that DNode looks something like this.
I'm developing a Java project, a log analyzer, that imports any kind of log informations in a universal way.
Actually, I divided any log sheet in a Log class instance, and any sheet line in a Event class instance.
The problem is: Log read all the sheet, and instance an Event with every read line, putting it in a LinkedHashSet.
When Event gets constructed, it takes - depending on which Log type is it in - all the informations (such as the time the event happened).
Everything works flawlessly, except for getting Date/Time from event string.
In fact, without any kind of error, Log's adding Event cycle, stops.
Deleting every Time-related line, I correctly manage to get everything: with them, it stops, without any error, but making me getting only bunch of lines, instead of the thousands I should get.
Here you can find Log and Event classes, and even the method that gets Time from event and the last line of Event the manage to get Time and the first one that doesn't.
Log:
public class Log {
/** LogType assigned to log file */
private LogType type;
/** filename associated to log file*/
private String name;
/** path associated to log file */
private String path;
private LinkedHashSet<Evento> events;
/**
* Log constructor:
* #param path points to file which create log instance from
* #param type is the LogType type associated with the rising Log
* #param bInitialize indicates if Log has to initialize events list
*/
public Log(String path, LogType type, boolean bInitialize) {
String[] pathComponents = path.split("/");
this.path = path;
this.type = type;
this.name = pathComponents[pathComponents.length - 1];
populateEvents();
}
public LinkedHashSet<Evento> getEvents() {
return events;
}
/** type field getter */
public LogType getType() {
return type;
}
/** name field getter */
public String getName() {
return name;
}
/** path field getter */
public String getPath() {
return path;
}
/** #return path field */
public String toString() {
return path;
}
public TreeSet<Utente> getUsers() {
TreeSet<Utente> users = new TreeSet<Utente>();
for (Evento event : events)
users.add(event.getUser());
return users;
}
private void populateEvents() {
events = new LinkedHashSet<Evento>();
try {
File file = new File(path);
FileInputStream fInputStream = new FileInputStream(file);
byte[] data = new byte[(int) file.length()];
fInputStream.read(data);
fInputStream.close();
String[] eventsRead = new String(data, "UTF-8").split("\n");
for (String event : eventsRead)
events.add(new Evento(event, type));
} catch (Exception e) {
// Nothing really needed.
}
}
/** name field setter */
public void setName(String name) {
this.name = name;
}
/**
* Requests that the file or directory denoted by this abstract
* pathname be deleted when the virtual machine terminates.
*/
public void setToDeleted() {
new File(path).deleteOnExit();
}
}
Event:
public class Evento implements Comparable<Evento> {
private LocalDateTime time;
private LogType type;
private String event;
private Utente user;
public Evento(String event, LogType type) {
this.event = event;
this.type = type;
time = type.getAssociatedLoader().getTimeFromLine(event);
user = type.getAssociatedLoader().getUserFromLine(event);
}
public boolean equals(Evento comparedEvent) {
return event.equals(comparedEvent.getEvent());
}
#Override
public int compareTo(Evento comparedEvent) {
return event.compareTo(comparedEvent.getEvent());
}
public LocalDateTime getTime() {
return time;
}
public String getEvent() {
return event;
}
public Utente getUser() {
return user;
}
#Override
public String toString() {
return event;
}
}
getTimeFromLine() method:
#Override
public LocalDateTime getTimeFromLine(String line) {
String clockString = line.split("\t")[2];
return LocalDateTime.of(Integer.parseInt(clockString.substring(0,4)),
Integer.parseInt(clockString.substring(6,7)),
Integer.parseInt(clockString.substring(9,10)),
Integer.parseInt(clockString.substring(11,13)),
Integer.parseInt(clockString.substring(15,16)),
Integer.parseInt(clockString.substring(17,19)));
}
Lines example (first correctly working, not the latter):
142\twestchester.gov\t2006-03-20 03:55:57\t1\thttp://www.westchestergov.com
142\tspace.comhttp\t2006-03-24 20:51:24\t\t
You should not swallow exceptions - if you had let it propagate you would have got a fairly self explanatory exception message that would have helped you find the problem!
java.time.DateTimeException: Invalid value for DayOfMonth (valid values 1 - 28/31): 0
So one of your clockString.substring() does not use the right indices
Your getTimeFromLine method is unnecessary complicated and I would recommend to use a DateTimeFormatter instead of parsing the string manually
Suggested replacement:
private static final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public LocalDateTime getTimeFromLine(String line) {
String clockString = line.split("\t")[2];
return LocalDateTime.parse(clockString, fmt);
}
i have the following problem: I read out database items in an observable list. Now I want to display some items from the selected line in a few textfields on the right side of my tableview.
I got the observable-line-index with the following code, but I want to select an other column of the line.
AnalysemethodenTable.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Object>() {
public void changed(ObservableValue<?> observable, Object oldvalue, Object newValue) {
index.set(analysemethodendata.indexOf(newValue));
databaseIndex = (analysemethodendata.indexOf(newValue) + 1);
System.out.println("Index:\t" + databaseIndex);
}
});
I found the following code: Click
But i don't understand this. It's something like to write a new list and place a copy of the items of the observable list in this new list.
I think, if I have the index of the line with my code, I can select the other items in the line of the observable list, too (I thought like "x,y" like an array)
If i cast it to String, the output is only machine code.
Hope I can understand the solution with your help!
EDIT: I inserted the following code:
System.out.println(analysemethodendata.get(databaseIndex).toString());
But I only get machine code in my Output:
table.analysemethoden_table#63c0d5b7
EDIT 2:
Table-Controller-Code:
package table;
import javafx.beans.property.SimpleFloatProperty;
import javafx.beans.property.SimpleStringProperty;
public class analysemethoden_table {
private final SimpleStringProperty rAmnorm;
private final SimpleStringProperty rMethverantw;
private final SimpleFloatProperty rBestimmungsgrenze;
private final SimpleFloatProperty rNachweisgrenze;
public analysemethoden_table (String sAmnorm, String sMethoverantw, Float sBestimmungsgrenze, Float sNachweisgrenze) {
this.rAmnorm = new SimpleStringProperty(sAmnorm);
this.rMethverantw = new SimpleStringProperty(sMethoverantw);
this.rBestimmungsgrenze = new SimpleFloatProperty(sBestimmungsgrenze);
this.rNachweisgrenze = new SimpleFloatProperty(sNachweisgrenze);
}
// Getter- und Setter-Methoden
/** rAmnorm **/
public String getRAmnorm() {
return rAmnorm.get();
}
public void setRAmnorm(String set) {
rAmnorm.set(set);
}
/** rMethverantw **/
public String getRMethverantw() {
return rMethverantw.get();
}
public void setRMethverantw(String set) {
rMethverantw.set(set);
}
/** rBestimmungsgrenze **/
public Float getRBestimmungsgrenze() {
return rBestimmungsgrenze.get();
}
public void setRBestimmungsgrenze(Float set) {
rBestimmungsgrenze.set(set);
}
/** rNachweisgrenze **/
public Float getRNachweisgrenze() {
return rNachweisgrenze.get();
}
public void setRNachweisgrenze(Float set) {
rNachweisgrenze.set(set);
}
}
You need to use
analysemethodendata.get(databaseIndex).getRAmnorm();
or any other getter method in place of getRAmnorm() to get the required output.
databaseIndex -> row number
How to get the description text of an eclipse wizard using SWTBot? The wizard.shell.gettext() method gives the title, but I could not find any method for getting the description.I need it to verify the description and the error messages displayed on the wizard page.
as a workaround , I used this code
public void verifyWizardMessage(String message) throws AssertionError{
try{
bot.text(" "+message);
}catch(WidgetNotFoundException e){
throw (new AssertionError("no matching message found"));
}
}
here bot is a SWTBot instance available to method.The wizard messages automatically prepends a space to the description field,so I m using " "+message. Hope it helps
In order to test our eclipse plug-ins, the team I worked with developped a custom DSL on top of SWTBot to represent wizards, dialogs and so-on. Here is a code snippet that is working well in our case (beware that this might be eclipse version dependent, seems OK with eclipse 3.6 and 4.2)
class Foo {
/**
* The shell of your dialog/wizard
*/
private SWTBotShell shell;
protected SWTBotShell getShell() {
return shell;
}
protected <T extends Widget> T getTopLevelCompositeChild(final Class<T> clazz, final int index) {
return UIThreadRunnable.syncExec(shell.display, new Result<T>() {
#SuppressWarnings("unchecked")
public T run() {
Shell widget = getShell().widget;
if (!widget.isDisposed()) {
for (Control control : widget.getChildren()) {
if (control instanceof Composite) {
Composite composite = (Composite) control;
int counter = 0;
for (Control child : composite.getChildren()) {
if (clazz.isInstance(child)) {
if (counter == index) {
return (T) child;
}
++counter;
}
}
}
}
}
return null;
}
});
}
/**
* Returns the wizard's description or message displayed in its title dialog
* area.
*
* A wizard's description or message is stored in the very first Text widget
* (cf. <tt>TitleAreaDialog.messageLabel</tt> initialization in
* <tt>org.eclipse.jface.dialogs.TitleAreaDialog.createTitleArea(Composite)</tt>
* ).
*
*/
public String getDescription() {
final Text text = getTopLevelCompositeChild(Text.class, 0);
return UIThreadRunnable.syncExec(getShell().display, new Result<String>() {
public String run() {
if (text != null && !text.isDisposed()) {
return text.getText();
}
return null;
}
});
}
}
In case of WizardNewProjectCreationPage I use:
bot.textWithLabel("Create Project"); // This title set by page.setTitle("Create Project");
bot.text("Description."); // this is description set by page.setDescription("Description.");