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

org.openide.nodes.PropertySupport Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.openide.nodes;

import java.beans.Beans;
import java.beans.FeatureDescriptor;
import java.beans.Introspector;
import java.beans.PropertyEditor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;

/** Support class for Node.Property.
 *
 * @param  the type of the represented property.
 *
 * @author Jan Jancura, Jaroslav Tulach, Ian Formanek
 */
public abstract class PropertySupport extends Node.Property {
    /** flag whether the property is readable */
    private boolean canR;

    /** flag whether the property is writable */
    private boolean canW;

    /** Constructs a new support.
    * @param name        the name of the property
    * @param type        the class type of the property
    * @param displayName the display name of the property
    * @param canR        whether the property is readable
    * @param canW        whether the property is writable
    */
    public PropertySupport(
        String name, Class type, String displayName, String shortDescription, boolean canR, boolean canW
    ) {
        super(type);
        this.setName(name);
        setDisplayName(displayName);
        setShortDescription(shortDescription);
        this.canR = canR;
        this.canW = canW;
    }

    /* Can read the value of the property.
    * Returns the value passed into constructor.
    * @return true if the read of the value is supported
    */
    @Override
    public boolean canRead() {
        return canR;
    }

    /* Can write the value of the property.
    * Returns the value passed into constructor.
    * @return true if the read of the value is supported
    */
    @Override
    public boolean canWrite() {
        return canW;
    }

    /**
     * Like {@link Class#cast} but handles primitive types.
     * See JDK #6456930.
     */
    @SuppressWarnings("unchecked")
    static  T cast(Class c, Object o) {
        if (c.isPrimitive()) {
            // Could try to actually type-check it, but never mind.
            return (T) o;
        } else {
            return c.cast(o);
        }
    }

    /**
     * Fluent wrapper method for {@link #setDisplayName(java.lang.String)}.
     *
     * @param displayName the display name to set.
     * @since 7.62
     *
     * @return this instance
     */
    public final PropertySupport withDisplayName(String displayName) {
        setDisplayName(displayName);
        return this;
    }

    /**
     * Fluent wrapper method for {@link #setShortDescription(java.lang.String)}.
     *
     * @param shortDescription short description
     * @since 7.62
     * @return this instance
     */
    public final PropertySupport withShortDescription(String shortDescription) {
        setShortDescription(shortDescription);
        return this;
    }

    /**
     * Creates a "virtual" property where getter and setter are backed by the
     * provided {@link Supplier} and {@link Consumer} functional interfaces.
     * @param  the type of the property
     * @param name the name of the property
     * @param valueType the type of the property
     * @param supplier the getter functional interface, can be {@code null} for write-only properties.
     * @param consumer the setter functional interface, can be {@code null} for read-only properties.
     *
     * @since 7.62
     * @return a {@link PropertySupport} instance where getter and setter are
     *         backed by the provided functional interfaces.
     */
    public static  PropertySupport readWrite(String name, Class valueType, Supplier supplier, Consumer consumer) {
        return new FunctionalProperty<>(name, valueType, supplier, consumer);
    }

    /**
     * Creates a read-only "virtual" property where getter is backed by the
     * provided {@link Supplier} functional interface.
     * @param  the type of the property
     * @param name the name of the property
     * @param valueType the type of the property
     * @param supplier the getter functional interface.
     *
     * @since 7.62
     * @return a read-only {@link PropertySupport} instance where getter is
     *         backed by the provided functional interface.
     */
    public static  PropertySupport readOnly(String name, Class valueType, Supplier supplier) {
        return new FunctionalProperty<>(name, valueType, supplier, null);
    }
    /**
     * Creates a write-only "virtual" property where setter is backed by the
     * provided {@link Consumer} functional interface.
     * @param  the type of the property
     * @param name the name of the property
     * @param valueType the type of the property
     * @param consumer the setter functional interface.
     *
     * @since 7.62
     * @return a write-only {@link PropertySupport} instance where setter is
     *         backed by the provided functional interface.
     */
    public static  PropertySupport writeOnly(String name, Class valueType, Consumer consumer) {
        return new FunctionalProperty<>(name, valueType, null, consumer);
    }

