001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016-2018 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.core.internal;
020
021import java.lang.reflect.Field;
022import java.lang.reflect.InvocationTargetException;
023import org.jgrapes.core.Channel;
024import org.jgrapes.core.ComponentType;
025import org.jgrapes.core.Manager;
026import org.jgrapes.core.NamedChannel;
027import org.jgrapes.core.annotation.ComponentManager;
028import org.jgrapes.core.annotation.Handler;
029
030/**
031 * The ComponentProxy is a special ComponentVertex that references the
032 * object implementing the Component interface (instead of being
033 * its base class).
034 */
035public final class ComponentProxy extends ComponentVertex {
036
037    /** The reference to the actual component. */
038    private final ComponentType component;
039    /** The referenced component's channel. */
040    private Channel componentChannel;
041
042    private static Field getManagerField(Class<?> clazz) {
043        try {
044            while (true) {
045                for (Field field : clazz.getDeclaredFields()) {
046                    if (Manager.class.isAssignableFrom(field.getType())
047                        && field
048                            .getAnnotation(ComponentManager.class) != null) {
049                        return field;
050                    }
051                }
052                clazz = clazz.getSuperclass();
053                if (clazz == null) {
054                    throw new IllegalArgumentException(
055                        "Components must have a manager attribute");
056                }
057            }
058        } catch (SecurityException e) {
059            throw (RuntimeException) (new IllegalArgumentException(
060                "Cannot access component's manager attribute")).initCause(e);
061        }
062    }
063
064    private static Channel getComponentChannel(Field field) {
065        ComponentManager cma = field.getAnnotation(ComponentManager.class);
066        if (cma.channel() != Handler.NoChannel.class) {
067            if (cma.channel() != Channel.BROADCAST.defaultCriterion()) {
068                try {
069                    return cma.channel().getConstructor().newInstance();
070                } catch (InstantiationException // NOPMD
071                        | IllegalAccessException | IllegalArgumentException
072                        | InvocationTargetException | NoSuchMethodException
073                        | SecurityException e) {
074                    // Ignored
075                }
076            }
077            return Channel.BROADCAST;
078        }
079        if (!cma.namedChannel().isEmpty()) {
080            return new NamedChannel(cma.namedChannel());
081        }
082        return Channel.SELF;
083    }
084
085    /**
086     * Create a new component proxy for the component and assign it to 
087     * the specified field which must be of type {@link Manager}.
088     * 
089     * @param field the field that gets the proxy assigned
090     * @param componentChannel the componen't channel
091     * @param component the component
092     */
093    private ComponentProxy(
094            Field field, ComponentType component, Channel componentChannel) {
095        this.component = component;
096        try {
097            field.set(component, this);
098            if (componentChannel == null) {
099                componentChannel = getComponentChannel(field);
100            }
101            if (componentChannel.equals(Channel.SELF)) {
102                componentChannel = this;
103            }
104            this.componentChannel = componentChannel;
105            initComponentsHandlers(null);
106        } catch (SecurityException | IllegalAccessException e) {
107            throw (RuntimeException) (new IllegalArgumentException(
108                "Cannot access component's manager attribute")).initCause(e);
109        }
110    }
111
112    /**
113     * Return the component node for a component that is represented
114     * by a proxy in the tree.
115     * 
116     * @param component the component
117     * @param componentChannel the component's channel
118     * @return the node representing the component in the tree
119     */
120    @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis",
121        "PMD.AvoidAccessibilityAlteration", "PMD.ConfusingTernary" })
122    /* default */ static ComponentVertex getComponentProxy(
123            ComponentType component, Channel componentChannel) {
124        ComponentProxy componentProxy;
125        try {
126            Field field = getManagerField(component.getClass());
127            synchronized (component) {
128                if (!field.canAccess(component)) {
129                    field.setAccessible(true);
130                    componentProxy = (ComponentProxy) field.get(component);
131                    if (componentProxy == null) {
132                        componentProxy = new ComponentProxy(
133                            field, component, componentChannel);
134                    }
135                    field.setAccessible(false);
136                } else {
137                    componentProxy = (ComponentProxy) field.get(component);
138                    if (componentProxy == null) {
139                        componentProxy = new ComponentProxy(
140                            field, component, componentChannel);
141                    }
142                }
143            }
144        } catch (SecurityException | IllegalAccessException e) {
145            throw (RuntimeException) (new IllegalArgumentException(
146                "Cannot access component's manager attribute")).initCause(e);
147        }
148        return componentProxy;
149    }
150
151    public ComponentType component() {
152        return component;
153    }
154
155    /*
156     * (non-Javadoc)
157     * 
158     * @see org.jgrapes.core.Manager#getChannel()
159     */
160    @Override
161    public Channel channel() {
162        return componentChannel;
163    }
164
165    /**
166     * Return the object itself as value.
167     */
168    @Override
169    public Object defaultCriterion() {
170        return this;
171    }
172
173    /**
174     * Matches the object itself (using identity comparison) or the
175     * {@link Channel} class.
176     * 
177     * @see Channel#isEligibleFor(Object)
178     */
179    @Override
180    public boolean isEligibleFor(Object value) {
181        return value.equals(Channel.class)
182            || value == defaultCriterion();
183    }
184}