org.wings.STransferHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.wings.STransferHandler.java

Source

/*
 * Copyright 2000,2008 wingS development team.
 *
 * This file is part of wingS (http://wingsframework.org).
 *
 * wingS 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.
 *
 * Please see COPYING for the complete licence.
 */
package org.wings;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wings.event.SMouseEvent;

import javax.swing.*;
import java.io.Serializable;
import java.io.IOException;
import java.awt.dnd.DnDConstants;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.lang.reflect.Method;
import java.beans.PropertyChangeListener;

/**
 * Slightly simplified Swing-compatible TransferHandler for the wingS-Framework
 * @author Florian Roks
 */
public class STransferHandler implements Serializable {
    private final transient static Log LOG = LogFactory.getLog(STransferHandler.class);

    /**
     * An integer representing no transfer action
     */
    public final static int NONE = DnDConstants.ACTION_NONE;
    /**
     * An integer representing a move transfer action
     */
    public final static int MOVE = DnDConstants.ACTION_MOVE;
    /**
     * An integer representing a copy transfer action
     */
    public final static int COPY = DnDConstants.ACTION_COPY;
    /**
     * An integer representing a copy|move transfer action
     */
    public final static int COPY_OR_MOVE = DnDConstants.ACTION_COPY_OR_MOVE;
    /**
     * An integer representing a link transfer action
     */
    public final static int LINK = DnDConstants.ACTION_LINK;

    private String propertyName = null;

    protected STransferHandler() {
        this(null);
    }

    /**
     * Constructs a new STransferHandler with Property property
     * @param property
     */
    public STransferHandler(String property) {
        this.propertyName = property;
    }

    /**
     * Returns whether if this component can import one of the Transfers DataFlavors  
     * @param component
     * @param transferFlavors
     * @return
     */
    public boolean canImport(SComponent component, DataFlavor[] transferFlavors) {
        PropertyDescriptor propertyDescriptor = getPropertyDescriptor(propertyName, component);

        if (propertyDescriptor == null)
            return false;

        // get the writer Method
        Method writeMethod = propertyDescriptor.getWriteMethod();
        if (writeMethod == null)
            return false;

        // get arguments of writeMethod
        Class<?>[] arguments = writeMethod.getParameterTypes();
        if (arguments == null || arguments.length != 1)
            return false;

        // check if the argument type matches one of the transferflavors
        DataFlavor flavor = getPropertyDataFlavor(arguments[0], transferFlavors);
        if (flavor == null)
            return false;

        return true;
    }

    /**
     * Returns if this component can import the given Data within a TransferSupport
     * @param support The TransferSupport to be checked
     * @return Returns true if the Component can import the data, false if not
     */
    public boolean canImport(STransferHandler.TransferSupport support) {
        return canImport(support.getComponent(), support.getDataFlavors());
    }

    /**
     * Creates a Transferable based on the data in the given component
     * @param component
     * @return
     */
    protected Transferable createTransferable(SComponent component) {
        PropertyDescriptor propertyDescriptor = getPropertyDescriptor(propertyName, component);
        if (propertyDescriptor != null) {
            return new PropertyTransferable(propertyDescriptor, component);
        }
        return null;
    }

    /**
     * Creates a Transferable based on the data in the given component and event (calls createTransferable(SComponent))
     * @param component
     * @param event
     * @return
     */
    protected Transferable createTransferable(SComponent component, SMouseEvent event) {
        return createTransferable(component);
    }

    /**
     * Exports component's data for a drag-operation - it is required for this method to call
     * the setTransferSupport Method in the SDragAndDropManager to set a TransferSupport, to be used for this
     * drag-operation 
     * @param component
     * @param event
     * @param action
     */
    public void exportAsDrag(SComponent component, SMouseEvent event, int action) {
        // (Swing) InputEvent -> (wingS) SMouseEvent
        int sourceActions = this.getSourceActions(component);
        // invalid action or requested action != actions allowed by source
        if ((action != MOVE && action != COPY && action != LINK) || (sourceActions & action) == 0) {
            action = NONE;
        }

        if (action != NONE) {
            Transferable transferable = createTransferable(component, event);
            component.getSession().getSDragAndDropManager()
                    .setTransferSupport(new TransferSupport(component, transferable));
        } else {
            exportDone(component, null, NONE);
        }
    }

    /**
     * Method that is called after a completed Drag-Operation - action will be NONE if
     * unsuccessfull and != NONE if successfull
     * @param source
     * @param data
     * @param action
     */
    public void exportDone(SComponent source, Transferable data, int action) {
    }

