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;
023
024import org.jgrapes.core.Channel;
025import org.jgrapes.core.ComponentType;
026import org.jgrapes.core.Manager;
027import org.jgrapes.core.NamedChannel;
028import org.jgrapes.core.annotation.ComponentManager;
029import org.jgrapes.core.annotation.Handler;
030
031/**
032 * The ComponentProxy is a special ComponentVertex that references the
033 * object implementing the Component interface (instead of being
034 * its base class).
035 */
036public final class ComponentProxy extends ComponentVertex {
037
038    /** The reference to the actual component. */
039    private final ComponentType component;
040    /** The referenced component's channel. */
041    private Channel componentChannel;
042
043    private static Field getManagerField(Class<?> clazz) {
044        try {
045            while (true) {
046                for (Field field : clazz.getDeclaredFields()) {
047                    if (Manager.class.isAssignableFrom(field.getType())
048                        && field
049                            .getAnnotation(ComponentManager.class) != null) {
050                        return field;
051                    }
052                }
053                clazz = clazz.getSuperclass();
054                if (clazz == null) {
055                    throw new IllegalArgumentException(
056                        "Components must have a manager attribute");
057                }
058            }
059        } catch (SecurityException e) {
060            throw (RuntimeException) (new IllegalArgumentException(
061                "Cannot access component's manager attribute")).initCause(e);
062        }
063    }
064
065    private static Channel getComponentChannel(Field field) {
066        ComponentManager cma = field.getAnnotation(ComponentManager.class);
067        if (cma.channel() != Handler.NoChannel.class) {
068            if (cma.channel() != Channel.BROADCAST.defaultCriterion()) {
069                try {
070                    return cma.channel().getConstructor().newInstance();
071                } catch (InstantiationException // NOPMD
072                        | IllegalAccessException | IllegalArgumentException
073                        | InvocationTargetException | NoSuchMethodException
074                        | SecurityException e) {
075                    // Ignored
076                }
077            }
078            return Channel.BROADCAST;
079        }
080        if (!cma.namedChannel().equals("")) {
081            return new NamedChannel(cma.namedChannel());
082        }
083        return Channel.SELF;
084    }
085
086    /**
087     * Create a new component proxy for the component and assign it to 
088     * the specified field which must be of type {@link Manager}.
089     * 
090     * @param field the field that gets the proxy assigned
091     * @param componentChannel the componen't channel
092     * @param component the component
093     */
094    private ComponentProxy(
095            Field field, ComponentType component, Channel componentChannel) {
096        this.component = component;
097        try {
098            field.set(component, this);
099            if (componentChannel == null) {
100                componentChannel = getComponentChannel(field);
101            }
102            if (componentChannel.equals(Channel.SELF)) {
103                componentChannel = this;
104            }
105            this.componentChannel = componentChannel;
106            initComponentsHandlers(null);
107        } catch (SecurityException | IllegalAccessException e) {
108            throw (RuntimeException) (new IllegalArgumentException(
109                "Cannot access component's manager attribute")).initCause(e);
110        }
111    }
112
113    /**
114     * Return the component node for a component that is represented
115     * by a proxy in the tree.
116     * 
117     * @param component the component
118     * @param componentChannel the component's channel
119     * @return the node representing the component in the tree
120     */
121    @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis" })
122    /* default */ static ComponentVertex getComponentProxy(
123            ComponentType component, Channel componentChannel) {
124        ComponentProxy componentProxy = null;
125        try {
126            Field field = getManagerField(component.getClass());
127            synchronized (component) {
128                if (!field.isAccessible()) { // NOPMD, handle problem first
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}