DefaultMVCGroupManager.java
001 /*
002  * Copyright 2008-2017 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *     http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 package org.codehaus.griffon.runtime.core.mvc;
017 
018 import griffon.core.ApplicationClassLoader;
019 import griffon.core.ApplicationEvent;
020 import griffon.core.GriffonApplication;
021 import griffon.core.artifact.ArtifactManager;
022 import griffon.core.artifact.GriffonArtifact;
023 import griffon.core.artifact.GriffonClass;
024 import griffon.core.artifact.GriffonController;
025 import griffon.core.artifact.GriffonMvcArtifact;
026 import griffon.core.artifact.GriffonView;
027 import griffon.core.mvc.MVCGroup;
028 import griffon.core.mvc.MVCGroupConfiguration;
029 import griffon.exceptions.FieldException;
030 import griffon.exceptions.GriffonException;
031 import griffon.exceptions.GriffonViewInitializationException;
032 import griffon.exceptions.MVCGroupInstantiationException;
033 import griffon.exceptions.NewInstanceException;
034 import griffon.inject.Contextual;
035 import griffon.inject.MVCMember;
036 import griffon.util.CollectionUtils;
037 import griffon.util.Instantiator;
038 import org.codehaus.griffon.runtime.core.injection.InjectionUnitOfWork;
039 import org.slf4j.Logger;
040 import org.slf4j.LoggerFactory;
041 
042 import javax.annotation.Nonnull;
043 import javax.annotation.Nullable;
044 import javax.inject.Inject;
045 import java.beans.PropertyDescriptor;
046 import java.lang.reflect.AnnotatedElement;
047 import java.lang.reflect.Field;
048 import java.lang.reflect.InvocationTargetException;
049 import java.lang.reflect.Method;
050 import java.lang.reflect.Modifier;
051 import java.util.ArrayList;
052 import java.util.Arrays;
053 import java.util.LinkedHashMap;
054 import java.util.List;
055 import java.util.Map;
056 
057 import static griffon.core.GriffonExceptionHandler.sanitize;
058 import static griffon.util.AnnotationUtils.annotationsOfMethodParameter;
059 import static griffon.util.AnnotationUtils.findAnnotation;
060 import static griffon.util.AnnotationUtils.namesFor;
061 import static griffon.util.ConfigUtils.getConfigValueAsBoolean;
062 import static griffon.util.GriffonClassUtils.getAllDeclaredFields;
063 import static griffon.util.GriffonClassUtils.getPropertyDescriptors;
064 import static griffon.util.GriffonClassUtils.setFieldValue;
065 import static griffon.util.GriffonClassUtils.setPropertiesOrFieldsNoException;
066 import static griffon.util.GriffonClassUtils.setPropertyOrFieldValueNoException;
067 import static griffon.util.GriffonNameUtils.capitalize;
068 import static griffon.util.GriffonNameUtils.isBlank;
069 import static java.util.Arrays.asList;
070 import static java.util.Objects.requireNonNull;
071 
072 /**
073  * Base implementation of the {@code MVCGroupManager} interface.
074  *
075  @author Andres Almiray
076  @since 2.0.0
077  */
078 public class DefaultMVCGroupManager extends AbstractMVCGroupManager {
079     private static final Logger LOG = LoggerFactory.getLogger(DefaultMVCGroupManager.class);
080     private static final String CONFIG_KEY_COMPONENT = "component";
081     private static final String CONFIG_KEY_EVENTS_LIFECYCLE = "events.lifecycle";
082     private static final String CONFIG_KEY_EVENTS_INSTANTIATION = "events.instantiation";
083     private static final String CONFIG_KEY_EVENTS_DESTRUCTION = "events.destruction";
084     private static final String CONFIG_KEY_EVENTS_LISTENER = "events.listener";
085     private static final String KEY_PARENT_GROUP = "parentGroup";
086 
087     protected final ApplicationClassLoader applicationClassLoader;
088     protected final Instantiator instantiator;
089 
090     @Inject
091     public DefaultMVCGroupManager(@Nonnull GriffonApplication application, @Nonnull ApplicationClassLoader applicationClassLoader, @Nonnull Instantiator instantiator) {
092         super(application);
093         this.applicationClassLoader = requireNonNull(applicationClassLoader, "Argument 'applicationClassLoader' must not be null");
094         this.instantiator = requireNonNull(instantiator, "Argument 'instantiator' must not be null");
095     }
096 
097     protected void doInitialize(@Nonnull Map<String, MVCGroupConfiguration> configurations) {
098         requireNonNull(configurations, "Argument 'configurations' must not be null");
099         for (MVCGroupConfiguration configuration : configurations.values()) {
100             addConfiguration(configuration);
101         }
102     }
103 
104     @Nonnull
105     protected MVCGroup createMVCGroup(@Nonnull MVCGroupConfiguration configuration, @Nullable String mvcId, @Nonnull Map<String, Object> args) {
106         requireNonNull(configuration, ERROR_CONFIGURATION_NULL);
107         requireNonNull(args, ERROR_ARGS_NULL);
108 
109         mvcId = resolveMvcId(configuration, mvcId);
110         checkIdIsUnique(mvcId, configuration);
111 
112         LOG.debug("Building MVC group '{}' with name '{}'", configuration.getMvcType(), mvcId);
113         Map<String, Object> argsCopy = copyAndConfigureArguments(args, configuration, mvcId);
114 
115         // figure out what the classes are
116         Map<String, ClassHolder> classMap = new LinkedHashMap<>();
117         for (Map.Entry<String, String> memberEntry : configuration.getMembers().entrySet()) {
118             String memberType = memberEntry.getKey();
119             String memberClassName = memberEntry.getValue();
120             selectClassesPerMember(memberType, memberClassName, classMap);
121         }
122 
123         boolean isEventPublishingEnabled = getApplication().getEventRouter().isEventPublishingEnabled();
124         getApplication().getEventRouter().setEventPublishingEnabled(isConfigFlagEnabled(configuration, CONFIG_KEY_EVENTS_INSTANTIATION));
125         Map<String, Object> instances = new LinkedHashMap<>();
126         List<Object> injectedInstances = new ArrayList<>();
127 
128         try {
129             InjectionUnitOfWork.start();
130         catch (IllegalStateException ise) {
131             throw new MVCGroupInstantiationException("Can not instantiate MVC group '" + configuration.getMvcType() "' with id '" + mvcId + "'", configuration.getMvcType(), mvcId, ise);
132         }
133 
134         try {
135             instances.putAll(instantiateMembers(classMap, argsCopy));
136         finally {
137             getApplication().getEventRouter().setEventPublishingEnabled(isEventPublishingEnabled);
138             try {
139                 injectedInstances.addAll(InjectionUnitOfWork.finish());
140             catch (IllegalStateException ise) {
141                 throw new MVCGroupInstantiationException("Can not instantiate MVC group '" + configuration.getMvcType() "' with id '" + mvcId + "'", configuration.getMvcType(), mvcId, ise);
142             }
143         }
144 
145         MVCGroup group = newMVCGroup(configuration, mvcId, instances, (MVCGroupargs.get(KEY_PARENT_GROUP));
146         adjustMvcArguments(group, argsCopy);
147 
148         boolean fireEvents = isConfigFlagEnabled(configuration, CONFIG_KEY_EVENTS_LIFECYCLE);
149         if (fireEvents) {
150             getApplication().getEventRouter().publishEvent(ApplicationEvent.INITIALIZE_MVC_GROUP.getName(), asList(configuration, group));
151         }
152 
153         // special case -- controllers are added as application listeners
154         if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LISTENER)) {
155             GriffonController controller = group.getController();
156             if (controller != null) {
157                 getApplication().getEventRouter().addEventListener(controller);
158             }
159         }
160 
161         // mutually set each other to the available fields and inject args
162         fillReferencedProperties(group, argsCopy);
163 
164         doAddGroup(group);
165 
166         initializeMembers(group, argsCopy);
167         if (group instanceof AbstractMVCGroup) {
168             ((AbstractMVCGroupgroup).getInjectedInstances().addAll(injectedInstances);
169         }
170 
171         if (fireEvents) {
172             getApplication().getEventRouter().publishEvent(ApplicationEvent.CREATE_MVC_GROUP.getName(), asList(group));
173         }
174 
175         return group;
176     }
177 
178     protected void adjustMvcArguments(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
179         // must set it again because mvcId might have been initialized internally
180         args.put("mvcId", group.getMvcId());
181         args.put("mvcGroup", group);
182         args.put("application", getApplication());
183     }
184 
185     @Nonnull
186     @SuppressWarnings("ConstantConditions")
187     protected String resolveMvcId(@Nonnull MVCGroupConfiguration configuration, @Nullable String mvcId) {
188         boolean component = getConfigValueAsBoolean(configuration.getConfig(), CONFIG_KEY_COMPONENT, false);
189 
190         if (isBlank(mvcId)) {
191             if (component) {
192                 mvcId = configuration.getMvcType() "-" + System.nanoTime();
193             else {
194                 mvcId = configuration.getMvcType();
195             }
196         }
197         return mvcId;
198     }
199 
200     @SuppressWarnings("unchecked")
201     protected void selectClassesPerMember(@Nonnull String memberType, @Nonnull String memberClassName, @Nonnull Map<String, ClassHolder> classMap) {
202         GriffonClass griffonClass = getApplication().getArtifactManager().findGriffonClass(memberClassName);
203         ClassHolder classHolder = new ClassHolder();
204         if (griffonClass != null) {
205             classHolder.artifactClass = (Class<? extends GriffonArtifact>griffonClass.getClazz();
206         else {
207             classHolder.regularClass = loadClass(memberClassName);
208         }
209         classMap.put(memberType, classHolder);
210     }
211 
212     @Nonnull
213     protected Map<String, Object> copyAndConfigureArguments(@Nonnull Map<String, Object> args, @Nonnull MVCGroupConfiguration configuration, @Nonnull String mvcId) {
214         Map<String, Object> argsCopy = CollectionUtils.<String, Object>map()
215             .e("application", getApplication())
216             .e("mvcType", configuration.getMvcType())
217             .e("mvcId", mvcId)
218             .e("configuration", configuration);
219 
220         if (args.containsKey(KEY_PARENT_GROUP)) {
221             if (args.get(KEY_PARENT_GROUPinstanceof MVCGroup) {
222                 MVCGroup parentGroup = (MVCGroupargs.get(KEY_PARENT_GROUP);
223                 for (Map.Entry<String, Object> e : parentGroup.getMembers().entrySet()) {
224                     args.put("parent" + capitalize(e.getKey()), e.getValue());
225                 }
226             }
227         }
228 
229         argsCopy.putAll(args);
230         return argsCopy;
231     }
232 
233     protected void checkIdIsUnique(@Nonnull String mvcId, @Nonnull MVCGroupConfiguration configuration) {
234         if (findGroup(mvcId!= null) {
235             String action = getApplication().getConfiguration().getAsString("griffon.mvcid.collision""exception");
236             if ("warning".equalsIgnoreCase(action)) {
237                 LOG.warn("A previous instance of MVC group '{}' with id '{}' exists. Destroying the old instance first.", configuration.getMvcType(), mvcId);
238                 destroyMVCGroup(mvcId);
239             else {
240                 throw new MVCGroupInstantiationException("Can not instantiate MVC group '" + configuration.getMvcType() "' with id '" + mvcId + "' because a previous instance with that name exists and was not disposed off properly.", configuration.getMvcType(), mvcId);
241             }
242         }
243     }
244 
245     @Nonnull
246     protected Map<String, Object> instantiateMembers(@Nonnull Map<String, ClassHolder> classMap, @Nonnull Map<String, Object> args) {
247         // instantiate the parts
248         Map<String, Object> instanceMap = new LinkedHashMap<>();
249         for (Map.Entry<String, ClassHolder> classEntry : classMap.entrySet()) {
250             String memberType = classEntry.getKey();
251             if (args.containsKey(memberType)) {
252                 // use provided value, even if null
253                 instanceMap.put(memberType, args.get(memberType));
254             else {
255                 // otherwise create a new value
256                 ClassHolder classHolder = classEntry.getValue();
257                 if (classHolder.artifactClass != null) {
258                     Class<? extends GriffonArtifact> memberClass = classHolder.artifactClass;
259                     ArtifactManager artifactManager = getApplication().getArtifactManager();
260                     GriffonClass griffonClass = artifactManager.findGriffonClass(memberClass);
261                     GriffonArtifact instance = artifactManager.newInstance(griffonClass);
262                     instanceMap.put(memberType, instance);
263                     args.put(memberType, instance);
264                 else {
265                     Class<?> memberClass = classHolder.regularClass;
266                     try {
267                         Object instance = instantiator.instantiate(memberClass);
268                         instanceMap.put(memberType, instance);
269                         args.put(memberType, instance);
270                     catch (RuntimeException e) {
271                         LOG.error("Can't create member {} with {}", memberType, memberClass);
272                         throw new NewInstanceException(memberClass, e);
273                     }
274                 }
275             }
276         }
277         return instanceMap;
278     }
279 
280     protected void initializeMembers(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
281         LOG.debug("Initializing each MVC member of group '{}'", group.getMvcId());
282         for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
283             String memberType = memberEntry.getKey();
284             Object member = memberEntry.getValue();
285             if (member instanceof GriffonArtifact) {
286                 initializeArtifactMember(group, memberType, (GriffonArtifactmember, args);
287             else {
288                 initializeNonArtifactMember(group, memberType, member, args);
289             }
290         }
291     }
292 
293     protected void initializeArtifactMember(@Nonnull final MVCGroup group, @Nonnull String type, @Nonnull final GriffonArtifact member, @Nonnull final Map<String, Object> args) {
294         if (member instanceof GriffonView) {
295             getApplication().getUIThreadManager().runInsideUISync(new Runnable() {
296                 @Override
297                 public void run() {
298                     try {
299                         GriffonView view = (GriffonViewmember;
300                         view.initUI();
301                     catch (RuntimeException e) {
302                         throw (RuntimeExceptionsanitize(new GriffonViewInitializationException(group.getMvcType(), group.getMvcId(), member.getClass().getName(), e));
303                     }
304                     ((GriffonMvcArtifactmember).mvcGroupInit(args);
305                 }
306             });
307         else if (member instanceof GriffonMvcArtifact) {
308             ((GriffonMvcArtifactmember).mvcGroupInit(args);
309         }
310     }
311 
312     protected void initializeNonArtifactMember(@Nonnull MVCGroup group, @Nonnull String type, @Nonnull Object member, @Nonnull Map<String, Object> args) {
313         // empty
314     }
315 
316     protected abstract static class InjectionPoint {
317         protected final String name;
318         protected final boolean nullable;
319         protected final Type type;
320 
321         protected InjectionPoint(String name, boolean nullable, Type type) {
322             this.name = name;
323             this.nullable = nullable;
324             this.type = type;
325         }
326 
327         protected enum Type {
328             MEMBER,
329             CONTEXTUAL,
330             OTHER
331         }
332 
333         protected abstract void apply(@Nonnull MVCGroup group, @Nonnull String memberType, @Nonnull Object instance, @Nonnull Map<String, Object> args);
334     }
335 
336     protected static class FieldInjectionPoint extends InjectionPoint {
337         protected final Field field;
338 
339         protected FieldInjectionPoint(String name, boolean nullable, Type type, Field field) {
340             super(name, nullable, type);
341             this.field = field;
342         }
343 
344         @Override
345         protected void apply(@Nonnull MVCGroup group, @Nonnull String memberType, @Nonnull Object instance, @Nonnull Map<String, Object> args) {
346             String[] keys = namesFor(field);
347             Object argValue = args.get(name);
348 
349             if (type == Type.CONTEXTUAL) {
350                 for (String key : keys) {
351                     if (group.getContext().containsKey(key)) {
352                         argValue = group.getContext().get(key);
353                         break;
354                     }
355                 }
356             }
357 
358             if (argValue == null) {
359                 if (!nullable) {
360                     if (type == Type.CONTEXTUAL) {
361                         throw new IllegalStateException("Could not find an instance of type " +
362                             field.getType().getName() " under keys '" + Arrays.toString(keys+
363                             "' in the context of MVCGroup[" + group.getMvcType() ":" + group.getMvcId() +
364                             "] to be injected on field '" + field.getName() +
365                             "' in " + type + " (" + resolveMemberClass(instance).getName() "). Field does not accept null values.");
366                     else if (type == Type.MEMBER) {
367                         throw new IllegalStateException("Could not inject argument on field '"
368                             + name + "' in " + memberType + " (" + resolveMemberClass(instance).getName() +
369                             "). Field does not accept null values.");
370                     }
371                 }
372                 return;
373             }
374 
375             try {
376                 setFieldValue(instance, name, argValue);
377                 if (type == Type.OTHER) {
378                     LOG.warn("Field '" + name + "' in " + memberType + " (" + resolveMemberClass(instance).getName() +
379                         ") must be annotated with @" + MVCMember.class.getName() ".");
380                 }
381             catch (FieldException e) {
382                 throw new MVCGroupInstantiationException(group.getMvcType(), group.getMvcId(), e);
383             }
384         }
385     }
386 
387     protected static class MethodInjectionPoint extends InjectionPoint {
388         protected final Method method;
389 
390         protected MethodInjectionPoint(String name, boolean nullable, Type type, Method method) {
391             super(name, nullable, type);
392             this.method = method;
393         }
394 
395         @Override
396         protected void apply(@Nonnull MVCGroup group, @Nonnull String memberType, @Nonnull Object instance, @Nonnull Map<String, Object> args) {
397             if (type == Type.CONTEXTUAL) {
398                 String[] keys = namesFor(method);
399                 Object argValue = args.get(name);
400 
401                 for (String key : keys) {
402                     if (group.getContext().containsKey(key)) {
403                         argValue = group.getContext().get(key);
404                         break;
405                     }
406                 }
407 
408                 if (argValue == null && !nullable) {
409                     throw new IllegalStateException("Could not find an instance of type " +
410                         method.getParameterTypes()[0].getName() " under keys '" + Arrays.toString(keys+
411                         "' in the context of MVCGroup[" + group.getMvcType() ":" + group.getMvcId() +
412                         "] to be injected on property '" + name +
413                         "' in " + type + " (" + resolveMemberClass(instance).getName() "). Property does not accept null values.");
414                 }
415 
416                 try {
417                     method.invoke(instance, argValue);
418                 catch (IllegalAccessException | InvocationTargetException e) {
419                     throw new MVCGroupInstantiationException(group.getMvcType(), group.getMvcId(), e);
420                 }
421             else {
422                 Object argValue = args.get(name);
423                 if (argValue == null) {
424                     if (!nullable) {
425                         if (type == Type.MEMBER) {
426                             throw new IllegalStateException("Could not inject argument on property '" +
427                                 name + "' in " + memberType + " (" + resolveMemberClass(instance).getName() +
428                                 "). Property does not accept null values.");
429                         }
430                     }
431                     return;
432                 }
433 
434                 try {
435                     method.invoke(instance, argValue);
436                     if (type == Type.OTHER) {
437                         LOG.warn("Property '" + name + "' in " + memberType + " (" + resolveMemberClass(instance).getName() +
438                             ") must be annotated with @" + MVCMember.class.getName() ".");
439                     }
440                 catch (IllegalAccessException | InvocationTargetException e) {
441                     throw new MVCGroupInstantiationException(group.getMvcType(), group.getMvcId(), e);
442                 }
443             }
444         }
445     }
446 
447     protected void fillReferencedProperties(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
448         for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
449             String memberType = memberEntry.getKey();
450             Object member = memberEntry.getValue();
451 
452             Map<String, Object> argsCopy = new LinkedHashMap<>(args);
453 
454             Map<String, Field> fields = new LinkedHashMap<>();
455             for (Field field : getAllDeclaredFields(resolveMemberClass(member))) {
456                 fields.put(field.getName(), field);
457             }
458             Map<String, InjectionPoint> injectionPoints = new LinkedHashMap<>();
459             for (PropertyDescriptor descriptor : getPropertyDescriptors(resolveMemberClass(member))) {
460                 Method method = descriptor.getWriteMethod();
461                 if (method == null || isInjectable(method)) { continue}
462                 boolean nullable = findAnnotation(annotationsOfMethodParameter(method, 0), Nonnull.class== null;
463                 InjectionPoint.Type type = resolveType(method);
464                 Field field = fields.get(descriptor.getName());
465                 if (field != null && type == InjectionPoint.Type.OTHER) {
466                     type = resolveType(field);
467                     nullable = field.getAnnotation(Nonnull.class== null;
468                 }
469                 injectionPoints.put(descriptor.getName()new MethodInjectionPoint(descriptor.getName(), nullable, type, method));
470             }
471 
472             for (Field field : getAllDeclaredFields(resolveMemberClass(member))) {
473                 if (Modifier.isStatic(field.getModifiers()) || isInjectable(field)) { continue}
474                 if (!injectionPoints.containsKey(field.getName())) {
475                     boolean nullable = field.getAnnotation(Nonnull.class== null;
476                     InjectionPoint.Type type = resolveType(field);
477                     injectionPoints.put(field.getName()new FieldInjectionPoint(field.getName(), nullable, type, field));
478                 }
479             }
480 
481             for (InjectionPoint ip : injectionPoints.values()) {
482                 ip.apply(group, memberType, member, args);
483                 argsCopy.remove(ip.name);
484             }
485 
486             /*
487             for (Map.Entry<String, Object> e : argsCopy.entrySet()) {
488                 try {
489                     setPropertyOrFieldValue(member, e.getKey(), e.getValue());
490                     LOG.warn("Property '" + e.getKey() + "' in " + memberType + " (" + resolveMemberClass(member).getName() +
491                         ") must be annotated with @" + MVCMember.class.getName() + ".");
492                 } catch (PropertyException ignored) {
493                     // OK
494                 }
495             }
496             */
497             setPropertiesOrFieldsNoException(member, argsCopy);
498         }
499     }
500 
501     @Nonnull
502     protected InjectionPoint.Type resolveType(@Nonnull AnnotatedElement element) {
503         if (isContextual(element)) {
504             return InjectionPoint.Type.CONTEXTUAL;
505         else if (isMvcMember(element)) {
506             return InjectionPoint.Type.MEMBER;
507         }
508         return InjectionPoint.Type.OTHER;
509     }
510 
511     protected boolean isContextual(AnnotatedElement element) {
512         return element != null && element.getAnnotation(Contextual.class!= null;
513     }
514 
515     protected boolean isInjectable(AnnotatedElement element) {
516         return element != null && element.getAnnotation(Inject.class!= null;
517     }
518 
519     protected boolean isMvcMember(AnnotatedElement element) {
520         return element != null && element.getAnnotation(MVCMember.class!= null;
521     }
522 
523     protected void doAddGroup(@Nonnull MVCGroup group) {
524         addGroup(group);
525     }
526 
527     public void destroyMVCGroup(@Nonnull String mvcId) {
528         MVCGroup group = findGroup(mvcId);
529         LOG.debug("Group '{}' points to {}", mvcId, group);
530 
531         if (group == null) { return}
532 
533         LOG.debug("Destroying MVC group identified by '{}'", mvcId);
534 
535         if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LISTENER)) {
536             GriffonController controller = group.getController();
537             if (controller != null) {
538                 getApplication().getEventRouter().removeEventListener(controller);
539             }
540         }
541 
542         boolean fireDestructionEvents = isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_DESTRUCTION);
543 
544         destroyMembers(group, fireDestructionEvents);
545 
546         doRemoveGroup(group);
547         group.destroy();
548 
549         if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LIFECYCLE)) {
550             getApplication().getEventRouter().publishEvent(ApplicationEvent.DESTROY_MVC_GROUP.getName(), asList(group));
551         }
552     }
553 
554     protected void destroyMembers(@Nonnull MVCGroup group, boolean fireDestructionEvents) {
555         for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
556             Object member = memberEntry.getValue();
557             if (member instanceof GriffonArtifact) {
558                 destroyArtifactMember(memberEntry.getKey()(GriffonArtifactmember, fireDestructionEvents);
559             else {
560                 destroyNonArtifactMember(memberEntry.getKey(), member, fireDestructionEvents);
561             }
562 
563         }
564 
565         if (group instanceof AbstractMVCGroup) {
566             List<Object> injectedInstances = ((AbstractMVCGroupgroup).getInjectedInstances();
567             for (Object instance : injectedInstances) {
568                 getApplication().getInjector().release(instance);
569             }
570             injectedInstances.clear();
571         }
572     }
573 
574     protected void destroyArtifactMember(@Nonnull String type, @Nonnull GriffonArtifact member, boolean fireDestructionEvents) {
575         if (member instanceof GriffonMvcArtifact) {
576             final GriffonMvcArtifact artifact = (GriffonMvcArtifactmember;
577             if (fireDestructionEvents) {
578                 getApplication().getEventRouter().publishEvent(ApplicationEvent.DESTROY_INSTANCE.getName(), asList(artifact.getTypeClass(), artifact));
579             }
580 
581             if (artifact instanceof GriffonView) {
582                 getApplication().getUIThreadManager().runInsideUISync(new Runnable() {
583                     @Override
584                     public void run() {
585                         try {
586                             artifact.mvcGroupDestroy();
587                         catch (RuntimeException e) {
588                             throw (RuntimeExceptionsanitize(e);
589                         }
590                     }
591                 });
592             else {
593                 artifact.mvcGroupDestroy();
594             }
595 
596             // clear all parent* references
597             for (String parentMemberName : new String[]{"parentModel""parentView""parentController""parentGroup"}) {
598                 setPropertyOrFieldValueNoException(member, parentMemberName, null);
599             }
600         }
601 
602         destroyContextualMemberProperties(type, member);
603     }
604 
605     protected void destroyContextualMemberProperties(@Nonnull String type, @Nonnull GriffonArtifact member) {
606         for (Field field : getAllDeclaredFields(member.getTypeClass())) {
607             if (isContextual(field)) {
608                 try {
609                     setFieldValue(member, field.getName()null);
610                 catch (FieldException e) {
611                     throw new IllegalStateException("Could not nullify field " +
612                         field.getName() "' in " + type + " (" + member.getTypeClass().getName() ")", e);
613                 }
614             }
615         }
616     }
617 
618     protected void destroyNonArtifactMember(@Nonnull String type, @Nonnull Object member, boolean fireDestructionEvents) {
619         // empty
620     }
621 
622     protected void doRemoveGroup(@Nonnull MVCGroup group) {
623         removeGroup(group);
624     }
625 
626     protected boolean isConfigFlagEnabled(@Nonnull MVCGroupConfiguration configuration, @Nonnull String key) {
627         return getConfigValueAsBoolean(configuration.getConfig(), key, true);
628     }
629 
630     @Nonnull
631     private static Class<?> resolveMemberClass(@Nonnull Object member) {
632         if (member instanceof GriffonArtifact) {
633             return ((GriffonArtifactmember).getTypeClass();
634         }
635         return member.getClass();
636     }
637 
638     @Nullable
639     protected Class<?> loadClass(@Nonnull String className) {
640         try {
641             return applicationClassLoader.get().loadClass(className);
642         catch (ClassNotFoundException e) {
643             // #39 do not ignore this CNFE
644             throw new GriffonException(e.toString(), e);
645         }
646     }
647 
648     protected static final class ClassHolder {
649         protected Class<?> regularClass;
650         protected Class<? extends GriffonArtifact> artifactClass;
651     }
652 }