/*
* Copyright 2014 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.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import org.cruxframework.crux.core.client.screen.binding.DataObjectBinder;
import org.cruxframework.crux.core.client.utils.EscapeUtils;
import org.cruxframework.crux.core.client.utils.StringUtils;
import org.cruxframework.crux.core.rebind.AbstractProxyCreator.SourcePrinter;
import org.cruxframework.crux.core.rebind.CruxGeneratorException;
import org.cruxframework.crux.core.rebind.context.RebindContext;
import org.cruxframework.crux.core.rebind.screen.View;
import org.cruxframework.crux.core.rebind.screen.widget.ViewFactoryCreator.DataBindingProcessor;
import org.cruxframework.crux.core.utils.JClassUtils;
import org.cruxframework.crux.core.utils.RegexpPatterns;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.dev.util.collect.HashSet;
/**
* @author Thiago da Rosa de Bustamante
*
*/
public class ViewBindHandler
{
private RebindContext context;
private Map<String, String> dataObjectBinderVariables = new HashMap<String, String>();
private Set<String> dataObjects = new HashSet<String>();
private View view;
private ViewFactoryCreator viewFactoryCreator;
public ViewBindHandler(RebindContext context, View view, ViewFactoryCreator viewFactoryCreator)
{
this.context = context;
this.view = view;
this.viewFactoryCreator = viewFactoryCreator;
}
protected void addDataObject(String dataObject)
{
dataObjects.add(dataObject);
}
/**
* Retrieve the variable name for the dataObjectBinder associated with the given alias.
* @param dataObjectAlias
* @param out
* @return
*/
protected String getDataObjectBinderVariable(String dataObjectAlias, SourcePrinter out)
{
String dataObjectBinder = dataObjectBinderVariables.get(dataObjectAlias);
if (dataObjectBinder == null)
{
dataObjectBinder = ViewFactoryCreator.createVariableName("dataObjectBinder");
dataObjectBinderVariables.put(dataObjectAlias, dataObjectBinder);
String dataObjectClassName = context.getDataObjects().getDataObject(dataObjectAlias);//getObjectDataBinding(dataObjectAlias).getDataObjectClassName();
out.println("final " + DataObjectBinder.class.getCanonicalName() + "<" + dataObjectClassName + "> " + dataObjectBinder + "=" +
ViewFactoryCreator.getViewVariable() + ".getDataObjectBinder("+EscapeUtils.quote(dataObjectAlias)+");");
}
return dataObjectBinder;
}
protected ExpressionDataBinding getExpressionDataBinding(String propertyValue, String widgetClassName,
String widgetPropertyPath, String uiObjectClassName, String getUiObjectExpression, DataBindingProcessor dataBindingProcessor,
String setterMethod, String propertyTypeName)
{
if (propertyValue == null)
{
return null;
}
JClassType widgetType = context.getGeneratorContext().getTypeOracle().findType(widgetClassName);
JClassType uiObjectType = (uiObjectClassName!=null?context.getGeneratorContext().getTypeOracle().findType(uiObjectClassName):widgetType);
JType widgetPropertyType;
if (!StringUtils.isEmpty(propertyTypeName))
{
widgetPropertyType = context.getGeneratorContext().getTypeOracle().findType(propertyTypeName);
}
else
{
widgetPropertyType = JClassUtils.getPropertyType(uiObjectType, widgetPropertyPath);
}
if (widgetPropertyType == null)
{
throw new CruxGeneratorException("Can not find out the widget property type, for property ["+widgetPropertyPath+"], on widget ["+widgetClassName+"]");
}
String trimPropertyValue = propertyValue.trim();
if (RegexpPatterns.REGEXP_CRUX_READ_ONLY_OBJECT_DATA_BINDING.matcher(trimPropertyValue).matches())
{
return getReadOnlyObjectBindingExpression(widgetPropertyPath, widgetPropertyType, widgetType,
trimPropertyValue, uiObjectType, getUiObjectExpression, dataBindingProcessor, setterMethod);
}
else if (widgetPropertyType == JPrimitiveType.BOOLEAN || Boolean.class.getCanonicalName().equals(widgetPropertyType.getQualifiedSourceName()))
{
if (RegexpPatterns.REGEXP_CRUX_EXPRESSION_DATA_BINDING.matcher(trimPropertyValue).matches())
{
return getLogicalBindingExpression(widgetType, widgetPropertyPath, trimPropertyValue, uiObjectType,
getUiObjectExpression, dataBindingProcessor, setterMethod);
}
}
else
{
return getMultipleBindingsExpression(widgetType, widgetPropertyPath, trimPropertyValue, uiObjectType,
getUiObjectExpression, dataBindingProcessor, setterMethod);
}
return null;
}
protected PropertyBindInfo getObjectDataBinding(String propertyValue, String widgetClassName, String widgetPropertyPath,
boolean boundToAttribute, String uiObjectClassName, String getUiObjectExpression,
DataBindingProcessor dataBindingProcessor)
{
String trimPropertyValue = propertyValue.trim();
if (isObjectDataBinding(trimPropertyValue, dataBindingProcessor))
{
return getPropertyBindInfo(widgetClassName, boundToAttribute, widgetPropertyPath, trimPropertyValue, uiObjectClassName,
getUiObjectExpression, dataBindingProcessor);
}
else
{
return null;
}
}
protected Iterator<String> iterateDataObjects()
{
return dataObjects.iterator();
}
private void checkDataObjectType(String dataObject, JClassType dataObjectType)
{
if (dataObjectType == null)
{
String message = "DataObject ["+dataObject+"], refered on view ["+view.getId()+"], could not be loaded. "
+ "\n Possible causes:"
+ "\n\t 1. Check if any type or subtype used by resource refers to another module and if this module is inherited in the .gwt.xml file."
+ "\n\t 2. Check if your resource or its members belongs to a client package."
+ "\n\t 3. Check the versions of all your modules."
;
throw new CruxGeneratorException(message);
}
}
/**
* Split the dataBindReference and separate the DataObject Class alias from the requested property and converters
*
* @param text
* @param removeBraces
* @return
*/
private String[] getBindingParts(String text, boolean removeBraces)
{
boolean hasConverter = text.indexOf(":") > 0;
boolean hasConverterParams = hasConverter && text.indexOf("(", text.indexOf(":")) > 0;
if (removeBraces)
{
text = text.substring(2, text.length()-1);
}
String[] result = new String[hasConverterParams?4:hasConverter?3:2];
int index = text.indexOf('.');
result[0] = text.substring(0, index);
String path = text.substring(index+1);
if (hasConverter)
{
int endPathIndex = path.indexOf(':');
result[1] = path.substring(0, endPathIndex);
if (hasConverterParams)
{
int paramStartPath = path.indexOf('(', endPathIndex);
result[2] = path.substring(endPathIndex+1, paramStartPath);
result[3] = EscapeUtils.quote(path.substring(paramStartPath+2, path.length()-2));
}
else
{
result[2] = path.substring(endPathIndex+1);
}
}
else
{
result[1] = path;
}
return result;
}
private ExpressionPart getExpressionPart(String bindingDeclaration, DataBindingProcessor dataBindingProcessor)
{
String[] bindParts = getBindingParts(bindingDeclaration, false);
String dataObject = bindParts[0];
String bindPath = bindParts[1];
String converter = bindParts.length > 2 ? bindParts[2] : null;
String dataObjectAlias = dataBindingProcessor.getDataObjectAlias(dataObject);
String dataObjectClassName = context.getDataObjects().getDataObject(dataObjectAlias);
if (dataObjectAlias.equals(dataObject))
{
addDataObject(dataObject);
}
JClassType dataObjectType = context.getGeneratorContext().getTypeOracle().findType(dataObjectClassName);
JClassType converterType = getConverterType(context, converter);
String converterParams = null;
if (bindParts.length > 3 && converterType != null &&
converterType.findConstructor(new JType[]{context.getGeneratorContext().getTypeOracle().findType(String.class.getCanonicalName())}) != null)
{
converterParams = bindParts[3];
}
checkDataObjectType(dataObject, dataObjectType);
try
{
return new ExpressionPart(bindPath, dataObjectType, converterType, dataObject, converterParams);
}
catch (NoSuchFieldException e)
{
throw new CruxGeneratorException("DataObject ["+dataObject+"], refered on view ["+view.getId()+"], has an invalid bind expression ["+bindPath+"]", e);
}
}
private ExpressionDataBinding getLogicalBindingExpression(JClassType widgetType, String widgetPropertyPath,
String trimPropertyValue, JClassType uiObjectType, String getUiObjectExpression, DataBindingProcessor dataBindingProcessor,
String setterMethod)
{
ExpressionDataBinding result;
trimPropertyValue = trimPropertyValue.substring(2, trimPropertyValue.length()-2);
int index = trimPropertyValue.indexOf('(');
String operator = trimPropertyValue.substring(0, index);
trimPropertyValue = trimPropertyValue.substring(index+1);
String[] parts = trimPropertyValue.split("\\s");
List<ExpressionPart> expressionParts = new ArrayList<ExpressionPart>();
for (String part : parts)
{
ExpressionPart expressionPart = getExpressionPart(part, dataBindingProcessor);
expressionParts.add(expressionPart);
}
result = new ExpressionDataBinding(context, widgetType, widgetPropertyPath, uiObjectType, getUiObjectExpression, setterMethod);
boolean negate = operator.startsWith("NOT ");
if (negate)
{
operator = operator.substring(4);
}
ExpressionDataBinding.LogicalOperation logicalOperations = ExpressionDataBinding.LogicalOperation.valueOf(operator.trim());
result.addLogicalBinding(expressionParts, logicalOperations, negate);
return result;
}
private ExpressionDataBinding getMultipleBindingsExpression(JClassType widgetType, String widgetPropertyPath,
String trimPropertyValue, JClassType uiObjectType, String getUiObjectExpression, DataBindingProcessor dataBindingProcessor,
String setterMethod)
{
ExpressionDataBinding result;
Matcher matcher = RegexpPatterns.REGEXP_CRUX_OBJECT_DATA_BINDING.matcher(trimPropertyValue);
result = new ExpressionDataBinding(context, widgetType, widgetPropertyPath, uiObjectType, getUiObjectExpression, setterMethod);
int pos = 0;
boolean hasExpression = false;
while (matcher.find())
{
hasExpression = true;
if (pos != matcher.start())
{
String literal = viewFactoryCreator.resolveI18NString(trimPropertyValue.substring(pos, matcher.start()));
result.addStringConstant(literal, false);
}
pos = matcher.end();
String group = matcher.group();
group = group.substring(2, group.length()-1);
ExpressionPart expressionPart = getExpressionPart(group, dataBindingProcessor);
result.addReadBinding(expressionPart);
}
if (!hasExpression)
{
return null;
}
if (pos != trimPropertyValue.length())
{
String i18nString = viewFactoryCreator.resolveI18NString(trimPropertyValue.substring(pos));
result.addStringConstant(i18nString, false);
}
return result;
}
private PropertyBindInfo getPropertyBindInfo(String widgetClassName, boolean boundToAttribute, String widgetPropertyPath, String propertyValue,
String uiObjectClassName, String getUiObjectExpression, DataBindingProcessor dataBindingProcessor)
{
String[] bindParts = getBindingParts(propertyValue, true);
String dataObject = bindParts[0];
String bindPath = bindParts[1];
String converter = bindParts.length > 2 ? bindParts[2] : null;
String dataObjectAlias = dataBindingProcessor.getDataObjectAlias(dataObject);
String dataObjectClassName = context.getDataObjects().getDataObject(dataObjectAlias);
if (dataObjectAlias.equals(dataObject))
{
addDataObject(dataObject);
}
JClassType widgetType = context.getGeneratorContext().getTypeOracle().findType(widgetClassName);
JClassType uiObjectType = uiObjectClassName!= null?context.getGeneratorContext().getTypeOracle().findType(uiObjectClassName): widgetType;
JClassType dataObjectType = context.getGeneratorContext().getTypeOracle().findType(dataObjectClassName);
JClassType converterType = getConverterType(context, converter);
String converterParams = null;
if (bindParts.length > 3 && converterType != null &&
converterType.findConstructor(new JType[]{context.getGeneratorContext().getTypeOracle().findType(String.class.getCanonicalName())}) != null)
{
converterParams = bindParts[3];
}
checkDataObjectType(dataObject, dataObjectType);
try
{
return new PropertyBindInfo(widgetPropertyPath, boundToAttribute, bindPath, widgetType, dataObjectType,
converterType, dataObject, converterParams, uiObjectType, getUiObjectExpression);
}
catch (NoSuchFieldException e)
{
throw new CruxGeneratorException("DataObject ["+dataObject+"], refered on view ["+view.getId()+"], has an invalid bind expression ["+bindPath+"]", e);
}
}
private ExpressionDataBinding getReadOnlyObjectBindingExpression(String widgetPropertyPath, JType widgetPropertyType,
JClassType widgetType, String trimPropertyValue, JClassType uiObjectType, String getUiObjectExpression,
DataBindingProcessor dataBindingProcessor, String setterMethod)
{
ExpressionDataBinding result;
trimPropertyValue = trimPropertyValue.substring(3, trimPropertyValue.length()-1);
ExpressionPart expressionPart = getExpressionPart(trimPropertyValue, dataBindingProcessor);
result = new ExpressionDataBinding(context, widgetType, widgetPropertyPath, uiObjectType, getUiObjectExpression, setterMethod);
result.addReadBinding(expressionPart);
if (!JClassUtils.isCompatibleTypes(widgetPropertyType, expressionPart.getType()))
{
throw new CruxGeneratorException("Invalid binding declaration. DataObject property [" +
trimPropertyValue + "] can not be cast to type ["+widgetPropertyType.getSimpleSourceName()+"]");
}
return result;
}
/**
* Returns <code>true</code> if the given text is a binding declaration to a dataObject property.
* @param text
* @param dataBindingProcessor
* @return <code>true</code> if the given text is a binding declaration.
*/
private boolean isObjectDataBinding(String text, DataBindingProcessor dataBindingProcessor)
{
if (text!= null && RegexpPatterns.REGEXP_CRUX_OBJECT_DATA_BINDING.matcher(text).matches())
{
String[] parts = getBindingParts(text, true);
String dataObjectAlias = dataBindingProcessor.getDataObjectAlias(parts[0]);
return (context.getDataObjects().getDataObject(dataObjectAlias) != null);
}
return false;
}
public static JClassType getConverterType(RebindContext context, String bindConverter)
{
JClassType converterType = null;
if (!StringUtils.isEmpty(bindConverter))
{
String converterClassName = context.getConverters().getConverter(bindConverter);
converterType = context.getGeneratorContext().getTypeOracle().findType(converterClassName);
}
return converterType;
}
}