package com.onaro.util.jfc.tables; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; import java.io.StringReader; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.swing.JComponent; import javax.swing.JTable; import javax.swing.TransferHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.onaro.sanscreen.client.view.selection.SelectedObject; import com.onaro.util.jfc.NonModalDialog; /** * Extends {@link JTable}'s default transfer-handler (cut&paste and drun&drop) to support the transfer of * {@link SelectedObject} arrays. */ public abstract class TableTransferHandler extends TransferHandler { private static final long serialVersionUID = 1L; static final Logger logger = LogManager.getLogger(NonModalDialog.class); /** * The flavor for SelectedObject arrays. */ private static final DataFlavor selectedObjectFlavor; /** * Maps flavors to getters which in turn create the {@link Transferable} data. */ static final Map dataFlavorsToTrasnferGetter; /** * The flavors are ordered according to preference of usage. */ static final DataFlavor dataFlavors[]; static { dataFlavorsToTrasnferGetter = new HashMap(); List flavorList = new LinkedList(); DataFlavor tmpSelectedObjectFlavor = null; try { DataFlavor flavor = new DataFlavor("text/html;class=java.lang.String"); //$NON-NLS-1$ TransferGetter transferGetter = new TransferGetter() { public Object getData(TableTransferable transfer) { return transfer.htmlData; } }; flavorList.add(flavor); dataFlavorsToTrasnferGetter.put(flavor, transferGetter); flavor = new DataFlavor("text/html;class=java.io.Reader"); //$NON-NLS-1$ transferGetter = new TransferGetter() { public Object getData(TableTransferable transfer) { return new StringReader(transfer.htmlData); } }; flavorList.add(flavor); dataFlavorsToTrasnferGetter.put(flavor, transferGetter); flavor = new DataFlavor("text/plain;class=java.lang.String"); //$NON-NLS-1$ transferGetter = new TransferGetter() { public Object getData(TableTransferable transfer) { return transfer.plainData; } }; flavorList.add(flavor); dataFlavorsToTrasnferGetter.put(flavor, transferGetter); flavor = new DataFlavor("text/plain;class=java.io.Reader"); //$NON-NLS-1$ transferGetter = new TransferGetter() { public Object getData(TableTransferable transfer) { return new StringReader(transfer.plainData); } }; flavorList.add(flavor); dataFlavorsToTrasnferGetter.put(flavor, transferGetter); tmpSelectedObjectFlavor = flavor = new DataFlavor(new SelectedObject[0].getClass(), DataFlavor.javaJVMLocalObjectMimeType + ";class=" + (new SelectedObject[0]).getClass().getName()); //$NON-NLS-1$ transferGetter = new TransferGetter() { public Object getData(TableTransferable transfer) { return transfer.selectedObjects; } }; flavorList.add(flavor); dataFlavorsToTrasnferGetter.put(flavor, transferGetter); } catch (ClassNotFoundException e) { logger.warn("Failed initializing the list of transfarable - " + e.getMessage(), e); //$NON-NLS-1$ } dataFlavors = flavorList.toArray(new DataFlavor[flavorList.size()]); selectedObjectFlavor = tmpSelectedObjectFlavor; } /** * This handler supports copying data from the table, not dragging. * @param c the table * @return {@link TransferHandler#COPY} */ @Override public int getSourceActions(JComponent c) { return COPY; } /** * Tests if any of the given flavours could be imported to the specified component. * @param comp target component * @param transferFlavors the flavours to test if any of them is supported by the component * @return true if the component will allow to import data of the given flavors */ @Override public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { for (int i = 0; i < transferFlavors.length; i++) { DataFlavor flavor = transferFlavors[i]; if (flavor != null && selectedObjectFlavor.equals(flavor)) return true; } return false; } /** * Tests if the given transferable can be imported by forwarding the question to {@link TableTransferHandler#canImport(com.onaro.sanscreen.client.view.selection.SelectedObject[]). * @param transfarable the data to test if could be imported * @return true if this handler will accept the given data */ public boolean canImport(Transferable transfarable) { if (transfarable.isDataFlavorSupported(selectedObjectFlavor)) { try { return canImport(getSelectedObjects(transfarable)); } catch (UnsupportedFlavorException e) { return false; } catch (IOException e) { return false; } } return false; } /** * If the imported data support the {@link SelectedObject}s array, then the array is delegated to the * {@link TableTransferHandler#importData(com.onaro.sanscreen.client.view.selection.SelectedObject[])} method * so it could actually import it to the table. * @param comp the table * @param transfarable the imported data * @return true if the data was imported */ @Override public boolean importData(JComponent comp, Transferable transfarable) { if (transfarable.isDataFlavorSupported(selectedObjectFlavor)) { try { return importData(getSelectedObjects(transfarable)); } catch (UnsupportedFlavorException e) { logger.warn("Failed importing data into '" + comp.getName() + "', transfarable is :" + transfarable+ " - " + e.getMessage(), e); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } catch (IOException e) { logger.warn("Failed importing data into '" + comp.getName() + "', transfarable is :" + transfarable+ " - " + e.getMessage(), e); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } return super.importData(comp, transfarable); } /** * Delegate the actual importing of {@link SelectedObject} to the implementing sub-class. * @param selectedObjects the data to be imported * @return true if the import was completed successfully */ public abstract boolean importData(SelectedObject selectedObjects[]); /** * Ask the implementing sub-class if the {@link SelectedObject} can be imported. * @param selectedObjects the data that may be imported * @return true if the data could be imported */ public abstract boolean canImport(SelectedObject[] selectedObjects); /** * Asks implementing sub-class for the {@link SelectedObject}s representing selected rows. */ public abstract SelectedObject[] getSelectedObjects(); private SelectedObject[] getSelectedObjects(Transferable transfarable) throws UnsupportedFlavorException, IOException { return (SelectedObject[]) transfarable.getTransferData(selectedObjectFlavor); } public static class TableTransferable implements Transferable { protected String plainData; protected String htmlData; protected SelectedObject[] selectedObjects; public TableTransferable(SelectedObject[] selectedObjects, String plainData, String htmlData) { this.plainData = plainData; this.htmlData = htmlData; this.selectedObjects = selectedObjects; } public DataFlavor[] getTransferDataFlavors() { return dataFlavors; } public boolean isDataFlavorSupported(DataFlavor flavor) { return dataFlavorsToTrasnferGetter.containsKey(flavor); } public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { TransferGetter transferGetter = dataFlavorsToTrasnferGetter.get(flavor); if (transferGetter != null) return transferGetter.getData(this); throw new UnsupportedFlavorException(flavor); } } interface TransferGetter { public Object getData(TableTransferable transfer); } }