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}