    /**
     * Support for properties from Java Reflection.
     * Since 7.19, the {@link FeatureDescriptor#getName} will be set automatically.
     */
    public static class Reflection extends Node.Property {
        /** Instance of a bean. */
        protected Object instance;

        /** setter method */
        private Method setter;

        /** getter method */
        private Method getter;

        /** class of property editor */
        private Class propertyEditorClass;

        /** Create a support with method objects specified.
        * The methods must be public.
        * @param instance (Bean) object to work on
        * @param valueType type of the property
        * @param getter getter method, can be null
        * @param setter setter method, can be null
        * @throws IllegalArgumentException if the methods are not public
        */
        public Reflection(Object instance, Class valueType, Method getter, Method setter) {
            super(valueType);

            if ((getter != null) && !Modifier.isPublic(getter.getModifiers())) {
                throw new IllegalArgumentException("Cannot use a non-public getter " + getter); // NOI18N
            }

            if ((setter != null) && !Modifier.isPublic(setter.getModifiers())) {
                throw new IllegalArgumentException("Cannot use a non-public setter " + setter); // NOI18N
            }

            if (getter != null) {
                setName(Introspector.decapitalize(getter.getName().replaceFirst("^(get|is|has)", "")));
            } else if (setter != null) {
                setName(Introspector.decapitalize(setter.getName().replaceFirst("^set", "")));
            }

            this.instance = instance;
            this.setter = setter;
            this.getter = getter;
        }

        /** Create a support with methods specified by name.
        * The instance class will be examined for the named methods.
        * But if the instance class is not public, the nearest public superclass
        * will be used instead, so that the getters and setters remain accessible.
        * @param instance (Bean) object to work on
        * @param valueType type of the property
        * @param getter name of getter method, can be null
        * @param setter name of setter method, can be null
        * @exception NoSuchMethodException if the getter or setter methods cannot be found
        */
        public Reflection(Object instance, Class valueType, String getter, String setter)
        throws NoSuchMethodException {
            this(
                instance, valueType,
                (
            // find the getter ()
            getter == null) ? null : findAccessibleClass(instance.getClass()).getMethod(getter),
                (
            // find the setter (valueType)
            setter == null) ? null : findAccessibleClass(instance.getClass()).getMethod(
                    setter, new Class[] { valueType }
                )
            );
        }

        // [PENDING] should use Beans API in case there is overriding BeanInfo  --jglick

        /** Create a support based on the property name.
        * The getter and setter methods are constructed by capitalizing the first
        * letter in the name of propety and prefixing it with get and
        * set, respectively.
        *
        * @param instance object to work on
        * @param valueType type of the property
        * @param property name of property
        * @exception NoSuchMethodException if the getter or setter methods cannot be found
        */
        public Reflection(Object instance, Class valueType, String property)
        throws NoSuchMethodException {
            this(
                instance, valueType, findGetter(instance, valueType, property),
                findAccessibleClass(instance.getClass()).getMethod(
                    firstLetterToUpperCase(property, "set"), valueType
                )
            );
        }

        /** Find the nearest superclass (or same class) that is public to this one. */
        private static  Class findAccessibleClass(Class clazz) {
            if (Modifier.isPublic(clazz.getModifiers())) {
                return clazz;
            } else {
                Class sup = clazz.getSuperclass();

                if (sup == null) {
                    return Object.class; // handle interfaces
                }

                return findAccessibleClass(sup);
            }
        }

        /** Helper method to convert the first letter of a string to uppercase.
        * And prefix the string with some next string.
        */
        private static String firstLetterToUpperCase(String s, String pref) {
            switch (s.length()) {
            case 0:
                return pref;

            case 1:
                return pref + Character.toUpperCase(s.charAt(0));

            default:
                return pref + Character.toUpperCase(s.charAt(0)) + s.substring(1);
            }
        }