    /**
     * Exports the data in componet to the given Clipboard
     * @param component
     * @param clipboard
     * @param action
     * @throws IllegalStateException
     */
    public void exportToClipboard(SComponent component, Clipboard clipboard, int action)
            throws IllegalStateException {
        Transferable t = createTransferable(component);
        if ((action != COPY && action != MOVE) || (getSourceActions(component) & action) == 0) {
            exportDone(component, null, NONE);
            return;
        }

        if (t != null) {
            try {
                clipboard.setContents(t, null);
                exportDone(component, t, action);
            } catch (IllegalStateException e) {
                exportDone(component, t, NONE);
                throw e;
            }
        }
    }

    /**
     * ClipboardAction - Helper-Class for the Clipboard-Actions, used by getCutAction, getCopyAction, getPasteAction
     */
    public static class ClipboardAction implements Action {
        private String name;

        public ClipboardAction(String name) {
            this.name = name;
        }

        public Object getValue(String key) {
            if (NAME.equals(key)) {
                return name;
            }
            return null;
        }

        public void putValue(String key, Object value) {

        }

        public void setEnabled(boolean b) {
        }

        public boolean isEnabled() {
            return isEnabled(null);
        }

        public boolean isEnabled(Object sender) {
            if (!(sender instanceof SComponent) || ((SComponent) sender).getTransferHandler() != null)
                return true;

            return false;
        }

        public void addPropertyChangeListener(PropertyChangeListener listener) {
        }

        public void removePropertyChangeListener(PropertyChangeListener listener) {
        }

        public void actionPerformed(ActionEvent event) {
            SComponent component = (SComponent) event.getSource();

            try {
                STransferHandler th = component.getTransferHandler();
                Clipboard clipboard = component.getSession().getClipboard();
                if (this.name.equals("cut")) { // cut (MOVE) from component to clipboard
                    th.exportToClipboard(component, clipboard, MOVE);
                } else if (this.name.equals("copy")) { // copy (COPY) from component to clipboard
                    th.exportToClipboard(component, clipboard, COPY);
                } else if (this.name.equals("paste")) { // import the data from the clipboard
                    Transferable transferable = clipboard.getContents(null);
                    TransferSupport support = new TransferSupport(component, transferable);
                    th.importData(support);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    static final Action copyAction = new ClipboardAction("copy");
    static final Action cutAction = new ClipboardAction("cut");
    static final Action pasteAction = new ClipboardAction("paste");

    /**
     * Returns an Action representing a copy operation to the Clipboard
     * @return
     */
    public static Action getCopyAction() {
        return copyAction;
    }

    /**
     * Returns an Action representing a cut operation to the Clipboard
     * @return
     */
    public static Action getCutAction() {
        return cutAction;
    }

    /**
     * Returns an Action representing a paste operation from the Clipboard
     * @return
     */
    public static Action getPasteAction() {
        return pasteAction;
    }

    private DataFlavor getPropertyDataFlavor(Class<?> argument, DataFlavor[] flavors) {
        for (DataFlavor flavor : flavors) {
            if ("application".equals(flavor.getPrimaryType())
                    && "x-java-jvm-local-objectref".equals(flavor.getSubType())) {
                if (argument.isAssignableFrom(flavor.getRepresentationClass())) {
                    return flavor;
                }
            }
        }

        return null;
    }

    private static Object invoke(Method method, SComponent component, Object[] arguments) throws Exception {
        try {
            Object returnValue = method.invoke(component, arguments);
            return returnValue;
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    private static class PropertyDescriptor {
        private String name = "";
        private Method readMethod;
        private Method writeMethod;
        private Class<?> propertyType;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Method getReadMethod() {
            return readMethod;
        }

        public void setReadMethod(Method readMethod) {
            this.readMethod = readMethod;

            if (this.readMethod != null) {
                Class<?>[] args = readMethod.getParameterTypes();
                if (args.length != 0) {
                    LOG.error("invalid argument count for readMethod");
                }
                if (this.propertyType == null)
                    this.propertyType = readMethod.getReturnType();
                else if (this.propertyType != readMethod.getReturnType()) {
                    LOG.error("type mismatch between writeMethod and readMethod");
                }
            } else {
                this.propertyType = null;
            }
        }

        public Method getWriteMethod() {
            return writeMethod;
        }

        public void setWriteMethod(Method writeMethod) {
            this.writeMethod = writeMethod;

            if (writeMethod != null) {
                Class<?>[] args = writeMethod.getParameterTypes();
                if (args.length != 1) {
                    LOG.error("invalid argument count for writeMethod");
                }
                Class<?>[] params = writeMethod.getParameterTypes();
                if (this.propertyType == null)
                    this.propertyType = params[0];
                else if (this.propertyType != params[0]) {
                    LOG.error("type mismatch between writeMethod and readMethod");
                }
            } else {
                this.propertyType = null;
            }
        }

        public Class<?> getPropertyType() {
            return this.propertyType;
        }
    }

    private static PropertyDescriptor getPropertyDescriptor(String propertyName, Class<?> componentClass) {
        PropertyDescriptor descriptor = new PropertyDescriptor();

        Method[] methods = componentClass.getMethods();
        for (Method method : methods) {
            if (method.getName().equalsIgnoreCase("get" + propertyName)) {
                descriptor.setReadMethod(method);
                continue;
            }

            if (method.getName().equalsIgnoreCase("set" + propertyName)) {
                descriptor.setWriteMethod(method);
                continue;
            }
        }

        return descriptor;
    }

    private static PropertyDescriptor getPropertyDescriptor(String propertyName, SComponent component) {
        // search for a getter method that is named by propertyName i.e.
        // if propertyName equals "text", getText() would be the method 
        if (propertyName == null) {
            return null;
        }

        try {
            PropertyDescriptor property = getPropertyDescriptor(propertyName, component.getClass());
            Method readMethod = property.getReadMethod();

            if (readMethod != null) {
                Class<?>[] params = readMethod.getParameterTypes();
                if (params == null || params.length == 0) {
                    return property;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * Returns the Actions supported by the given Component - Actions may be any combination of COPY, MOVE, LINK or NONE
     * @param component
     * @return
     */
    public int getSourceActions(SComponent component) {
        PropertyDescriptor propertyDescriptor = getPropertyDescriptor(this.propertyName, component);
        if (propertyDescriptor != null) // only COPY allowed by default if a read method is available
            return COPY;

        return NONE;
    }

    /**
     * Returns an SIcon representing the visual representation of the Transferable transferable
     * @param transferable
     * @return
     */
    public SIcon getVisualRepresentation(Transferable transferable) {
        return null;
    }

    private SLabel label = null;

    /**
     * Returns an SLabel representing the visual representation of the Transferable transferable
     * Note: (calls SIcon getVisualRepresentation(Transferable))
     * @param transferable
     * @return
     */
    public SLabel getVisualRepresentationLabel(Transferable transferable) {
        SIcon icon = getVisualRepresentation(transferable);
        if (icon == null)
            return null;

        if (label == null)
            label = new SLabel();

        label.setIcon(icon);
        return label;
    }

    /**
     * Imports the data specified in the Transferable into the given component
     * @param component
     * @param transferable
     * @return
     */
    public boolean importData(SComponent component, Transferable transferable) {
        PropertyDescriptor propertyDecsriptor = getPropertyDescriptor(this.propertyName, component);
        if (propertyDecsriptor == null)
            return false;
        Method writeMethod = propertyDecsriptor.getWriteMethod();
        if (writeMethod == null)
            return false;
        Class<?>[] arguments = writeMethod.getParameterTypes();
        if (arguments == null || arguments.length != 1) {
            return false;
        }

        DataFlavor flavor = getPropertyDataFlavor(arguments[0], transferable.getTransferDataFlavors());
        if (flavor != null) {
            try {
                Object value = transferable.getTransferData(flavor);

                STransferHandler.invoke(writeMethod, component, new Object[] { value });
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * Imports the data specified in TransferSupport into the component specified in TransferSupport
     * @param support 
     * @return
     */
    public boolean importData(STransferHandler.TransferSupport support) {
        return importData(support.getComponent(), support.getTransferable());
    }

    /**
     * PropertyTransferable - represents Data from a Property
     */
    public static class PropertyTransferable implements Transferable {
        private PropertyDescriptor propertyDescriptor = null;
        private SComponent component;

        public PropertyTransferable(PropertyDescriptor propertyDescriptor, SComponent component) {
            this.propertyDescriptor = propertyDescriptor;
            this.component = component;
        }

        public DataFlavor[] getTransferDataFlavors() {
            Class<?> propertyType = propertyDescriptor.getPropertyType();
            String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=" + propertyType.getName();
            try {
                return new DataFlavor[] { new DataFlavor(mimeType) };
            } catch (ClassNotFoundException e) {
                return new DataFlavor[0];
            }
        }

        public boolean isDataFlavorSupported(DataFlavor dataFlavor) {
            Class<?> propertyType = propertyDescriptor.getPropertyType();
            if (dataFlavor.getPrimaryType().equals("application")
                    && dataFlavor.getSubType().equals("x-java-jvm-local-objectref")
                    && dataFlavor.getRepresentationClass().isAssignableFrom(propertyType))
                return true;

            return false;
        }

        public Object getTransferData(DataFlavor dataFlavor) throws UnsupportedFlavorException, IOException {
            if (!isDataFlavorSupported(dataFlavor))
                throw new UnsupportedFlavorException(dataFlavor);

            Method reader = this.propertyDescriptor.getReadMethod();
            try {
                return invoke(reader, this.component, null);
            } catch (Exception e) {
                throw new IOException("Invoke: Property read failed: " + this.propertyDescriptor.getName());
            }
        }
    }

    /**
     * TransferSupport encapsulates details of a transfer
     */
    public final static class TransferSupport {
        private SComponent component;
        private Transferable transferable;
        private boolean isDrop = false;
        private int dropAction = -1;
        private int requestedDropAction = -1;
        private DropLocation dropLocation;
        private SComponent source;

        public TransferSupport(SComponent component, Transferable transferable) {
            if (component == null)
                throw new NullPointerException("component must be not null");
            if (transferable == null)
                throw new NullPointerException("transferable must be not null");

            this.component = component;
            this.transferable = transferable;
        }

        /**
         * Prepares the TransferSupport to be used in dragover cases
         * @param component
         * @param event
         * @param action
         */ /*
             public void setIsDragOver(SComponent component, SComponent source, SMouseEvent event, int action) {
             this.component = component;
             this.source = source;
             this.requestedDropAction = action;
                 
             setDropLocation(this.component, event.getPoint());
             } */

        /**
         * Prepares the TransferSupport to be used in drop cases
         * @param component
         * @param event
         * @param action
         */
        public void setIsDrop(SComponent component, SComponent source, SMouseEvent event, int action) {
            this.isDrop = true;
            this.component = component;
            this.source = source;
            this.requestedDropAction = action;

            setDropLocation(this.component, event.getPoint());
        }

        /**
         * Returns the component on which the cursor is  
         * @return
         */
        public SComponent getComponent() {
            return this.component;
        }

        /**
         * Returns the Data Flavors, that this TransferSupport supports
         * @return
         */
        public DataFlavor[] getDataFlavors() {
            return transferable.getTransferDataFlavors();
        }

        /**
         * Returns the drop action requested by the user
         * @return
         */
        public int getUserDropAction() {
            if (!isDrop())
                throw new IllegalStateException("getUserDropAction called when isDrop = false");

            return this.requestedDropAction;
        }

        /**
         * Returns the drop action to be used - either getUserDropAction in case that none was set programatically
         * or the action set with setDropAction(int)
         * @return
         */
        public int getDropAction() {
            if (dropAction == -1)
                return getUserDropAction();

            return dropAction;
        }

        /**
         * Returns the Drop-Actions supported by the source-component
         * @return
         */
        public int getSourceDropActions() {
            if (!isDrop())
                throw new IllegalStateException("getSourceDropActions called when isDrop = false");

            return this.source.getTransferHandler().getSourceActions(this.source);
        }

        /**
         * Returns the Transferable associated with this TransferSupport
         * @return
         */
        public Transferable getTransferable() {
            return this.transferable;
        }

        /**
         * Returns if dataFlavor is supported by this TransferSupport
         * @param dataFlavor
         * @return
         */
        public boolean isDataFlavorSupported(DataFlavor dataFlavor) {
            return transferable.isDataFlavorSupported(dataFlavor);
        }

        /**
         * Returns if this TransferSupport represents a drop
         * @return
         */
        public boolean isDrop() {
            return this.isDrop;
        }

        /**
         * Sets a drop action programmatically - it must be supported by the source component and a valid action
         * @param dropAction
         */
        public void setDropAction(int dropAction) {
            if (!isDrop())
                throw new IllegalStateException("isDrop was false when setDropAction was called");

            int action = dropAction & getSourceDropActions();
            if (action != COPY && action != MOVE && action != LINK) {
                throw new IllegalArgumentException("unsupported drop action " + dropAction);
            }

            this.dropAction = dropAction;
        }

        /*
        public void setShowDropLocation(boolean showDropLocation) {
        if(!isDrop())
            throw new IllegalStateException("isDrop was false when setShowDropLocation was called");
            
        }
        */
        protected void setDropLocation(SComponent component, SPoint point) {
            this.dropLocation = component.dropLocationForPoint(point);
        }

        /**
         * Returns the DropLocation for this  Transfer
         * @return
         */
        public DropLocation getDropLocation() {
            if (!isDrop())
                throw new IllegalStateException("isDrop was false when getDropLocation was called");

            return this.dropLocation;
        }
    }

    /**
     * Represents a location where dropped data should be inserted
     */
    public static class DropLocation {
        private final SPoint point;

        protected DropLocation(SPoint point) {
            this.point = point;
        }

        public final SPoint getDropPoint() {
            return this.point;
        }
    }
}