Package org.apache.felix.sigil.common.model

Source Code of org.apache.felix.sigil.common.model.ModelElementSupport$SetPropertyAdapter

/*
* 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.apache.felix.sigil.common.model;

import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Logger;

import org.apache.felix.sigil.common.model.annotations.Required;

public class ModelElementSupport implements Serializable
{

    private static final Logger log = Logger.getLogger(ModelElementSupport.class.getName());

    private static final long serialVersionUID = 1L;

    private static final PropertyAdapter[] EMPTY_PROPS = new PropertyAdapter[] {};
    private static final IModelElement[] EMPTY_ELEMENTS = new IModelElement[] {};
    private static final Object[] ZERO_ARGS = new Object[] {};
    private static final Class<?>[] ZERO_PARAMS = new Class[] {};

    private static final WeakHashMap<Class<?>, SoftReference<ChildAdapter[]>> adapterCache = new WeakHashMap<Class<?>, SoftReference<ChildAdapter[]>>();;
    private static final WeakHashMap<Class<?>, SoftReference<PropertyAdapter[]>> propertyCache = new WeakHashMap<Class<?>, SoftReference<PropertyAdapter[]>>();;

    private IModelElement target;

    private transient SoftReference<PropertyAdapter[]> propertyReference;
    private transient SoftReference<ChildAdapter[]> childrenReference;
    private transient SoftReference<Set<String>> propertyNameReference;

    public ModelElementSupport(IModelElement target)
    {
        this.target = target;
    }

    public void setProperty(String name, Object value) throws NoSuchMethodException
    {
        PropertyAdapter p = findProperty(name, value);
        if (p == null)
        {
            throw new NoSuchMethodException("No such property " + name + " for type "
                + target.getClass());
        }
        invoke(target, p.getWriteMethod(), value);
    }

    public void addProperty(String name, Object value) throws NoSuchMethodException
    {
        PropertyAdapter p = findProperty(name, value);
        if (p == null)
        {
            throw new NoSuchMethodException("No such property " + name + " for type "
                + target.getClass());
        }
        invoke(target, p.getAddMethod(), value);
    }

    public void removeProperty(String name, Object value) throws NoSuchMethodException
    {
        PropertyAdapter p = findProperty(name, value);
        if (p == null)
        {
            throw new NoSuchMethodException("No such property " + name + " for type "
                + target.getClass());
        }
        invoke(target, p.getRemoveMethod(), value);
    }

    public Object getProperty(String name) throws NoSuchMethodException
    {
        PropertyAdapter p = findProperty(name, null);
        if (p == null)
        {
            throw new NoSuchMethodException("No such property " + name + " for type "
                + target.getClass());
        }
        return invoke(target, p.getReadMethod(), ZERO_ARGS);
    }

    public Set<String> getPropertyNames()
    {
        Set<String> names = propertyNameReference == null ? null
            : propertyNameReference.get();

        if (names == null)
        {
            names = new HashSet<String>();

            PropertyAdapter[] props = cachedProps(target.getClass());
            for (PropertyAdapter prop : props)
            {
                names.add(prop.getName());
            }

            propertyNameReference = new SoftReference<Set<String>>(names);
        }

        return names;
    }

    public Object getDefaultPropertyValue(String name)
    {
        try
        {
            Method m = target.getClass().getMethod(makeDefaultPropertyValue(name),
                ZERO_PARAMS);
            return invoke(target, m, ZERO_ARGS);
        }
        catch (SecurityException e)
        {
            throw new UndeclaredThrowableException(e);
        }
        catch (NoSuchMethodException e)
        {
            // fine no default
            return null;
        }
    }

    public Class<?> getPropertyType(String name) throws NoSuchMethodException
    {
        PropertyAdapter p = findProperty(name, null);
        if (p == null)
        {
            throw new NoSuchMethodException("No such property " + name + " for type "
                + target.getClass());
        }
        return p.getPropertyType();
    }

    @SuppressWarnings("unchecked")
    public <T extends IModelElement> T[] childrenOfType(Class<T> type)
    {
        ChildAdapter[] adapters = cachedAdapters();

        if (adapters.length == 0)
        {
            // return (T[]) EMPTY_ELEMENTS;
            return ((T[]) Array.newInstance(type, 0));
        }

        ArrayList<T> elements = new ArrayList<T>();

        for (ChildAdapter adapter : adapters)
        {
            Collection<? extends IModelElement> val = adapter.members(target);

            for (IModelElement e : val)
            {
                if (type.isInstance(e))
                {
                    elements.add((T) e);
                }
            }
        }

        //return elements.toArray( (T[]) EMPTY_ELEMENTS );
        return elements.toArray((T[]) Array.newInstance(type, elements.size()));
    }

    public IModelElement[] children()
    {
        ChildAdapter[] adapters = cachedAdapters();

        if (adapters.length == 0)
        {
            return EMPTY_ELEMENTS;
        }

        ArrayList<IModelElement> elements = new ArrayList<IModelElement>();

        for (ChildAdapter adapter : adapters)
        {
            elements.addAll(adapter.members(target));
        }

        return elements.toArray(EMPTY_ELEMENTS);
    }

    public boolean addChild(IModelElement element) throws InvalidModelException
    {
        if (element.getParent() == null)
        {
            ChildAdapter[] adapters = cachedAdapters();

            if (adapters.length > 0)
            {
                for (ChildAdapter adapter : adapters)
                {
                    if (adapter.add(target, element))
                    {
                        element.setParent(target);
                        return true;
                    }
                }
            }
        }

        return false;
    }

    public boolean removeChild(IModelElement element)
    {
        if (element.getParent() == target)
        {
            ChildAdapter[] adapters = cachedAdapters();

            if (adapters.length > 0)
            {
                for (ChildAdapter adapter : adapters)
                {
                    if (adapter.remove(target, element))
                    {
                        element.setParent(null);
                        return true;
                    }
                }
            }
        }

        return false;
    }

    public Set<Class<? extends IModelElement>> getChildrenTypes(boolean required)
    {
        ChildAdapter[] adapters = cachedAdapters();

        if (adapters.length == 0)
        {
            return Collections.emptySet();
        }

        HashSet<Class<? extends IModelElement>> types = new HashSet<Class<? extends IModelElement>>();

        for (ChildAdapter adapter : adapters)
        {
            if (adapter.isRequired() == required)
            {
                Class<? extends IModelElement> type = adapter.getType();

                if (type != null)
                {
                    types.add(type);
                }
            }
        }

        return types;
    }

    private PropertyAdapter findProperty(String name, Object value)
    {
        PropertyAdapter[] props = propertyReference == null ? null
            : propertyReference.get();

        if (props == null)
        {
            props = cachedProps(target.getClass());
            propertyReference = new SoftReference<PropertyAdapter[]>(props);
        }

        for (PropertyAdapter prop : props)
        {
            if (prop.getName().equals(name)
                && (value == null || prop.getRawType().isAssignableFrom(value.getClass())))
            {
                return prop;
            }
        }

        return null;
    }

    private static synchronized PropertyAdapter[] cachedProps(
        Class<? extends IModelElement> type)
    {
        SoftReference<PropertyAdapter[]> ref = propertyCache.get(type);

        PropertyAdapter[] props = ref == null ? null : ref.get();

        if (props == null)
        {
            props = loadProps(type);
            propertyCache.put(type, new SoftReference<PropertyAdapter[]>(props));
        }

        return props;
    }

    private static PropertyAdapter[] loadProps(Class<? extends IModelElement> type)
    {
        ArrayList<PropertyAdapter> props = new ArrayList<PropertyAdapter>();
        for (Method m : type.getMethods())
        {
            if (isValidProperty(m))
            {
                try
                {
                    PropertyAdapter p = new PropertyAdapter(m, type);
                    props.add(p);
                }
                catch (NoSuchMethodException e)
                {
                    // fine not a bean method
                    log.finer("Invalid bean property method " + m + ": " + e.getMessage());
                }
            }
        }

        return props.toArray(EMPTY_PROPS);
    }

    private static boolean isValidProperty(Method m)
    {
        return m.getName().startsWith("get") && m.getParameterTypes().length == 0
            && !m.getDeclaringClass().equals(Object.class)
            && !IModelElement.class.isAssignableFrom(m.getReturnType());
    }

    private static String makeDefaultPropertyValue(String name)
    {
        return "getDefault" + capitalise(name);
    }

    private static String capitalise(String name)
    {
        return Character.toUpperCase(name.charAt(0)) + name.substring(1);
    }

    private static String decapitalise(String substring)
    {
        return Character.toLowerCase(substring.charAt(0)) + substring.substring(1);
    }

    private ChildAdapter[] cachedAdapters()
    {
        ChildAdapter[] adapters = childrenReference == null ? null
            : childrenReference.get();

        if (adapters == null)
        {
            adapters = loadAdapters(target);
            childrenReference = new SoftReference<ChildAdapter[]>(adapters);
        }

        return adapters;
    }

    private static synchronized ChildAdapter[] loadAdapters(IModelElement target)
    {
        Class<? extends IModelElement> type = target.getClass();
        SoftReference<ChildAdapter[]> ref = adapterCache.get(type);

        ChildAdapter[] adapters = ref == null ? null : ref.get();

        if (adapters == null)
        {
            adapters = buildAdapters(type);
            adapterCache.put(type, new SoftReference<ChildAdapter[]>(adapters));
        }

        return adapters;
    }

    private static ChildAdapter[] buildAdapters(Class<? extends IModelElement> type)
    {
        ArrayList<ChildAdapter> adapters = new ArrayList<ChildAdapter>();

        for (Method m : type.getMethods())
        {
            ChildAdapter adapter = null;

            if (isValidGetProperty(m))
            {
                adapter = buildGetAdapter(m);
            }
            else if (isValidSetProperty(m))
            {
                adapter = buildSetAdapter(m);
            }
            else if (isValidAddProperty(m))
            {
                adapter = buildAddAdapter(m);
            }
            else if (isValidRemoveProperty(m))
            {
                adapter = buildRemoveAdapter(m);
            }

            if (adapter != null)
            {
                adapters.add(adapter);
            }
        }

        return adapters.toArray(new ChildAdapter[adapters.size()]);
    }

    private static ChildAdapter buildGetAdapter(Method m)
    {
        if (IModelElement.class.isAssignableFrom(m.getReturnType()))
        {
            return new GetPropertyAdapter(m);
        }
        else if (Collection.class.isAssignableFrom(m.getReturnType()))
        {
            return new GetCollectionAdapter(m);
        }
        else if (isModelArray(m.getReturnType()))
        {
            return new GetArrayAdapter(m);
        }
        else
        {
            return null;
        }
    }

    private static ChildAdapter buildSetAdapter(Method m)
    {
        if (IModelElement.class.isAssignableFrom(m.getParameterTypes()[0]))
        {
            return new SetPropertyAdapter(m);
        }
        else
        {
            return null;
        }
    }

    private static ChildAdapter buildAddAdapter(Method m)
    {
        if (IModelElement.class.isAssignableFrom(m.getParameterTypes()[0]))
        {
            return new AddPropertyAdapter(m);
        }
        else
        {
            return null;
        }
    }

    private static ChildAdapter buildRemoveAdapter(Method m)
    {
        if (IModelElement.class.isAssignableFrom(m.getParameterTypes()[0]))
        {
            return new RemovePropertyAdapter(m);
        }
        else
        {
            return null;
        }
    }

    private static boolean isValidRemoveProperty(Method m)
    {
        return m.getParameterTypes().length == 1 && m.getName().startsWith("remove")
            && !isDeclared(ICompoundModelElement.class, m);
    }

    private static boolean isValidAddProperty(Method m)
    {
        return m.getParameterTypes().length == 1 && m.getName().startsWith("add")
            && !isDeclared(ICompoundModelElement.class, m);
    }

    private static boolean isDeclared(Class<? extends IModelElement> element, Method m)
    {
        try
        {
            element.getMethod(m.getName(), m.getParameterTypes());
            return true;
        }
        catch (SecurityException e)
        {
            throw new UndeclaredThrowableException(e);
        }
        catch (NoSuchMethodException e)
        {
            return false;
        }
    }

    private static boolean isValidSetProperty(Method m)
    {
        return m.getParameterTypes().length == 1 && m.getName().startsWith("set")
            && !isDeclared(IModelElement.class, m);
    }

    private static boolean isValidGetProperty(Method m)
    {
        return m.getParameterTypes().length == 0 && m.getName().startsWith("get")
            && !isDeclared(IModelElement.class, m)
            && !isDeclared(ICompoundModelElement.class, m);
    }

    private static Object invoke(Object target, Method m, Object... args)
    {
        try
        {
            return m.invoke(target, args);
        }
        catch (IllegalArgumentException e)
        {
            // this should already have been tested
            throw new IllegalStateException(e);
        }
        catch (IllegalAccessException e)
        {
            throw new UndeclaredThrowableException(e);
        }
        catch (InvocationTargetException e)
        {
            throw new UndeclaredThrowableException(e.getCause());
        }
    }

    private static class PropertyAdapter
    {

        String prop;
        String name;
        Method g;
        Method s;
        Method a;
        Method r;
        Class<?> propertyType;

        public PropertyAdapter(Method g, Class<?> type) throws SecurityException, NoSuchMethodException
        {
            if (g.getReturnType().isArray()
                || Iterable.class.isAssignableFrom(g.getReturnType()))
            {
                prop = g.getName().substring(3);
                // remove trailing s - as in addWibble, removeWibble, getWibbles
                prop = prop.substring(0, prop.length() - 1);
                name = decapitalise(prop);
                a = find("add", prop, g.getReturnType(), type);
                propertyType = a.getParameterTypes()[0];
                r = find("remove", prop, g.getReturnType(), type);
                if (r.getParameterTypes()[0] != propertyType)
                {
                    throw new NoSuchMethodException(
                        "Add remove property method types do not match");
                }
                propertyType = Array.newInstance(propertyType, 0).getClass();
            }
            else
            {
                prop = g.getName().substring(3);
                name = decapitalise(prop);
                propertyType = g.getReturnType();
                s = find("set", prop, propertyType, type);
            }

            this.g = g;
        }

        public Class<?> getRawType()
        {
            return propertyType.isArray() ? propertyType.getComponentType()
                : propertyType;
        }

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

        public Method getReadMethod()
        {
            return g;
        }

        public Method getAddMethod() throws NoSuchMethodException
        {
            if (a == null)
            {
                throw new NoSuchMethodException("No such method add" + prop);
            }

            return a;
        }

        public Method getRemoveMethod() throws NoSuchMethodException
        {
            if (r == null)
            {
                throw new NoSuchMethodException("No such method remove" + prop);
            }

            return r;
        }

        public Method getWriteMethod() throws NoSuchMethodException
        {
            if (s == null)
            {
                throw new NoSuchMethodException("No such method set" + prop);
            }

            return s;
        }

        @Override
        public String toString()
        {
            return "PropertyAdapter[" + name + "]";
        }

        private Method find(String prefix, String prop, Class<?> returnType, Class<?> type)
            throws SecurityException, NoSuchMethodException
        {
            String methodName = prefix + prop;

            if (returnType.isArray())
            {
                Class<?> t = returnType.getComponentType();
                return type.getMethod(methodName, new Class[] { t });
            }
            else if (Iterable.class.isAssignableFrom(returnType))
            {
                Method f = null;
                for (Method m : type.getMethods())
                {
                    if (m.getParameterTypes().length == 1
                        && m.getName().equals(methodName)
                        && !IModelElement.class.isAssignableFrom(m.getParameterTypes()[0]))
                    {
                        if (f == null)
                        {
                            f = m;
                        }
                        else
                        {
                            throw new NoSuchMethodException("Found duplicate "
                                + methodName);
                        }
                    }
                }
                if (f == null)
                {
                    throw new NoSuchMethodException("No such method " + methodName);
                }

                return f;
            }
            else
            {
                return type.getMethod(methodName, new Class[] { returnType });
            }
        }

        public String getName()
        {
            return name;
        }

    }

    private static abstract class ChildAdapter
    {
        Method m;

        ChildAdapter(Method m)
        {
            this.m = m;
        }

        public boolean isRequired()
        {
            return m.isAnnotationPresent(Required.class);
        }

        boolean add(Object target, IModelElement element)
        {
            return false;
        }

        abstract Class<IModelElement> getType();

        boolean remove(Object target, IModelElement element)
        {
            return false;
        }

        Collection<? extends IModelElement> members(Object target)
        {
            return Collections.emptyList();
        }

        @Override
        public String toString()
        {
            return "ChildAdapter[ " + m.getName() + "]";
        }
    }

    private static class GetPropertyAdapter extends ChildAdapter
    {
        GetPropertyAdapter(Method m)
        {
            super(m);
        }

        @Override
        Collection<? extends IModelElement> members(Object target)
        {
            IModelElement member = (IModelElement) invoke(target, m, ZERO_ARGS);
            if (member == null)
            {
                return Collections.emptyList();
            }
            else
            {
                return Collections.<IModelElement> singleton(member);
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        Class<IModelElement> getType()
        {
            return (Class<IModelElement>) m.getReturnType();
        }
    }

    private static class GetCollectionAdapter extends ChildAdapter
    {
        public GetCollectionAdapter(Method m)
        {
            super(m);
        }

        @SuppressWarnings("unchecked")
        @Override
        Collection<? extends IModelElement> members(Object target)
        {
            Collection members = (Collection) invoke(target, m, ZERO_ARGS);
            if (members == null)
            {
                return Collections.emptyList();
            }
            else
            {
                ArrayList<IModelElement> safe = new ArrayList<IModelElement>(
                    members.size());
                for (Object o : members)
                {
                    if (o instanceof IModelElement)
                    {
                        safe.add((IModelElement) o);
                    }
                }
                return safe;
            }
        }

        @Override
        Class<IModelElement> getType()
        {
            // impossible to get type of a collection as erasure removes generics info
            return null;
        }

    }

    private static class GetArrayAdapter extends ChildAdapter
    {
        public GetArrayAdapter(Method m)
        {
            super(m);
        }

        @Override
        Collection<? extends IModelElement> members(Object target)
        {
            IModelElement[] array = (IModelElement[]) invoke(target, m, ZERO_ARGS);
            if (array == null || array.length == 0)
            {
                return Collections.emptyList();
            }
            else
            {
                return (Collection<? extends IModelElement>) Arrays.asList(array);
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        Class<IModelElement> getType()
        {
            return (Class<IModelElement>) m.getReturnType().getComponentType();
        }
    }

    private static class SetPropertyAdapter extends ChildAdapter
    {
        public SetPropertyAdapter(Method m)
        {
            super(m);
        }

        @Override
        boolean add(Object target, IModelElement element)
        {
            if (m.getParameterTypes()[0].isAssignableFrom(element.getClass()))
            {
                invoke(target, m, new Object[] { element });
                return true;
            }
            else
            {
                return false;
            }
        }

        @Override
        boolean remove(Object target, IModelElement element)
        {
            if (m.getParameterTypes()[0].isAssignableFrom(element.getClass()))
            {
                invoke(target, m, new Object[] { null });
                return true;
            }
            else
            {
                return false;
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        Class<IModelElement> getType()
        {
            return (Class<IModelElement>) m.getParameterTypes()[0];
        }
    }

    private static class AddPropertyAdapter extends ChildAdapter
    {
        public AddPropertyAdapter(Method m)
        {
            super(m);
        }

        @Override
        boolean add(Object target, IModelElement element)
        {
            if (m.getParameterTypes()[0].isAssignableFrom(element.getClass()))
            {
                invoke(target, m, new Object[] { element });
                return true;
            }
            else
            {
                return false;
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        Class<IModelElement> getType()
        {
            return (Class<IModelElement>) m.getParameterTypes()[0];
        }
    }

    private static class RemovePropertyAdapter extends ChildAdapter
    {

        public RemovePropertyAdapter(Method m)
        {
            super(m);
        }

        @Override
        boolean remove(Object target, IModelElement element)
        {
            if (m.getParameterTypes()[0].isAssignableFrom(element.getClass()))
            {
                invoke(target, m, new Object[] { element });
                return true;
            }
            else
            {
                return false;
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        Class<IModelElement> getType()
        {
            return (Class<IModelElement>) m.getParameterTypes()[0];
        }
    }

    private static boolean isModelArray(Class<?> returnType)
    {
        return returnType.isArray()
            && IModelElement.class.isAssignableFrom(returnType.getComponentType());
    }
}
TOP

Related Classes of org.apache.felix.sigil.common.model.ModelElementSupport$SetPropertyAdapter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.