All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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; } }