001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016-2026 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
013 * License for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License
016 * along 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 */
035@SuppressWarnings("PMD.AvoidSynchronizedStatement")
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 final 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() != 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 BROADCAST;
079        }
080        if (!cma.namedChannel().isEmpty()) {
081            return new NamedChannel(cma.namedChannel());
082        }
083        return 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        super(null);
097        this.component = component;
098        try {
099            field.set(component, this);
100            if (componentChannel == null) {
101                componentChannel = getComponentChannel(field);
102            }
103            if (componentChannel.equals(SELF)) {
104                componentChannel = this;
105            }
106            this.componentChannel = componentChannel;
107            initComponentsHandlers();
108        } catch (SecurityException | IllegalAccessException e) {
109            throw (RuntimeException) new IllegalArgumentException(
110                "Cannot access component's manager attribute").initCause(e);
111        }
112    }
113
114    /**
115     * Return the component node for a component that is represented
116     * by a proxy in the tree.
117     * 
118     * @param component the component
119     * @param componentChannel the component's channel
120     * @return the node representing the component in the tree
121     */
122    @SuppressWarnings({ "PMD.AvoidAccessibilityAlteration",
123        "PMD.ConfusingTernary" })
124    /* default */ static ComponentVertex getComponentProxy(
125            ComponentType component, Channel componentChannel) {
126        ComponentProxy componentProxy;
127        try {
128            Field field = getManagerField(component.getClass());
129            synchronized (component) {
130                if (!field.canAccess(component)) {
131                    field.setAccessible(true);
132                    componentProxy = (ComponentProxy) field.get(component);
133                    if (componentProxy == null) {
134                        componentProxy = new ComponentProxy(
135                            field, component, componentChannel);
136                    }
137                    field.setAccessible(false);
138                } else {
139                    componentProxy = (ComponentProxy) field.get(component);
140                    if (componentProxy == null) {
141                        componentProxy = new ComponentProxy(
142                            field, component, componentChannel);
143                    }
144                }
145            }
146        } catch (SecurityException | IllegalAccessException e) {
147            throw (RuntimeException) new IllegalArgumentException(
148                "Cannot access component's manager attribute").initCause(e);
149        }
150        return componentProxy;
151    }
152
153    @Override
154    public ComponentType component() {
155        return component;
156    }
157
158    /*
159     * (non-Javadoc)
160     * 
161     * @see org.jgrapes.core.Manager#getChannel()
162     */
163    @Override
164    public Channel channel() {
165        return componentChannel;
166    }
167
168    /**
169     * Return the object itself as value.
170     */
171    @Override
172    public Object defaultCriterion() {
173        return this;
174    }
175
176    /**
177     * Matches the object itself (using identity comparison) or the
178     * {@link Channel} class.
179     * 
180     * @see Channel#isEligibleFor(Object)
181     */
182    @Override
183    @SuppressWarnings("PMD.CompareObjectsWithEquals")
184    public boolean isEligibleFor(Object value) {
185        return value.equals(Channel.class)
186            || value == defaultCriterion();
187    }
188}