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 final 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        super(null);
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();
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        "PMD.AvoidAccessibilityAlteration", "PMD.ConfusingTernary" })
123    /* default */ static ComponentVertex getComponentProxy(
124            ComponentType component, Channel componentChannel) {
125        ComponentProxy componentProxy;
126        try {
127            Field field = getManagerField(component.getClass());
128            synchronized (component) {
129                if (!field.canAccess(component)) {
130                    field.setAccessible(true);
131                    componentProxy = (ComponentProxy) field.get(component);
132                    if (componentProxy == null) {
133                        componentProxy = new ComponentProxy(
134                            field, component, componentChannel);
135                    }
136                    field.setAccessible(false);
137                } else {
138                    componentProxy = (ComponentProxy) field.get(component);
139                    if (componentProxy == null) {
140                        componentProxy = new ComponentProxy(
141                            field, component, componentChannel);
142                    }
143                }
144            }
145        } catch (SecurityException | IllegalAccessException e) {
146            throw (RuntimeException) new IllegalArgumentException(
147                "Cannot access component's manager attribute").initCause(e);
148        }
149        return componentProxy;
150    }
151
152    public ComponentType component() {
153        return component;
154    }
155
156    /*
157     * (non-Javadoc)
158     * 
159     * @see org.jgrapes.core.Manager#getChannel()
160     */
161    @Override
162    public Channel channel() {
163        return componentChannel;
164    }
165
166    /**
167     * Return the object itself as value.
168     */
169    @Override
170    public Object defaultCriterion() {
171        return this;
172    }
173
174    /**
175     * Matches the object itself (using identity comparison) or the
176     * {@link Channel} class.
177     * 
178     * @see Channel#isEligibleFor(Object)
179     */
180    @Override
181    @SuppressWarnings("PMD.CompareObjectsWithEquals")
182    public boolean isEligibleFor(Object value) {
183        return value.equals(Channel.class)
184            || value == defaultCriterion();
185    }
186}