        // Finds the proper getter
        private static Method findGetter(Object instance, Class valueType, String property)
        throws NoSuchMethodException {
            NoSuchMethodException nsme;

            try {
                return findAccessibleClass(instance.getClass()).getMethod(
                    firstLetterToUpperCase(property, "get")
                );
            } catch (NoSuchMethodException e) {
                if (valueType != boolean.class) {
                    throw e;
                } else {
                    nsme = e;
                }
            }

            // Is of type boolean and "get" getter does not exist
            try {
                return findAccessibleClass(instance.getClass()).getMethod(
                    firstLetterToUpperCase(property, "is")
                );
            } catch (NoSuchMethodException e) {
                throw e;
            }
        }

        /* Can read the value of the property.
        * @return true if the read of the value is supported
        */
        public boolean canRead() {
            return getter != null;
        }

        /* Getter for the value.
        * @return the value of the property
        * @exception IllegalAccessException cannot access the called method
        * @exception IllegalArgumentException wrong argument
        * @exception InvocationTargetException an exception during invocation
        */
        public T getValue() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            if (getter == null) {
                throw new IllegalAccessException();
            }

            Object valideInstance = Beans.getInstanceOf(instance, getter.getDeclaringClass());

            try {
                try {
                    return cast(getValueType(), getter.invoke(valideInstance));
                } catch (IllegalAccessException ex) {
                    try {
                        getter.setAccessible(true);

                        return cast(getValueType(), getter.invoke(valideInstance));
                    } finally {
                        getter.setAccessible(false);
                    }
                }
            } catch (IllegalArgumentException iae) {
                //Provide a better message for debugging
                StringBuilder sb = new StringBuilder("Attempted to invoke method ");
                sb.append(getter.getName());
                sb.append(" from class ");
                sb.append(getter.getDeclaringClass().getName());
                sb.append(" on an instance of ");
                sb.append(valideInstance.getClass().getName());
                sb.append(" Problem:");
                sb.append(iae.getMessage());
                throw (IllegalArgumentException) new IllegalArgumentException(sb.toString()).initCause(iae);
            }
        }

        /* Can write the value of the property.
        * @return true if the read of the value is supported
        */
        @Override
        public boolean canWrite() {
            return setter != null;
        }

        /* Setter for the value.
        * @param val the value of the property
        * @exception IllegalAccessException cannot access the called method
        * @exception IllegalArgumentException wrong argument
        * @exception InvocationTargetException an exception during invocation
        */
        @Override
        public void setValue(T val)
        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            if (setter == null) {
                throw new IllegalAccessException();
            }

            Object valideInstance = Beans.getInstanceOf(instance, setter.getDeclaringClass());

            try {
                setter.invoke(valideInstance, val);
            } catch (IllegalAccessException ex) {
                try {
                    setter.setAccessible(true);
                    setter.invoke(valideInstance, val);
                } finally {
                    setter.setAccessible(false);
                }
            }
        }

        /* Returns property editor for this property.
        * @return the property editor or null if there should not be
        *    any editor.
        */
        @Override
        public PropertyEditor getPropertyEditor() {
            if (propertyEditorClass != null) {
                try {
                    return propertyEditorClass.getDeclaredConstructor().newInstance();
                } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }

            return super.getPropertyEditor();
        }

        /** Set the property editor explicitly.
        * @param clazz class type of the property editor
        */
        public void setPropertyEditorClass(Class clazz) {
            propertyEditorClass = clazz;
        }
    }

    /** A simple read/write property.
    * Subclasses should implement
    * {@link #getValue} and {@link #setValue}.
    */
    public abstract static class ReadWrite extends PropertySupport {
        /** Construct a new support.
        * @param name        the name of the property
        * @param type        the class type of the property
        * @param displayName the display name of the property
        * @param shortDescription a short description of the property
        */
        public ReadWrite(String name, Class type, String displayName, String shortDescription) {
            super(name, type, displayName, shortDescription, true, true);
        }
    }

    private static final class FunctionalProperty extends PropertySupport {
        private final Supplier supplier;
        private final Consumer consumer;

        public FunctionalProperty(String name, Class type, Supplier supplier, Consumer consumer) {
            super(name, type, null, null, supplier != null, consumer != null);
            this.supplier = supplier;
            this.consumer = consumer;
        }

        @Override
        public T getValue() throws IllegalAccessException {
            if (supplier != null) {
                return supplier.get();
            } else {
                throw new IllegalAccessException("Cannod read from WriteOnly property"); // NOI18N
            }
        }

        @Override
        public void setValue(T val) throws IllegalAccessException {
            if (consumer != null) {
                consumer.accept(val);
            } else {
                throw new IllegalAccessException("Cannot write to ReadOnly property"); // NOI18N
            }
        }
    }

    /** A simple read-only property.
    * Subclasses should implement {@link #getValue}.
    */
    public abstract static class ReadOnly extends PropertySupport {
        /** Construct a new support.
        * @param name        the name of the property
        * @param type        the class type of the property
        * @param displayName the display name of the property
        * @param shortDescription a short description of the property
        */
        public ReadOnly(String name, Class type, String displayName, String shortDescription) {
            super(name, type, displayName, shortDescription, true, false);
        }

        /* Setter for the value.
        * @param val the value of the property
        * @exception IllegalAccessException cannot access the called method
        * @exception IllegalArgumentException wrong argument
        * @exception InvocationTargetException an exception during invocation
        */
        @Override
        public void setValue(T val)
        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            throw new IllegalAccessException("Cannot write to ReadOnly property"); // NOI18N
        }
    }

    /** A simple write-only property.
    * Subclasses should implement {@link #setValue}.
    */
    public abstract static class WriteOnly extends PropertySupport {
        /** Construct a new support.
        * @param name        the name of the property
        * @param type        the class type of the property
        * @param displayName the display name of the property
        * @param shortDescription a short description of the property
        */
        public WriteOnly(String name, Class type, String displayName, String shortDescription) {
            super(name, type, displayName, shortDescription, false, true);
        }

        /* Getter for the value.
        * @return the value of the property
        * @exception IllegalAccessException cannot access the called method
        * @exception IllegalArgumentException wrong argument
        * @exception InvocationTargetException an exception during invocation
        */
        @Override
        public T getValue() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            throw new IllegalAccessException("Cannod read from WriteOnly property"); // NOI18N
        }
    }

    /** Support for the name property of a node. Delegates {@link #setValue} and {@link #getValue}
    * to {@link Node#setName} and {@link Node#getName}.
    */
    public static final class Name extends PropertySupport {
        /** The node to which we delegate the work. */
        private final Node node;

        /** Create the name property for a node with the standard name and hint.
        * @param node the node
        */
        public Name(final Node node) {
            this(node, NbBundle.getMessage(PropertySupport.class, "CTL_StandardName"),
                NbBundle.getMessage(PropertySupport.class, "CTL_StandardHint")
            );
        }

        /** Create the name property for a node.
        * @param node the node
        * @param propName name of the "name" property
        * @param hint hint message for the "name" property
        */
        public Name(final Node node, final String propName, final String hint) {
            super(Node.PROP_NAME, String.class, propName, hint, true, node.canRename());
            this.node = node;
        }

        /* Getter for the value. Delegates to Node.getName().
        * @return the name
        */
        @Override
        public String getValue() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            return node.getName();
        }

        /* Setter for the value. Delegates to Node.setName().
        * @param val new name
        */
        @Override
        public void setValue(String val)
        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            Object oldName = node.getName();
            node.setName(val);
            node.firePropertyChange(Node.PROP_NAME, oldName, val);
        }
    }
     // end of Name inner class
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy