org.tentackle.fx.rdc.table.TableUtilities Maven / Gradle / Ivy
/*
* Tentackle - https://tentackle.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.tentackle.fx.rdc.table;
import javafx.print.PrinterJob;
import javafx.stage.Stage;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.tentackle.bind.Bindable;
import org.tentackle.common.Constants;
import org.tentackle.common.Service;
import org.tentackle.common.ServiceFactory;
import org.tentackle.common.StringHelper;
import org.tentackle.fx.Fx;
import org.tentackle.fx.FxFactory;
import org.tentackle.fx.FxRuntimeException;
import org.tentackle.fx.FxUtilities;
import org.tentackle.fx.bind.FxTableBinding;
import org.tentackle.fx.component.FxTableView;
import org.tentackle.fx.component.FxTreeTableView;
import org.tentackle.fx.rdc.RdcFxRdcBundle;
import org.tentackle.fx.rdc.RdcUtilities;
import org.tentackle.fx.table.TableColumnConfiguration;
import org.tentackle.fx.table.TableConfiguration;
import org.tentackle.pdo.PdoMember;
import org.tentackle.pdo.PdoUtilities;
import org.tentackle.pdo.PersistentDomainObject;
import org.tentackle.pdo.PersistentObject;
import javax.xml.XMLConstants;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collection;
import java.util.List;
import java.util.ResourceBundle;
interface TableUtilitiesHolder {
TableUtilities INSTANCE = ServiceFactory.createService(TableUtilities.class);
}
/**
* Table-related utility methods.
*
* @author harald
*/
@Service(TableUtilities.class) // defaults to self
public class TableUtilities {
/*
* TableView and TreeTableView don't inherit from each other and thus
* each spawn their own related types such as TableColum, TreeTableColumn, etc...
* As a result, since TableUtilities apply to both tables and treetables,
* a lot of semantically identical code is necessary.
* However, it is not really duplicate code. There's not much we can do to reduce it.
*/
/**
* The singleton.
*
* @return the singleton
*/
public static TableUtilities getInstance() {
return TableUtilitiesHolder.INSTANCE;
}
private static final String SPREADSHEET_EXTENSION = ".csv";
private static final String LAST_SPREADSHEET_PREFIX = "lastCsvNames/";
private static final String SPREADSHEET_KEY = "path";
private static final String XML_EXTENSION = ".xml";
private static final String LAST_XML_PREFIX = "lastXmlNames/";
private static final String XML_KEY = "path";
/**
* Creates a default table configuration for a given PDO class.
* Each attribute getter (annotated with {@link org.tentackle.session.Persistent}) gets its own column,
* except the default attributes from {@link PersistentObject} and object ids for object relations.
*
* @param pdoClass the PDO class
* @param bundle optional bundle to determine the displayed column names, null if name is attribute name
* @param the PDO type
* @return the table configuration
*/
public > TableConfiguration createTableConfiguration(Class pdoClass, ResourceBundle bundle) {
TableConfiguration config = FxFactory.getInstance().createTableConfiguration(pdoClass, null);
for (PdoMember member: PdoUtilities.getInstance().getAttributes(config.getObjectClass(), true)) {
Bindable bindable = member.getGetter().getAnnotation(Bindable.class);
if (bindable != null) { // skip non-bindables
// columnname corresponds to the binding path
String displayName = bundle != null ? bundle.getString(member.getName()) : StringHelper.firstToUpper(member.getName());
config.addColumn(member.getName(), displayName);
}
}
if (config.getColumnConfigurations().isEmpty()) {
// no columns at all: fallback to the ID and SERIAL columns
config.addColumn(Constants.CN_ID, Constants.AN_ID);
config.addColumn(Constants.CN_SERIAL, Constants.AN_SERIAL);
}
return config;
}
/**
* Selects the spreadsheet file.
*
* @param tableName the configured table name
* @param owner the window owner for the selection dialog, null if none
* @return the selected file, null if aborted
*/
public File selectSpreadsheetFile(String tableName, Stage owner) {
return RdcUtilities.getInstance().selectFile(
StringHelper.trimRight(LAST_SPREADSHEET_PREFIX + tableName, '/'),
SPREADSHEET_KEY, SPREADSHEET_EXTENSION,
RdcFxRdcBundle.getString("SPREADSHEET FILE"), owner);
}
/**
* Selects the XML file.
*
* @param tableName the configured table name
* @param owner the window owner for the selection dialog, null if none
* @return the selected file, null if aborted
*/
public File selectXmlFile(String tableName, Stage owner) {
return RdcUtilities.getInstance().selectFile(
StringHelper.trimRight(LAST_XML_PREFIX + tableName, '/'),
XML_KEY, XML_EXTENSION,
RdcFxRdcBundle.getString("XML FILE"), owner);
}
/**
* Converts the table to a spreadsheet file.
*
* @param the row type
* @param table the table view
* @param file the output file
* @param onlySelected true if export only selected rows
*/
public void toSpreadsheet(FxTableView table, File file, boolean onlySelected) {
List items = onlySelected ? table.getSelectionModel().getSelectedItems() : table.getItems();
toSpreadsheet(table.getConfiguration().getColumnConfigurations(), file, items);
}
/**
* Converts the treetable to a spreadsheet file.
*
* @param the row type
* @param treeTable the table view
* @param file the output file
* @param onlySelected true if export only selected rows
*/
public void toSpreadsheet(FxTreeTableView treeTable, File file, boolean onlySelected) {
List items = onlySelected ? treeTable.getSelectedItems() : treeTable.getItems();
toSpreadsheet(treeTable.getConfiguration().getColumnConfigurations(), file, items);
}
/**
* Converts the column configurations to a spreadsheet file.
* @param columnConfigurations the column configurerations
* @param file the output file
* @param items the items to export
* @param the row type
*/
protected void toSpreadsheet(Collection> columnConfigurations, File file, List items) {
String separator = FxUtilities.getInstance().getCsvSeparator();
try (PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(file)))) {
// write header
int ndx = 0;
for (TableColumnConfiguration tc : columnConfigurations) {
if (isColumnVisible(tc)) {
if (ndx > 0) {
out.append(separator);
}
out.append(toCSV(tc.getDisplayedName()));
ndx++;
}
}
out.append('\n');
// write rows
for (S item : items) {
ndx = 0;
for (TableColumnConfiguration tc : columnConfigurations) {
if (isColumnVisible(tc)) {
if (ndx > 0) {
out.append(separator);
}
FxTableBinding binding = tc.getBinding();
if (binding != null) {
binding.setBoundRootObject(item);
Object value = binding.getModelValue();
if (value != null) {
out.append(toCSV(value.toString()));
}
}
ndx++;
}
}
out.append('\n');
}
}
catch (IOException ex) {
throw new FxRuntimeException("creating spreadsheet file failed", ex);
}
}
/**
* Returns whether a column is visible.
*
* @param columnConfiguration the column configuration
* @return true if visible
*/
public boolean isColumnVisible(TableColumnConfiguration, ?> columnConfiguration) {
return columnConfiguration.getTableColumn().isVisible() && columnConfiguration.getTreeTableColumn().isVisible();
}
/**
* Exports a table to an XML file.
*
* @param the row type
* @param table the table view
* @param file the output file
* @param onlySelected true if export only selected rows
*/
public void toXml(FxTableView table, File file, boolean onlySelected) {
List items = onlySelected ? table.getSelectionModel().getSelectedItems() : table.getItems();
toXml(table.getConfiguration().getColumnConfigurations(), table.getConfiguration().getName(), file, items);
}
/**
* Exports a treetable to an XML file.
*
* @param the row type
* @param treeTable the treetable view
* @param file the output file
* @param onlySelected true if export only selected rows
*/
public void toXml(FxTreeTableView treeTable, File file, boolean onlySelected) {
treeTable.getExpandedItemCount();
List items = onlySelected ? treeTable.getSelectedItems() : treeTable.getItems();
toXml(treeTable.getConfiguration().getColumnConfigurations(), treeTable.getConfiguration().getName(), file, items);
}
private void toXml(Collection> columnConfigurations, String name, File file, List items) {
try (PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(file)))) {
StreamResult streamResult = new StreamResult(out);
SAXTransformerFactory tf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
// SAX2.0 ContentHandler.
TransformerHandler hd = tf.newTransformerHandler();
Transformer serializer = hd.getTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
hd.setResult(streamResult);
hd.startDocument();
AttributesImpl atts = new AttributesImpl();
hd.startElement("", "", "Table", atts);
for (S item : items) {
hd.startElement("", "", name, atts);
for (TableColumnConfiguration tc : columnConfigurations) {
if (isColumnVisible(tc)) {
String tag = tc.getName();
FxTableBinding binding = tc.getBinding();
if (binding != null) {
binding.setBoundRootObject(item);
Object value = binding.getModelValue();
String text = value == null ? "" : value.toString();
hd.startElement("", "", tag, atts);
hd.characters(text.toCharArray(), 0, text.length());
hd.endElement("", "", tag);
}
}
}
hd.endElement("", "", name);
}
hd.endElement("", "", "Table");
hd.endDocument();
}
catch (IOException | TransformerConfigurationException | SAXException ex) {
throw new FxRuntimeException("creating XML file failed", ex);
}
}
/**
* Prints a table view.
* Tables need a special handling for multiple pages.
*
* @param table the table to print
* @param title the optional title, null if take from table's configuration
*/
public void print(FxTableView> table, String title) {
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null) {
if (job.showPrintDialog(Fx.getStage(table))) {
TablePrinter.print(table, title, job);
}
}
else {
Fx.error(RdcFxRdcBundle.getString("NO PRINTERS FOUND"));
}
}
/**
* Prints a tree table view.
* Tables need a special handling for multiple pages.
*
* @param treeTable the treetable to print
* @param title the optional title, null if take from treetable's configuration
*/
public void print(FxTreeTableView> treeTable, String title) {
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null) {
if (job.showPrintDialog(Fx.getStage(treeTable))) {
TablePrinter.print(treeTable, title, job); //
}
}
else {
Fx.error(RdcFxRdcBundle.getString("NO PRINTERS FOUND"));
}
}
/**
* Converts a string to a quoted CSV-Field.
*
* @return the quoted string, null if value was null
*/
private String toCSV(String value) {
if (value != null) {
StringBuilder field = new StringBuilder();
field.append('"');
int length = value.length();
for (int i=0; i < length; i++) {
char c = value.charAt(i);
if (c == '"') {
field.append('"');
}
field.append(c);
}
field.append('"');
return field.toString();
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy