MetaComponentFactory.groovy
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 griffon.builder.core.factory
017 
018 import griffon.core.ApplicationEvent
019 import griffon.core.GriffonApplication
020 import griffon.core.RunnableWithArgs
021 import griffon.core.mvc.MVCGroup
022 import griffon.core.mvc.MVCGroupManager
023 
024 import javax.annotation.Nullable
025 
026 import static org.codehaus.griffon.runtime.groovy.mvc.GroovyAwareMVCGroup.CURRENT_MVCGROUP
027 
028 /**
029  * Enables MVC groups to be used as component nodes
030  *
031  @author Andres Almiray
032  @author Alexander Klein
033  */
034 @SuppressWarnings("rawtypes")
035 class MetaComponentFactory extends AbstractFactory {
036     @Override
037     Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) {
038         Map attrs = resolveAttributes(attributes)
039         String mvcType = resolveMvcType(name, value, attrs)
040         String mvcId = resolveMvcId(builder, name, value, attrs)
041         Map mvcArgs = resolveMvcArgs(attrs)
042         Map mvcArgsCopy = [*: mvcArgs]
043         attributes.clear()
044         attributes.putAll(attrs)
045 
046         MVCGroup parentGroup = builder.getVariables().get(CURRENT_MVCGROUP)
047         def receiver = parentGroup ?: builder.application.mvcGroupManager
048         MVCGroup mvcGroup = receiver.createMVCGroup(mvcType, mvcId, mvcArgs)
049         def root = mvcGroup.rootNode
050 
051         new DestroyEventHandler(mvcId, mvcGroup, builder.application)
052 
053         builder.context.root = root
054         builder.context.mvcGroup = mvcGroup
055         builder.context.mvcArgs = mvcArgsCopy
056         root
057     }
058 
059     protected Map resolveAttributes(Map attributes) {
060         [*: attributes]
061     }
062 
063     protected String resolveMvcType(Object name, Object value, Map attributes) {
064         String mvcType = ''
065         if (value != null && value instanceof CharSequence) {
066             return value.toString()
067         }
068         throw new IllegalArgumentException("In $name value must be an MVC group type")
069     }
070 
071     protected String resolveMvcId(FactoryBuilderSupport builder, Object name, String mvcType, Map attributes) {
072         String mvcId = attributes.remove('mvcId') ?: mvcType
073 
074         MVCGroupManager mvcGroupManager = builder.application.mvcGroupManager
075         if (mvcGroupManager.findGroup(mvcId)) {
076             mvcId += '-' + UUID.randomUUID().toString()
077         }
078         mvcId
079     }
080 
081     protected Map resolveMvcArgs(Map attributes) {
082         (attributes.remove('mvcArgs') ?: [:]) [metaComponentArgs: attributes]
083     }
084 
085     private static class DestroyEventHandler implements RunnableWithArgs {
086         private final String parentId
087         private final MVCGroup childGroup
088         private final GriffonApplication application
089 
090         DestroyEventHandler(String parentId, MVCGroup childGroup, GriffonApplication application) {
091             this.parentId = parentId
092             this.childGroup = childGroup
093             this.application = application
094             application.eventRouter.addEventListener(ApplicationEvent.DESTROY_MVC_GROUP.name, this)
095         }
096 
097         @Override
098         void run(@Nullable Object... args) {
099             Object destroyedGroup = args[0]
100             if (destroyedGroup.mvcId == parentId) {
101                 childGroup.destroy()
102                 application.eventRouter.removeEventListener(ApplicationEvent.DESTROY_MVC_GROUP.name, this)
103             }
104         }
105     }
106 
107     @Override
108     boolean onHandleNodeAttributes(FactoryBuilderSupport builder, Object node, Map attributes) {
109         try {
110             return builder.context.mvcGroup.controller.metaClass.invokeMethod(builder.context.mvcGroup.controller, 'onHandleNodeAttributes', builder, node, attributes)
111         catch (MissingMethodException e) {
112             return false
113         }
114     }
115 
116     @Override
117     boolean onNodeChildren(FactoryBuilderSupport builder, Object node, Closure childContent) {
118         def root = builder.context.root
119         builder = builder.context.mvcGroup.builder
120         Closure handleChildContent = builder.getVariables().get('handleChildContent')
121         if (handleChildContent != null) {
122             handleChildContent(childContent)
123         else {
124             builder.container(root, childContent)
125         }
126         false
127     }
128 
129     boolean isHandlesNodeChildren() {
130         false
131     }
132 
133     @Override
134     void setChild(FactoryBuilderSupport builder, Object parent, Object child) {
135         safeInvoke(builder.context.mvcGroup.controller, 'setChild', builder, parent, child)
136         safeInvoke(builder.parentContext.mvcGroup.builder, 'setChild', builder, parent, child)
137         super.setChild(builder, parent, child)
138     }
139 
140     @Override
141     void setParent(FactoryBuilderSupport builder, Object parent, Object child) {
142         safeInvoke(builder.context.mvcGroup.controller, 'setParent', builder, parent, child)
143         safeInvoke(builder.context.mvcGroup.builder, 'setParent', builder, parent, child)
144         super.setParent(builder, parent, child)
145     }
146 
147     @Override
148     void onNodeCompleted(FactoryBuilderSupport builder, Object parent, Object node) {
149         safeInvoke(builder.context.mvcGroup.controller, 'onNodeCompleted', builder, parent, node)
150         safeInvoke(builder.context.mvcGroup.builder, 'onNodeCompleted', builder, parent, node)
151         super.onNodeCompleted(builder, parent, node)
152     }
153 
154     static protected def safeInvoke(Object obj, String method, Object... args) {
155         try {
156             return obj.metaClass.invokeMethod(obj, method, args)
157         catch (MissingMethodException e) {
158             return null
159         }
160     }
161 }