/*
gResultTable.java
This module is designed to provide a tabular view of the results
of a query.
Created: 14 July 1997
Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2013
The University of Texas at Austin
Ganymede is a registered trademark of The University of Texas at Austin
Contact information
Author Email: ganymede_author@arlut.utexas.edu
Email mailing list: ganymede@arlut.utexas.edu
US Mail:
Computer Science Division
Applied Research Laboratories
The University of Texas at Austin
PO Box 8029, Austin TX 78713-8029
Telephone: (512) 835-3200
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package arlut.csd.ganymede.client;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Font;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyVetoException;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JInternalFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.JToolBar;
import arlut.csd.JDialog.StringDialog;
import arlut.csd.JTable.rowSelectCallback;
import arlut.csd.JTable.SmartTable;
import arlut.csd.ganymede.common.DumpResult;
import arlut.csd.ganymede.common.Invid;
import arlut.csd.ganymede.common.Query;
import arlut.csd.ganymede.common.ReturnVal;
import arlut.csd.ganymede.rmi.FileTransmitter;
import arlut.csd.ganymede.rmi.Session;
import arlut.csd.Util.PackageResources;
import arlut.csd.Util.TranslationService;
/*------------------------------------------------------------------------------
class
gResultTable
------------------------------------------------------------------------------*/
/**
* <p>Client internal window for displaying the results of a query
* {@link
* arlut.csd.ganymede.rmi.Session#dump(arlut.csd.ganymede.common.Query)
* dump} in a table form.</p>
*
* <p>This window is created when {@link
* arlut.csd.ganymede.client.windowPanel windowPanel}'s {@link
* arlut.csd.ganymede.client.windowPanel#addTableWindow(arlut.csd.ganymede.rmi.Session,
* arlut.csd.ganymede.common.Query,arlut.csd.ganymede.common.DumpResult)
* addTableWindow} method is called.</p>
*
* <p>Note that windowPanel's addTableWindow method is called from
* {@link arlut.csd.ganymede.client.gclient gclient}'s actionPerformed
* method, which spawns a separate thread in which the query is
* performed and the gResultTable window is created.</p>
*
* <p>Constructors for this class take a {@link
* arlut.csd.ganymede.common.Query Query} object describing the query
* that this table was generated from, and a {@link
* arlut.csd.ganymede.common.DumpResult DumpResult} object actually
* containing the dump results from the Ganymede server. gResultTable
* can resubmit the dump query to the server if the user chooses to
* refresh the query, but normally the dump query is performed by
* gclient.</p>
*
* @author Jonathan Abbey, jonabbey@arlut.utexas.edu
*/
public class gResultTable extends JInternalFrame implements rowSelectCallback, ActionListener
{
static final boolean debug = false;
/**
* TranslationService object for handling string localization in the
* Ganymede client.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.client.gResultTable");
static final String
mail_report = ts.l("global.mail_report"), // "Mail Report"
save_report = ts.l("global.save_report"), // "Save Report"
print_report = ts.l("global.print_report"), // "Print Report"
refresh_query = ts.l("global.refresh_query"); // "Refresh Query"
static final String
tab_option = ts.l("global.tab_option"), // "Tab separated ASCII"
csv_option = ts.l("global.csv_option"), // "CSV File"
html_option = ts.l("global.html_option"), // "HTML"
xml_option = ts.l("global.xml_option"); // "Ganymede XML File"
static final String TABLE_SAVE = "query_table_default_dir";
// ---
/**
* Reference to the desktop pane containing the client's internal
* windows. Used to access some GUI resources.
*/
windowPanel wp;
/**
* Our key-indexed SmartTable
*/
SmartTable sTable;
/**
* Main remote interface for communications with the server. Used to resubmit the
* query on query refresh.
*/
Session session;
/**
* The actual Query used to create this gResultTable. Used if the user asks that the
* query be refreshed.
*/
Query query;
/**
* The contentPane for this internal window. We place the table in
* this container.
*/
Container contentPane;
JToolBar toolbar;
// Row Menus for right click popup
JMenuItem viewMI = new JMenuItem(ts.l("init.view")); // "View Entry"
JMenuItem editMI = new JMenuItem(ts.l("init.edit")); // "Edit Entry"
JMenuItem cloneMI = new JMenuItem(ts.l("init.clone")); // "Clone Entry"
JMenuItem deleteMI = new JMenuItem(ts.l("init.delete")); // "Delete Entry"
JMenuItem inactivateMI = new JMenuItem(ts.l("init.inactivate")); // "Inactivate Entry"
public boolean[] used;
/**
* Constructor for gResultTable. Creates the GUI table, loads it,
* and presents it. DumpResult is dissociated when this constructor is
* through with it, to aid GC.
*/
public gResultTable(windowPanel wp, Session session, Query query, DumpResult results) throws RemoteException
{
super(); // JInternalFrame init
this.wp = wp;
this.session = session;
this.query = query;
contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
loadResults(results);
}
/**
* This method loads the DumpResult into a table. The DumpResult is
* dissociated when this method is through with it, to aid GC.
*/
public void loadResults(DumpResult results)
{
// "Loading table"
setStatus(ts.l("loadResults.loading_status"), 0);
getContentPane().removeAll(); // for the refresh, we must clean this pane out
// Add our Toolbar in.
toolbar = createToolBar();
add("North",toolbar);
toolbar.grabFocus();
// Create Row Menus popups now.
viewMI = new JMenuItem(ts.l("init.view")); // "View Entry"
editMI = new JMenuItem(ts.l("init.edit")); // "Edit Entry"
cloneMI = new JMenuItem(ts.l("init.clone")); // "Clone Entry"
deleteMI = new JMenuItem(ts.l("init.delete")); // "Delete Entry"
inactivateMI = new JMenuItem(ts.l("init.inactivate")); // "Inactivate Entry"
JPopupMenu rowMenu = new JPopupMenu();
rowMenu.add(viewMI);
rowMenu.add(editMI);
rowMenu.add(cloneMI);
rowMenu.add(deleteMI);
rowMenu.add(inactivateMI);
// Set our title with the result count
String queryType = null;
if (query.objectName != null)
{
queryType = query.objectName;
}
else if (query.objectType != -1)
{
queryType = wp.getgclient().loader.getObjectType(query.objectType);
}
else
{
queryType = ts.l("loadResults.unknown_query_type"); // "<unknown>"
}
int rows = results.resultSize();
List<String> headers = results.getHeaders();
// calculate which columns are not used, so we can exclude them
boolean used[] = new boolean[headers.size()];
int colsUsed = 0;
for (int j = 0; j < headers.size(); j++)
{
used[j] = false;
for (int i = 0; !used[j] && i < rows; i++)
{
Object x = results.getResult(i, j);
if (x != null && !x.toString().equals(""))
{
used[j] = true;
colsUsed++;
}
}
}
// "Query: [{0}] results: {1,num,#} entries"
wp.setWindowTitle(this, ts.l("loadResults.window_title", queryType, Integer.valueOf(rows)));
wp.updateWindowMenu();
if (debug)
{
System.err.println("gResultTable: " + headers.size() + " headers returned by query, " + colsUsed + " in use.");
}
String[] columnNames = new String[colsUsed];
// Get all used Column Names now
for (int j=0, k=0; j < headers.size(); j++)
{
if (used[j])
{
columnNames[k] = headers.get(j);
if (debug)
{
System.out.println("columnNames " + k + " value is:" + columnNames[k] + "*");
}
k++;
}
}
// Pass our SmartTable the results set, and a text Row menu
// This will render the table nicely and setup a header and row right click menu (user provided)
// header can sort and remove columns, the columns must be defined before creating the table, the
// data cells may be filled in later. this is for the callback functions
sTable = new SmartTable(columnNames, rowMenu, this);
getContentPane().add(sTable);
// Now Read in all the result lines.
for (int i=0; i < rows; i++)
{
Invid invid = results.getInvid(i);
sTable.newRow(invid);
for (int j=0, k=0; j < headers.size(); j++)
{
if (used[j])
{
Object cellResult = results.getResult(i, j);
sTable.setCellValue(invid, k, cellResult);
k++;
}
}
}
validate(); // needed after refresh results.
sTable.calcResizeMode();
// "Query Complete."
setStatus(ts.l("loadResults.complete_status"));
}
/**
* Creates and initializes the JInternalFrame's toolbar.
*/
private JToolBar createToolBar()
{
// New image icons from: http://tango.freedesktop.org/Tango_Icon_Library
Image mailIcon = PackageResources.getImageResource(this, "queryTB_mail.png", getClass());
Image saveIcon = PackageResources.getImageResource(this, "queryTB_save.png", getClass());
Image printIcon = PackageResources.getImageResource(this, "queryTB_print.png", getClass());
Image refreshIcon = PackageResources.getImageResource(this, "queryTB_refresh.png", getClass());
Insets insets = new Insets(0,0,0,0);
JToolBar toolBarTemp = new JToolBar();
toolBarTemp.setBorderPainted(true);
toolBarTemp.setFloatable(false);
toolBarTemp.setMargin(insets);
// "Mail"
JButton b = new JButton(ts.l("createToolBar.mail_button"), new ImageIcon(mailIcon));
// "M"
if (ts.hasPattern("createToolBar.mail_button_mnemonic_optional"))
{
b.setMnemonic((int) ts.l("createToolBar.mail_button_mnemonic_optional").charAt(0));
}
b.setFont(new Font("SansSerif", Font.PLAIN, 10));
b.setMargin(insets);
b.setActionCommand(mail_report);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "Email results"
if (ts.hasPattern("createToolBar.mail_button_tooltip_optional"))
{
b.setToolTipText(ts.l("createToolBar.mail_button_tooltip_optional"));
}
b.addActionListener(this);
toolBarTemp.add(b);
// No save feature if running from applet
if (!glogin.isApplet())
{
// "Save"
b = new JButton(ts.l("createToolBar.save_button"), new ImageIcon(saveIcon));
// "S"
if (ts.hasPattern("createToolBar.save_button_mnemonic_optional"))
{
b.setMnemonic((int) ts.l("createToolBar.save_button_mnemonic_optional").charAt(0));
}
b.setFont(new Font("SansSerif", Font.PLAIN, 10));
b.setMargin(insets);
b.setActionCommand(save_report);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "Save results"
if (ts.hasPattern("createToolBar.save_button_tooltip_optional"))
{
b.setToolTipText(ts.l("createToolBar.save_button_tooltip_optional"));
}
b.addActionListener(this);
toolBarTemp.add(b);
}
// "Print"
b = new JButton(ts.l("createToolBar.print_button"), new ImageIcon(printIcon));
// "P"
if (ts.hasPattern("createToolBar.print_button_mnemonic_optional"))
{
b.setMnemonic((int) ts.l("createToolBar.print_button_mnemonic_optional").charAt(0));
}
b.setFont(new Font("SansSerif", Font.PLAIN, 10));
b.setMargin(insets);
b.setActionCommand(print_report);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "Print results"
if (ts.hasPattern("createToolBar.print_button_tooltip_optional"))
{
b.setToolTipText(ts.l("createToolBar.print_button_tooltip_optional"));
}
b.addActionListener(this);
toolBarTemp.add(b);
// "Refresh"
b = new JButton(ts.l("createToolBar.refresh_button"), new ImageIcon(refreshIcon));
// "R"
if (ts.hasPattern("createToolBar.refresh_button_mnemonic_optional"))
{
b.setMnemonic((int) ts.l("createToolBar.refresh_button_mnemonic_optional").charAt(0));
}
b.setFont(new Font("SansSerif", Font.PLAIN, 10));
b.setMargin(insets);
b.setActionCommand(refresh_query);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "Refresh query"
if (ts.hasPattern("createToolBar.refresh_button_tooltip_optional"))
{
b.setToolTipText(ts.l("createToolBar.refresh_button_tooltip_optional"));
}
b.addActionListener(this);
toolBarTemp.add(b);
return toolBarTemp;
}
JToolBar getToolBar()
{
return toolbar;
}
public void actionPerformed(ActionEvent event)
{
if (event.getActionCommand().equals(mail_report))
{
sendReport(true);
toolbar.requestFocus();
return;
}
if (event.getActionCommand().equals(save_report))
{
sendReport(false);
toolbar.requestFocus();
return;
}
if (event.getActionCommand().equals(print_report))
{
sTable.print();
toolbar.requestFocus();
return;
}
if (event.getActionCommand().equals(refresh_query))
{
refreshQuery();
toolbar.requestFocus();
return;
}
}
public void sendReport(boolean mailit)
{
SaveDialog dialog = new SaveDialog(wp.gc, mailit);
String addresses;
String format;
StringBuffer report = null;
List<String> formatChoices = new ArrayList<String>();
formatChoices.add(tab_option);
formatChoices.add(csv_option);
formatChoices.add(html_option);
formatChoices.add(xml_option);
dialog.setFormatChoices(formatChoices);
if (!dialog.showDialog())
{
return;
}
format = dialog.getFormat();
if (format.equals(html_option))
{
report = generateHTMLRep();
}
else if (format.equals(csv_option))
{
report = generateCSV();
}
else if (format.equals(tab_option))
{
report = generateTextRep('\t');
}
else if (format.equals(xml_option))
{
try
{
report = generateXMLReport();
}
catch (RemoteException ex)
{
this.wp.getgclient().processException(ex);
}
}
else
{
throw new RuntimeException("Error, SaveDialog returned unrecognized format! " + format);
}
if (mailit)
{
addresses = dialog.getRecipients();
try
{
if (format.equals(html_option))
{
session.sendHTMLMail(addresses, dialog.getSubject(), null, report);
}
else
{
session.sendMail(addresses, dialog.getSubject(), report);
}
}
catch (Exception ex)
{
wp.gc.processException(ex, "Could not send mail.");
}
return;
}
else
{
JFileChooser chooser = new JFileChooser();
File file;
PrintWriter writer = null;
chooser.setDialogType(JFileChooser.SAVE_DIALOG);
// "Save Report As"
chooser.setDialogTitle(ts.l("sendReport.dialog_title"));
if (gclient.prefs != null)
{
String defaultPath = gclient.prefs.get(TABLE_SAVE, null);
if (defaultPath != null)
{
chooser.setCurrentDirectory(new File(defaultPath));
}
}
int returnValue = chooser.showDialog(wp.gc, null);
if (!(returnValue == JFileChooser.APPROVE_OPTION))
{
return;
}
file = chooser.getSelectedFile();
File directory = chooser.getCurrentDirectory();
try
{
if (gclient.prefs != null)
{
gclient.prefs.put(TABLE_SAVE, directory.getCanonicalPath());
}
}
catch (java.io.IOException ex)
{
// we don't really care if we can't save the directory
// path in our preferences all that much.
}
if (file.exists())
{
// "Warning, file {0} already exists.
// "Warning, file {0} already exists. Are you sure you want to replace this file?"
// "Yes, Overwrite"
StringDialog d = new StringDialog(wp.gc,
ts.l("sendReport.file_exists_title", file.getName()),
ts.l("sendReport.file_exists", file.getName()),
ts.l("sendReport.overwrite"),
StringDialog.getDefaultCancel(),
null);
Hashtable result = d.showDialog();
if (result == null)
{
return;
}
}
try
{
writer = new PrintWriter(new BufferedWriter(new FileWriter(file)));
}
catch (java.io.IOException e)
{
// "Trouble saving"
// "Could not open the file."
wp.gc.showErrorMessage(ts.l("sendReport.save_problem_subj"),
ts.l("sendReport.save_problem_text"));
return;
}
writer.print(report.toString());
writer.close();
}
}
public void refreshQuery()
{
DumpResult buffer = null;
// "Querying server"
setStatus(ts.l("refreshQuery.querying_status"), 0);
if (query == null)
{
return;
}
try
{
buffer = session.dump(query);
}
catch (Exception ex)
{
this.wp.getgclient().processException(ex);
}
if (buffer == null)
{
if (debug)
{
System.err.println("null query dump result");
}
// "No results"
setStatus(ts.l("refreshQuery.no_result_status"));
}
else
{
loadResults(buffer);
}
}
/**
* Called when a row is double selected (double clicked) in the table
*
* @param key Hash key for the selected row
*/
public void rowSelected(Object key)
{
}
/**
* Called when a row is unselected in the table
*
* @param key Hash key for the unselected row
*/
public void rowDoubleSelected(Object key)
{
}
/**
* Called when a row is unselected in the table
*
* @param key Hash key for the row on which the popup menu item was performed
* @param endSelected false if the callback should assume that the final
* state of the system due to the user's present
* action will have no row selected
*/
public void rowUnSelected(Object key, boolean endSelected)
{
}
/**
* This function is called from inside SmartTable via the right
* click row menus, then passed back up to main client
*
* @param key Hash key for the row on which the popup menu item was performed
* @param event the original ActionEvent from the popupmenu.
* See event.getSource() to identify the menu item performed.
*/
public void rowMenuPerformed(Object key, java.awt.event.ActionEvent event)
{
if (event.getSource() == viewMI)
{
wp.getgclient().viewObject((Invid) key);
}
else if (event.getSource() == editMI)
{
wp.getgclient().editObject((Invid)key);
}
else if (event.getSource() == deleteMI)
{
wp.getgclient().deleteObject((Invid)key, true);
}
else if (event.getSource() == inactivateMI)
{
wp.getgclient().inactivateObject((Invid)key);
}
else if (event.getSource() == cloneMI)
{
wp.getgclient().cloneObject((Invid)key);
}
}
public void colMenuPerformed(int menuCol, java.awt.event.ActionEvent event)
{
}
/**
* This method generates an HTML representation of the table's
* contents.
*/
StringBuffer generateHTMLRep()
{
StringBuffer result = new StringBuffer();
int colcount = sTable.getColumnCount();
int size = sTable.getRowCount();
String cellText;
String date = (new Date()).toString();
// we just have to hope that the table isn't going to
// change while we're creating things. There's actually
// a possibility for problems here, since the user is
// free to mess with the table while we work, but since
// we are likely to be dispatched from the GUI thread,
// this may not be an issue.
result.append("<html>\n");
result.append(" <head>\n");
result.append(" <title>Ganymede Table Dump - ");
result.append(date);
result.append("</title>\n");
result.append(" </head>\n");
result.append(" <body bgcolor=\"#FFFFFF\">\n");
result.append(" <h1>Ganymede Table Dump - ");
result.append(date);
result.append("</h1>\n");
result.append(" <hr/>\n");
result.append(" <table border>\n");
result.append(" <tr>\n");
for (int i = 0; i < colcount; i++)
{
result.append(" <th>");
result.append(sTable.getColumnName(i));
result.append("</th>\n");
}
result.append(" </tr>\n");
for (int i = 0; i < size; i++)
{
result.append(" <tr>\n");
for (int j = 0; j < colcount; j++)
{
result.append(" <td>");
Object value = sTable.getValueAt(i, j);
if (value != null)
{
cellText = escapeHTML(value.toString());
result.append(cellText);
}
result.append("</td>\n");
}
result.append(" </tr>\n");
}
result.append(" </table>\n");
result.append(" <hr/>\n");
result.append(" </body>\n");
result.append("</html>\n");
return result;
}
/**
* This method generates a CSV representation of the table's
* contents, one line per row of the table.
*/
StringBuffer generateCSV()
{
StringBuffer result = new StringBuffer();
int colcount = sTable.getColumnCount();
int size = sTable.getRowCount();
String cellText;
// we just have to hope that the table isn't going to
// change while we're creating things. There's actually
// a possibility for problems here, since the user is
// free to mess with the table while we work, but since
// we are likely to be dispatched from the GUI thread,
// this may not be an issue.
for (int i = 0; i < size; i++)
{
for (int j = 0; j < colcount; j++)
{
if (j > 0)
{
result.append(",");
}
Object value = sTable.getValueAt(i, j);
if (value != null)
{
cellText = escapeCSV(value.toString());
result.append(cellText);
}
}
result.append("\n");
}
return result;
}
/**
* This method generates a sepChar-separated dump of the table's
* contents, one line per row of the table.
*/
StringBuffer generateTextRep(char sepChar)
{
StringBuffer result = new StringBuffer();
int colcount = sTable.getColumnCount();
int size = sTable.getRowCount();
String cellText;
// we just have to hope that the table isn't going to
// change while we're creating things. There's actually
// a possibility for problems here, since the user is
// free to mess with the table while we work, but since
// we are likely to be dispatched from the GUI thread,
// this may not be an issue.
for (int i = 0; i < size; i++)
{
for (int j = 0; j < colcount; j++)
{
if (j > 0)
{
result.append(sepChar);
}
Object value = sTable.getValueAt(i, j);
if (value != null)
{
cellText = escapeString(value.toString(), sepChar);
result.append(cellText);
}
}
result.append("\n");
}
return result;
}
/**
* This method generates a Ganymede XML data file containing the
* table's contents.
*/
StringBuffer generateXMLReport() throws RemoteException
{
ReturnVal retVal = session.runXMLQuery(query);
if (!retVal.didSucceed())
{
this.wp.getgclient().handleReturnVal(retVal);
return null;
}
FileTransmitter transmitter = retVal.getFileTransmitter();
if (transmitter != null)
{
ByteArrayOutputStream outstream = new ByteArrayOutputStream();
byte[] chunk = transmitter.getNextChunk();
while (chunk != null)
{
outstream.write(chunk, 0, chunk.length);
chunk = transmitter.getNextChunk();
}
transmitter.end();
return new StringBuffer(outstream.toString());
}
return null;
}
/**
* This helper method makes a field string safe to emit
* to a sepChar separated text file.
*/
String escapeString(String string, char sepChar)
{
char[] chars;
StringBuilder buffer = new StringBuilder();
chars = string.toCharArray();
for (int j = 0; j < chars.length; j++)
{
if (chars[j] == '\\')
{
buffer.append("\\\\");
}
else if (chars[j] == '\t')
{
buffer.append(" ");
}
else if (chars[j] == '\n')
{
buffer.append("\\n");
}
else if (chars[j] == sepChar)
{
buffer.append("\\");
buffer.append(sepChar);
}
else
{
buffer.append(chars[j]);
}
}
return buffer.toString();
}
/**
* This helper method makes a field string safe to emit to a CSV
* file.
*/
String escapeCSV(String string)
{
char[] chars;
StringBuilder buffer = new StringBuilder();
boolean surround_with_quotes = false;
chars = string.toCharArray();
for (int j = 0; j < chars.length; j++)
{
if (chars[j] == '"')
{
buffer.append("\"\"");
continue;
}
else if (chars[j] == '\n')
{
surround_with_quotes = true;
}
else if (chars[j] == ',')
{
surround_with_quotes = true;
}
buffer.append(chars[j]);
}
if (surround_with_quotes)
{
return "\"" + buffer.toString() + "\"";
}
else
{
return buffer.toString();
}
}
/**
* This helper method makes a field string safe to emit
* to an HTML file.
*/
String escapeHTML(String string)
{
char[] chars;
StringBuilder buffer = new StringBuilder();
chars = string.toCharArray();
for (int j = 0; j < chars.length; j++)
{
if (chars[j] == '<')
{
buffer.append("<");
}
else if (chars[j] == '>')
{
buffer.append(">");
}
else
{
buffer.append(chars[j]);
}
}
return buffer.toString();
}
// -- helper functions
private final void setStatus(String s)
{
wp.gc.setStatus(s);
}
private final void setStatus(String s, int timeLimit)
{
wp.gc.setStatus(s, timeLimit);
}
/**
* Give access to the protected paramString() method of our
* ancestors for debug.
*/
public String paramString()
{
return super.paramString();
}
}