MetaComponentFactory.groovy
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2008-2017 the original author or authors.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     http://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package griffon.builder.core.factory
019 
020 import griffon.core.ApplicationEvent
021 import griffon.core.GriffonApplication
022 import griffon.core.RunnableWithArgs
023 import griffon.core.mvc.MVCGroup
024 import griffon.core.mvc.MVCGroupManager
025 
026 import javax.annotation.Nullable
027 
028 import static org.codehaus.griffon.runtime.groovy.mvc.GroovyAwareMVCGroup.CURRENT_MVCGROUP
029 
030 /**
031  * Enables MVC groups to be used as component nodes
032  *
033  @author Andres Almiray
034  @author Alexander Klein
035  */
036 @SuppressWarnings("rawtypes")
037 class MetaComponentFactory extends AbstractFactory {
038     @Override
039     Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) {
040         Map attrs = resolveAttributes(attributes)
041         String mvcType = resolveMvcType(name, value, attrs)
042         String mvcId = resolveMvcId(builder, name, value, attrs)
043         Map mvcArgs = resolveMvcArgs(attrs)
044         Map mvcArgsCopy = [*: mvcArgs]
045         attributes.clear()
046         attributes.putAll(attrs)
047 
048         MVCGroup parentGroup = builder.getVariables().get(CURRENT_MVCGROUP)
049         def receiver = parentGroup ?: builder.application.mvcGroupManager
050         MVCGroup mvcGroup = receiver.createMVCGroup(mvcType, mvcId, mvcArgs)
051         def root = mvcGroup.rootNode
052 
053         new DestroyEventHandler(mvcId, mvcGroup, builder.application)
054 
055         builder.context.root = root
056         builder.context.mvcGroup = mvcGroup
057         builder.context.mvcArgs = mvcArgsCopy
058         root
059     }
060 
061     protected Map resolveAttributes(Map attributes) {
062         [*: attributes]
063     }
064 
065     protected String resolveMvcType(Object name, Object value, Map attributes) {
066         String mvcType = ''
067         if (value != null && value instanceof CharSequence) {
068             return value.toString()
069         }
070         throw new IllegalArgumentException("In $name value must be an MVC group type")
071     }
072 
073     protected String resolveMvcId(FactoryBuilderSupport builder, Object name, String mvcType, Map attributes) {
074         String mvcId = attributes.remove('mvcId') ?: mvcType
075 
076         MVCGroupManager mvcGroupManager = builder.application.mvcGroupManager
077         if (mvcGroupManager.findGroup(mvcId)) {
078             mvcId += '-' + UUID.randomUUID().toString()
079         }
080         mvcId
081     }
082 
083     protected Map resolveMvcArgs(Map attributes) {
084         (attributes.remove('mvcArgs') ?: [:]) [metaComponentArgs: attributes]
085     }
086 
087     private static class DestroyEventHandler implements RunnableWithArgs {
088         private final String parentId
089         private final MVCGroup childGroup
090         private final GriffonApplication application
091 
092         DestroyEventHandler(String parentId, MVCGroup childGroup, GriffonApplication application) {
093             this.parentId = parentId
094             this.childGroup = childGroup
095             this.application = application
096             application.eventRouter.addEventListener(ApplicationEvent.DESTROY_MVC_GROUP.name, this)
097         }
098 
099         @Override
100         void run(@Nullable Object... args) {
101             Object destroyedGroup = args[0]
102             if (destroyedGroup.mvcId == parentId) {
103                 childGroup.destroy()
104                 application.eventRouter.removeEventListener(ApplicationEvent.DESTROY_MVC_GROUP.name, this)
105             }
106         }
107     }
108 
109     @Override
110     boolean onHandleNodeAttributes(FactoryBuilderSupport builder, Object node, Map attributes) {
111         try {
112             return builder.context.mvcGroup.controller.metaClass.invokeMethod(builder.context.mvcGroup.controller, 'onHandleNodeAttributes', builder, node, attributes)
113         catch (MissingMethodException e) {
114             return false
115         }
116     }
117 
118     @Override
119     boolean onNodeChildren(FactoryBuilderSupport builder, Object node, Closure childContent) {
120         def root = builder.context.root
121         builder = builder.context.mvcGroup.builder
122         Closure handleChildContent = builder.getVariables().get('handleChildContent')
123         if (handleChildContent != null) {
124             handleChildContent(childContent)
125         else {
126             builder.container(root, childContent)
127         }
128         false
129     }
130 
131     boolean isHandlesNodeChildren() {
132         false
133     }
134 
135     @Override
136     void setChild(FactoryBuilderSupport builder, Object parent, Object child) {
137         safeInvoke(builder.context.mvcGroup.controller, 'setChild', builder, parent, child)
138         safeInvoke(builder.parentContext.mvcGroup.builder, 'setChild', builder, parent, child)
139         super.setChild(builder, parent, child)
140     }
141 
142     @Override
143     void setParent(FactoryBuilderSupport builder, Object parent, Object child) {
144         safeInvoke(builder.context.mvcGroup.controller, 'setParent', builder, parent, child)
145         safeInvoke(builder.context.mvcGroup.builder, 'setParent', builder, parent, child)
146         super.setParent(builder, parent, child)
147     }
148 
149     @Override
150     void onNodeCompleted(FactoryBuilderSupport builder, Object parent, Object node) {
151         safeInvoke(builder.context.mvcGroup.controller, 'onNodeCompleted', builder, parent, node)
152         safeInvoke(builder.context.mvcGroup.builder, 'onNodeCompleted', builder, parent, node)
153         super.onNodeCompleted(builder, parent, node)
154     }
155 
156     static protected def safeInvoke(Object obj, String method, Object... args) {
157         try {
158             return obj.metaClass.invokeMethod(obj, method, args)
159         catch (MissingMethodException e) {
160             return null
161         }
162     }
163 }