/* * Copyright 2011 cruxframework.org. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.cruxframework.crux.core.rebind.screen.widget; import java.util.HashMap; import java.util.Map; import org.cruxframework.crux.core.client.screen.DeviceAdaptive.Device; import org.cruxframework.crux.core.client.screen.LazyPanelWrappingType; import org.cruxframework.crux.core.client.screen.views.ViewFactoryUtils; import org.cruxframework.crux.core.client.utils.StringUtils; import org.cruxframework.crux.core.declarativeui.LazyWidgets; import org.cruxframework.crux.core.declarativeui.LazyWidgets.WidgetLazyChecker; import org.cruxframework.crux.core.rebind.AbstractProxyCreator.SourcePrinter; import org.cruxframework.crux.core.rebind.CruxGeneratorException; import org.cruxframework.crux.core.rebind.screen.widget.ViewFactoryCreator.LazyCompatibleWidgetConsumer; import org.cruxframework.crux.core.rebind.screen.widget.ViewFactoryCreator.WidgetConsumer; import org.cruxframework.crux.core.rebind.screen.widget.WidgetCreatorAnnotationsProcessor.ChildProcessor; import org.cruxframework.crux.core.rebind.screen.widget.WidgetCreatorAnnotationsProcessor.ChildrenProcessor; import org.cruxframework.crux.core.rebind.screen.widget.creator.children.AllChildProcessor; import org.cruxframework.crux.core.rebind.screen.widget.creator.children.AnyWidgetChildProcessor; import org.cruxframework.crux.core.rebind.screen.widget.creator.children.ChoiceChildProcessor; import org.cruxframework.crux.core.rebind.screen.widget.creator.children.HasPostProcessor; import org.cruxframework.crux.core.rebind.screen.widget.creator.children.SequenceChildProcessor; import org.cruxframework.crux.core.rebind.screen.widget.creator.children.TextChildProcessor; import org.cruxframework.crux.core.rebind.screen.widget.creator.children.WidgetChildProcessor; import org.cruxframework.crux.core.rebind.screen.widget.creator.children.WidgetChildProcessor.AnyWidget; import org.cruxframework.crux.core.rebind.screen.widget.declarative.TagChild; import org.cruxframework.crux.core.rebind.screen.widget.declarative.TagChildLazyConditions; import org.cruxframework.crux.core.rebind.screen.widget.declarative.TagChildren; import org.cruxframework.crux.core.rebind.screen.widget.declarative.TagConstraints; import org.cruxframework.crux.core.utils.ClassUtils; import org.json.JSONArray; import org.json.JSONObject; import com.google.gwt.user.client.ui.HasText; import com.google.gwt.user.client.ui.Widget; /** * @author Thiago da Rosa de Bustamante * */ class ChildrenAnnotationScanner { private static final int UNBOUNDED = -1; private LazyPanelFactory lazyFactory; private Map<String, ChildrenProcessor> scannedProcessors; private final WidgetCreator<?> widgetCreator; /** * @param widgetCreator * @param type */ ChildrenAnnotationScanner(WidgetCreator<?> widgetCreator) { this.widgetCreator = widgetCreator; this.lazyFactory = new LazyPanelFactory(widgetCreator.getViewFactory()); } /** * @return */ ChildrenProcessor scanChildren() { scannedProcessors = new HashMap<String, WidgetCreatorAnnotationsProcessor.ChildrenProcessor>(); return scanChildren(widgetCreator.getClass(), false); } /** * * @param isAnyWidget * @param widgetProperty * @param lazyChecker * @param processor * @param supportedDevices * @return */ @SuppressWarnings({ "unchecked", "rawtypes" }) private ChildProcessor createChildProcessor(final boolean isAnyWidget, final String widgetProperty, final String method, final WidgetLazyChecker lazyChecker, final WidgetChildProcessor processor, final Device[] supportedDevices, final boolean applyDeviceFilters, final boolean autoProcessingEnabled) { final boolean isHasPostProcessor = processor instanceof HasPostProcessor; return new ChildProcessor() { public void processChild(SourcePrinter out, WidgetCreatorContext context) { if (applyDeviceFilters) { if (!widgetCreator.isCurrentDeviceSupported(context.readChildProperty("size"), context.readChildProperty("input"))) { return; } } if (widgetCreator.isCurrentDeviceSupported(supportedDevices)) { if (autoProcessingEnabled) { if (isAnyWidget) { processAnyWidgetChild(out, context); } //TODO aqui pode-se adicionar um processamento automatico por filho. Este é o ponto //context.pushWidgetComponent(WidgetComponentClass, variable); } try { processor.processChildren(out, context); } catch (Exception e) { throw new CruxGeneratorException("Error invoking ChildProcessor method.",e); } processChildren(out, context); } } /** * @param out * @param context */ private void processAnyWidgetChild(SourcePrinter out, WidgetCreatorContext context) { String childWidget; WidgetConsumer consumer = widgetCreator.getViewFactory().getScreenWidgetConsumer(); if (consumer != null && consumer instanceof LazyCompatibleWidgetConsumer && lazyChecker != null && lazyChecker.isLazy(context.getWidgetElement())) { childWidget = lazyFactory.getLazyPanel(out, context.getChildElement(), context.getWidgetId(), LazyPanelWrappingType.wrapChildren); String lazyPanelId = ViewFactoryUtils.getLazyPanelId(context.getWidgetId(), LazyPanelWrappingType.wrapChildren); consumer.consume(out, lazyPanelId, childWidget, widgetCreator.getWidgetFactoryDeclaration(), context.getWidgetElement()); ((LazyCompatibleWidgetConsumer)consumer).handleLazyWrapChildrenCreation(out, context.getWidgetId()); } else { childWidget = widgetCreator.createChildWidget(out, context.getChildElement(), context); } boolean childPartialSupport = widgetCreator.hasChildPartialSupport(context.getChildElement()); if (childPartialSupport) { out.println("if ("+widgetCreator.getChildWidgetClassName(context.getChildElement())+".isSupported()){"); } if (!Widget.class.isAssignableFrom(widgetCreator.getChildWidgetClass(context.getChildElement()))) { childWidget = childWidget+".asWidget()"; } if (!StringUtils.isEmpty(method)) { out.println(context.getWidget()+"."+method+"("+childWidget+");"); } else if (!StringUtils.isEmpty(widgetProperty)) { out.println(context.getWidget()+"."+ClassUtils.getSetterMethod(widgetProperty)+"("+childWidget+");"); } else { out.println(context.getWidget()+".add("+childWidget+");"); } if (childPartialSupport) { out.println("}"); } } @Override void postProcessChild(SourcePrinter out, WidgetCreatorContext context) { if (isHasPostProcessor) { ((HasPostProcessor)processor).postProcessChildren(out, context); } } }; } /** * @param acceptNoChildren * @param childrenProcessor * @param childProcessorClass * @param isAgregator * @param processor */ private void createChildProcessorForMultipleChildrenProcessor(boolean acceptNoChildren, ChildrenProcessor childrenProcessor, Class<?> childProcessorClass, boolean isAgregator, WidgetChildProcessor<?> processor, Device[] supportedDevices) { TagConstraints processorConstraints = this.widgetCreator.getTagConstraints(childProcessorClass); final String widgetProperty = (processorConstraints!=null?processorConstraints.widgetProperty():""); final String method = (processorConstraints!=null?processorConstraints.method():""); String tagName = (processorConstraints!=null?processorConstraints.tagName():""); boolean autoProcessingEnabled = (processorConstraints!=null?processorConstraints.autoProcessingEnabled():false); final boolean applyDeviceFilters = processorConstraints!=null?processorConstraints.applyDeviceFilters():false; final boolean isAnyWidget = isAnyWidget(childProcessorClass, processorConstraints); TagChildLazyConditions lazyConditions = childProcessorClass.getAnnotation(TagChildLazyConditions.class); final WidgetLazyChecker lazyChecker = (lazyConditions== null?null:LazyWidgets.initializeLazyChecker(lazyConditions)); final String childName = getChildTagName(tagName, isAgregator, isAnyWidget); ChildProcessor childProcessor = createChildProcessor(isAnyWidget, widgetProperty, method, lazyChecker, processor, supportedDevices, applyDeviceFilters, autoProcessingEnabled); if (!isAnyWidget) { childProcessor.setChildrenProcessor(scanChildren(childProcessorClass, isAgregator)); } childrenProcessor.addChildProcessor(childName, childProcessor); } /** * @param processorClass * @param child * @param acceptNoChildren * @return */ private ChildrenProcessor createChildProcessorForText(Class<?> processorClass, TagChild child, final boolean acceptNoChildren) { Class<?> childProcessor = child.value(); TagConstraints processorAttributes = widgetCreator.getTagConstraints(childProcessor); final String widgetProperty = processorAttributes.widgetProperty(); final boolean isHasText = HasText.class.isAssignableFrom(widgetCreator.getWidgetClass()); ChildrenProcessor childrenProcessor = new ChildrenProcessor(widgetCreator) { public void processChildren(SourcePrinter out, WidgetCreatorContext context) { String child = widgetCreator.ensureTextChild(context.getChildElement(), acceptNoChildren, context.getWidgetId(), true); if (child != null) { if (!StringUtils.isEmpty(widgetProperty)) { out.println(context.getWidget()+"."+ClassUtils.getSetterMethod(widgetProperty)+"("+child+");"); } else if (isHasText) { out.println(context.getWidget()+".setText("+child+");"); } else { throw new CruxGeneratorException("Can not process the text property for widget ["+context.getWidgetId()+"]. The widget is not assignable to HasText and its factory does not define any property for text value."); } } } }; scannedProcessors.put(processorClass.getCanonicalName(), childrenProcessor); return childrenProcessor; } /** * @param processorClass * @param children * @param acceptNoChildren * @param isAgregatorChild * @return */ private ChildrenProcessor createChildrenProcessorForMultipleChildren(Class<?> processorClass, TagChildren children, boolean acceptNoChildren, boolean isAgregatorChild) { try { ChildrenProcessor childrenProcessor = doCreateChildrenProcessorForMultipleChildren(processorClass, acceptNoChildren, isAgregatorChild); boolean hasAgregator = false; for (TagChild child : children.value()) { if (child.autoProcess()) { Class<?> childProcessorClass = child.value(); final boolean isTextProcessor = TextChildProcessor.class.isAssignableFrom(childProcessorClass); if (isTextProcessor) { throw new CruxGeneratorException("A TextProcessor child processor can not have any sibling processor defined."); } boolean isAgregator = isAgregatorProcessor(childProcessorClass); if (isAgregator) { if (hasAgregator) { throw new CruxGeneratorException("You can not define more than one agregator under the same parent processor."); } hasAgregator = true; } WidgetChildProcessor<?> processor; processor = child.value().newInstance(); processor.setWidgetCreator(widgetCreator); createChildProcessorForMultipleChildrenProcessor(acceptNoChildren, childrenProcessor, childProcessorClass, isAgregator, processor, child.supportedDevices()); } } return childrenProcessor; } catch (Exception e) { throw new CruxGeneratorException("Error creating ChildrenProcessor class.", e); } } /** * @param processorClass * @param child * @param acceptNoChildren * @return */ private ChildrenProcessor createChildrenProcessorForSingleChild(Class<?> processorClass, TagChild child, final boolean acceptNoChildren) { try { if (!child.autoProcess()) { return null; } Class<?> childProcessor = child.value(); final boolean isTextProcessor = TextChildProcessor.class.isAssignableFrom(childProcessor); if (isTextProcessor) { return createChildProcessorForText(processorClass, child, acceptNoChildren); } WidgetChildProcessor<?> processor; processor = child.value().newInstance(); processor.setWidgetCreator(widgetCreator); Device[] supportedDevices = child.supportedDevices(); ChildrenProcessor childrenProcessor = doCreateChildrenProcessorForSingleChild(processorClass, acceptNoChildren, processor, childProcessor, supportedDevices); return childrenProcessor; } catch (Exception e) { throw new CruxGeneratorException("Error creating ChildrenProcessor class.", e); } } /** * @param processorClass * @param acceptNoChildren * @param isAgregatorChild * @param processor * @param processorMethod * @param childProcessorClass * @return */ private ChildrenProcessor doCreateChildrenProcessorForMultipleChildren(Class<?> processorClass, final boolean acceptNoChildren, final boolean isAgregatorChild) { ChildrenProcessor childrenProcessor = new ChildrenProcessor(widgetCreator) { public void processChildren(SourcePrinter out, WidgetCreatorContext context) { String childName; if (isAgregatorChild) { childName = getChildName(context.getChildElement()); processChild(out, context, childName); } else { JSONArray children = widgetCreator.ensureChildren(context.getChildElement(), acceptNoChildren, context.getWidgetId()); if (children != null) { for (int i = 0; i < children.length(); i++) { JSONObject child = children.optJSONObject(i); childName = getChildName(child); context.setChildElement(child); processChild(out, context, childName); } } } } private String getChildName(JSONObject child) { String childName; if (widgetCreator.isWidget(child)) { childName = "_innerWidget"; } else { childName = WidgetCreator.getChildName(child); } if (!hasChildProcessor(childName)) { childName = "_agregator"; } return childName; } }; scannedProcessors.put(processorClass.getCanonicalName(), childrenProcessor); return childrenProcessor; } /** * @param processorClass * @param acceptNoChildren * @param processor * @param childProcessorClass * @return */ private ChildrenProcessor doCreateChildrenProcessorForSingleChild(Class<?> processorClass, final boolean acceptNoChildren, WidgetChildProcessor<?> processor, Class<?> childProcessorClass, Device[] supportedDevices) { TagConstraints processorConstraints = this.widgetCreator.getTagConstraints(childProcessorClass); final String widgetProperty = (processorConstraints!=null?processorConstraints.widgetProperty():""); final String method = (processorConstraints!=null?processorConstraints.method():""); String tagName = (processorConstraints!=null?processorConstraints.tagName():""); boolean autoProcessingEnabled = (processorConstraints!=null?processorConstraints.autoProcessingEnabled():false); final boolean applyDeviceFilters = processorConstraints!=null?processorConstraints.applyDeviceFilters():false; final boolean isAgregator = isAgregatorProcessor(childProcessorClass); final boolean isAnyWidget = isAnyWidget(childProcessorClass, processorConstraints); TagChildLazyConditions lazyConditions = childProcessorClass.getAnnotation(TagChildLazyConditions.class); final WidgetLazyChecker lazyChecker = (lazyConditions== null?null:LazyWidgets.initializeLazyChecker(lazyConditions)); final String childName = getChildTagName(tagName, isAgregator, isAnyWidget); ChildrenProcessor childrenProcessor = new ChildrenProcessor(widgetCreator) { public void processChildren(SourcePrinter out, WidgetCreatorContext context) { JSONObject child = widgetCreator.ensureFirstChild(context.getChildElement(), acceptNoChildren, context.getWidgetId()); if (child != null) { context.setChildElement(child); processChild(out, context, childName); } } }; scannedProcessors.put(processorClass.getCanonicalName(), childrenProcessor); ChildProcessor childProcessor = createChildProcessor(isAnyWidget, widgetProperty, method, lazyChecker, processor, supportedDevices, applyDeviceFilters, autoProcessingEnabled); if (!isAnyWidget) { childProcessor.setChildrenProcessor(scanChildren(childProcessorClass, isAgregator)); } childrenProcessor.addChildProcessor(childName, childProcessor); return childrenProcessor; } private boolean isAnyWidget(Class<?> childProcessorClass, TagConstraints processorConstraints) { final boolean isAnyWidget = (AnyWidgetChildProcessor.class.isAssignableFrom(childProcessorClass)) || (processorConstraints!=null && (AnyWidget.class.isAssignableFrom(processorConstraints.type()) || WidgetCreator.class.isAssignableFrom(processorConstraints.type()))); return isAnyWidget; } /** * * @param children * @return */ private AllowedOccurrences getAllowedOccurrences(TagChildren children) { AllowedOccurrences allowed = new AllowedOccurrences(); for (TagChild child: children.value()) { if (children.value().length > 1 && TextChildProcessor.class.isAssignableFrom(child.value())) { throw new CruxGeneratorException("Error generating widget factory. An element can not contains text and other children."); } if (child.autoProcess()) { AllowedOccurrences allowedForChild = getAllowedOccurrencesForChild(child); mergeAllowedOccurrences(allowed, allowedForChild); } } return allowed; } /** * * @param child * @return */ private AllowedOccurrences getAllowedOccurrencesForChild(TagChild child) { AllowedOccurrences allowed = new AllowedOccurrences(); try { Class<?> childProcessorType = child.value(); TagConstraints processorAttributes = widgetCreator.getTagConstraints(childProcessorType); if (processorAttributes != null) { String minOccurs = processorAttributes.minOccurs(); if (minOccurs.equals("unbounded")) { allowed.minOccurs = UNBOUNDED; } else { allowed.minOccurs = Integer.parseInt(minOccurs); } String maxOccurs = processorAttributes.maxOccurs(); if (maxOccurs.equals("unbounded")) { allowed.maxOccurs = UNBOUNDED; } else { allowed.maxOccurs = Integer.parseInt(maxOccurs); } } else if (AllChildProcessor.class.isAssignableFrom(child.value()) || SequenceChildProcessor.class.isAssignableFrom(child.value())) { TagChildren tagChildren = childProcessorType.getAnnotation(TagChildren.class); if (tagChildren != null) { AllowedOccurrences allowedChildren = getAllowedOccurrences(tagChildren); mergeAllowedOccurrences(allowed, allowedChildren); } } else { allowed.minOccurs = 1; allowed.maxOccurs = 1; } return allowed; } catch (Exception e) { throw new CruxGeneratorException(e.getMessage(), e); } } /** * @param tagName * @param isAgregator * @param isAnyWidget * @return */ private String getChildTagName(String tagName, final boolean isAgregator, final boolean isAnyWidget) { final String childName; if (isAnyWidget) { childName = "_innerWidget"; } else if (isAgregator) { childName = "_agregator"; } else { childName = tagName; } if (StringUtils.isEmpty(childName)) { throw new CruxGeneratorException("Invalid tagName for child processor."); } return childName; } /** * @param childProcessorClass * @return */ private boolean isAgregatorProcessor(Class<?> childProcessorClass) { return (ChoiceChildProcessor.class.isAssignableFrom(childProcessorClass) || SequenceChildProcessor.class.isAssignableFrom(childProcessorClass) || AllChildProcessor.class.isAssignableFrom(childProcessorClass)); } /** * @param allowed * @param allowedForChild */ private void mergeAllowedOccurrences(AllowedOccurrences allowed, AllowedOccurrences allowedForChild) { if (allowedForChild.minOccurs == UNBOUNDED) { allowed.minOccurs = UNBOUNDED; } else if (allowed.minOccurs != UNBOUNDED) { allowed.minOccurs += allowedForChild.minOccurs; } if (allowedForChild.maxOccurs == UNBOUNDED) { allowed.maxOccurs = UNBOUNDED; } else if (allowed.maxOccurs != UNBOUNDED) { allowed.maxOccurs += allowedForChild.maxOccurs; } } /** * @param children * @return */ private boolean mustGenerateChildrenProcessMethod(TagChildren children) { if (children != null) { for (TagChild child : children.value()) { if (child.autoProcess()) { return true; } } } return false; } /** * @param processChildrenMethod * @return */ private ChildrenProcessor scanChildren(Class<?> processorClass, boolean isAgregatorChild) { String processorName = processorClass.getCanonicalName(); if (scannedProcessors.containsKey(processorName)) { return scannedProcessors.get(processorName); } ChildrenProcessor result = null; TagChildren children = processorClass.getAnnotation(TagChildren.class); if (children != null && mustGenerateChildrenProcessMethod(children)) { AllowedOccurrences allowedChildren = getAllowedOccurrences(children); boolean acceptNoChildren = (allowedChildren.minOccurs == 0); if (allowedChildren.maxOccurs == 1) { TagChild child = children.value()[0]; result = createChildrenProcessorForSingleChild(processorClass, child, acceptNoChildren); } else { result = createChildrenProcessorForMultipleChildren(processorClass, children, acceptNoChildren, isAgregatorChild); } } return result; } /** * * @author Thiago da Rosa de Bustamante * */ private static class AllowedOccurrences { int maxOccurs = 0; int minOccurs = 0; } }