/*
* 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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.cruxframework.crux.core.client.screen.binding.NativeWrapper;
import org.cruxframework.crux.core.client.utils.EscapeUtils;
import org.cruxframework.crux.core.client.utils.StringUtils;
import org.cruxframework.crux.core.rebind.CruxGeneratorException;
import org.cruxframework.crux.core.rebind.context.RebindContext;
import org.cruxframework.crux.core.utils.JClassUtils;
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.dom.client.Element;
import com.ibm.icu.text.MessageFormat;
/**
* @author Thiago da Rosa de Bustamante
*
*/
public class ExpressionDataBinding
{
protected static String WIDGET_VAR_REF = "widget";
private RebindContext context;
private Set<String> convertersDeclarations = new HashSet<String>();
private Set<String> dataObjects = new HashSet<String>();
private StringBuilder expression = new StringBuilder();
private JType expressionType;
private String getUiObjectExpression;
private String setterMethod;
private JClassType uiObjectType;
private String widgetClassName;
private String widgetPropertyPath;
private JClassType widgetType;
public ExpressionDataBinding(RebindContext context, JClassType widgetType, String widgetPropertyPath,
JClassType uiObjectType, String getUiObjectExpression, String setterMethod)
{
this.context = context;
this.widgetType = widgetType;
this.widgetPropertyPath = widgetPropertyPath;
this.setterMethod = setterMethod;
this.uiObjectType = uiObjectType==null?widgetType:uiObjectType;
this.getUiObjectExpression = getUiObjectExpression;
this.widgetClassName = widgetType.getQualifiedSourceName();
}
public void addLogicalBinding(List<ExpressionPart> expressionParts, LogicalOperation logicalOperation, boolean negate)
{
assert (expression.length() == 0):"Invalid Expression. Can not add logical binnding on a multi expression binding";
if (negate)
{
expression.append("!(");
}
boolean first = true;
for (ExpressionPart expressionPart : expressionParts)
{
if (!first)
{
expression.append(" && ");
}
first = false;
String readExpression = expressionPart.getExpression(expressionPart.getDataObjectReadExpression(), "${"+expressionPart.getDataObject()+"}");
if (logicalOperation == LogicalOperation.NULL)
{
addLogicalBindingForNullOperation(expressionPart, readExpression);
}
else if (logicalOperation == LogicalOperation.FILLED)
{
addLogicalBindingForFilledOperation(expressionPart, readExpression);
}
else if (logicalOperation == LogicalOperation.EMPTY)
{
addLogicalBindingForEmptyOperation(expressionPart, readExpression);
}
else if (logicalOperation == LogicalOperation.IS)
{
expression.append(readExpression);
}
else
{
addLogicalBindingForNumericOperations(expressionPart, logicalOperation, readExpression);
}
dataObjects.add(expressionPart.getDataObject());
appendConverterDeclaration(expressionPart);
}
if (negate)
{
expression.append(")");
}
expressionType = JPrimitiveType.BOOLEAN;
}
public void addReadBinding(ExpressionPart expressionPart)
{
if (expression.length() > 0)
{
expression.append(" + ");
}
dataObjects.add(expressionPart.getDataObject());
//expressionParts.add(expressionPart);
expression.append(expressionPart.getExpression(expressionPart.getDataObjectReadExpression(), "${"+expressionPart.getDataObject()+"}"));
appendConverterDeclaration(expressionPart);
if (expressionType == null)
{
expressionType = expressionPart.getType();
}
else if (!expressionType.getQualifiedSourceName().equals(String.class.getCanonicalName()))
{
JPrimitiveType primitive = expressionPart.getType().isPrimitive();
if (primitive != null)
{
if (primitive == JPrimitiveType.BOOLEAN || primitive == JPrimitiveType.CHAR)
{
expressionType = primitive;
}
else if (primitive == JPrimitiveType.VOID)
{
expressionType = context.getGeneratorContext().getTypeOracle().findType(String.class.getCanonicalName());;
}
else
{
expressionType = JPrimitiveType.DOUBLE;
}
}
}
}
public void addStringConstant(String constant, boolean needsQuote)
{
if (constant == null || constant.length() == 0)
{
return;
}
if (expression.length() > 0)
{
expression.append(" + ");
}
if (needsQuote)
{
expression.append(EscapeUtils.quote(constant));
}
else
{
expression.append(constant);
}
expressionType = context.getGeneratorContext().getTypeOracle().findType(String.class.getCanonicalName());
}
public Set<String> getConverterDeclarations()
{
return convertersDeclarations;
}
public String getExpression(String resultVariable, String contextVariable, String collectionDataObjectRef, String collectionItemVar)
{
StringBuilder result = new StringBuilder();
result.append(resultVariable + " = " + expression);
for (String dataObject : dataObjects)
{
String dataObjectReference = "${"+dataObject+"}";
int index;
boolean isCollectionObjectReference = collectionDataObjectRef != null && collectionItemVar.equals(dataObject);
String dataObjectVar = isCollectionObjectReference?collectionDataObjectRef:ViewFactoryCreator.createVariableName(dataObject);
boolean isReferenced = false;
while ((index = result.indexOf(dataObjectReference)) >= 0)
{
isReferenced = !isCollectionObjectReference;
result.replace(index, index+dataObjectReference.length(), dataObjectVar);
}
if (isReferenced)
{
String dataObjectClassName = context.getDataObjects().getDataObject(dataObject);
result.insert(0, dataObjectClassName+" "+dataObjectVar+" = "+contextVariable+".getDataObject("+EscapeUtils.quote(dataObject)+");\n");
}
}
return result.toString();
}
public JType getType()
{
return expressionType;
}
public String getUiObjectClassName()
{
return uiObjectType.getParameterizedQualifiedSourceName();
}
public String getUiObjectExpression()
{
return getUiObjectExpression;
}
public String getUIObjectVar(String widgetVar)
{
if (!hasUiObjectExpression())
{
return widgetVar;
}
String uiObjectVar = MessageFormat.format(getUiObjectExpression, widgetVar);
return uiObjectVar;
}
public String getWidgetClassName()
{
return widgetClassName;
}
public String getWriteExpression(String contextVariable) throws NoSuchFieldException
{
return getWriteExpression(contextVariable, WIDGET_VAR_REF, null, null, null);
}
public String getWriteExpression(String contextVariable, String widgetVar, String collectionDataObjectRef,
String collectionItemVar, String uiObjectVariable)
throws NoSuchFieldException
{
StringBuilder writeExpression = new StringBuilder();
String uiObjectVar = getUIObjectVar(widgetVar);
boolean hasUiObjectVariable = !StringUtils.isEmpty(uiObjectVariable);
boolean createAuxiliaryVariable = hasUiObjectExpression() && !hasUiObjectVariable;
uiObjectVariable = createAuxiliaryVariable?ViewFactoryCreator.createVariableName("uiObjectVariable"):
(hasUiObjectVariable?uiObjectVariable:uiObjectVar);
if (createAuxiliaryVariable)
{
writeExpression.append(getUiObjectClassName() + " " + uiObjectVariable + " = " + uiObjectVar + ";\n");
}
writeExpression.append("if ("+uiObjectVariable + " != null)\n");
if (!StringUtils.isEmpty(setterMethod))
{
writeExpression.append(uiObjectVariable+"."+setterMethod+"("+expression.toString()+");");
}
else
{
boolean nativeWrapper = getUiObjectClassName().equals(NativeWrapper.class.getCanonicalName()) ||
getUiObjectClassName().equals(Element.class.getCanonicalName());
if (nativeWrapper)
{
String propertySetter = DataBindingNativeTypeResolver.resolveTypeForProperty(widgetPropertyPath).getSetter();
writeExpression.append(uiObjectVariable+"."+propertySetter+"("+EscapeUtils.quote(widgetPropertyPath)+","+expression.toString()+");");
}
else
{
JClassUtils.buildSetValueExpression(writeExpression, uiObjectType, widgetPropertyPath, uiObjectVariable, expression.toString());
}
}
for (String dataObject : dataObjects)
{
String dataObjectReference = "${"+dataObject+"}";
int index;
boolean isCollectionObjectReference = collectionDataObjectRef != null && collectionItemVar.equals(dataObject);
String dataObjectVar = isCollectionObjectReference?collectionDataObjectRef:ViewFactoryCreator.createVariableName(dataObject);
boolean isReferenced = false;
while ((index = writeExpression.indexOf(dataObjectReference)) >= 0)
{
isReferenced = !isCollectionObjectReference;
writeExpression.replace(index, index+dataObjectReference.length(), dataObjectVar);
}
if (isReferenced)
{
String dataObjectClassName = context.getDataObjects().getDataObject(dataObject);
writeExpression.insert(0, dataObjectClassName+" "+dataObjectVar+" = "+contextVariable+".getDataObject("+EscapeUtils.quote(dataObject)+");\n");
}
}
return writeExpression.toString();
}
public boolean hasUiObjectExpression()
{
return !StringUtils.isEmpty(getUiObjectExpression);
}
public Iterator<String> iterateDataObjects()
{
return dataObjects.iterator();
}
private void addLogicalBindingForEmptyOperation(ExpressionPart expressionPart, String readExpression)
{
JType type = expressionPart.getType();
if (type.isArray() != null)
{
expression.append(readExpression + " != null ? "+readExpression + ".length == 0 : true");
}
else if (JClassUtils.isCollection(type))
{
expression.append(readExpression + " != null ? "+readExpression + ".size() == 0 : true");
}
else
{
throw new CruxGeneratorException("Invalid expression. property ["+widgetPropertyPath+"] "
+ "of widget ["+widgetType.getQualifiedSourceName()+"] is not a valid collection or array.");
}
appendConverterDeclaration(expressionPart);
}
private void addLogicalBindingForFilledOperation(ExpressionPart expressionPart, String readExpression)
{
JType expressionType = expressionPart.getType();
if (expressionType.getQualifiedSourceName().equals(String.class.getCanonicalName()))
{
expression.append("!"+StringUtils.class.getCanonicalName()+".isEmpty("+readExpression+")");
}
else
{
String emptyValueForType = JClassUtils.getEmptyValueForType(expressionType);
if(emptyValueForType == null)
{
throw new CruxGeneratorException("Invalid expression. property ["+widgetPropertyPath+"] "
+ "of widget ["+widgetType.getQualifiedSourceName()+"] is void.");
}
expression.append(readExpression+" != "+emptyValueForType);
}
appendConverterDeclaration(expressionPart);
}
private void addLogicalBindingForNullOperation(ExpressionPart expressionPart, String readExpression)
{
JType expressionType = expressionPart.getType();
if (expressionType.getQualifiedSourceName().equals(String.class.getCanonicalName()))
{
expression.append(StringUtils.class.getCanonicalName()+".isEmpty("+readExpression+")");
}
else
{
String emptyValueForType = JClassUtils.getEmptyValueForType(expressionType);
if(emptyValueForType == null)
{
throw new CruxGeneratorException("Invalid expression. property ["+widgetPropertyPath+"] "
+ "of widget ["+widgetType.getQualifiedSourceName()+"] is void.");
}
expression.append(readExpression+" == "+emptyValueForType);
}
appendConverterDeclaration(expressionPart);
}
private void addLogicalBindingForNumericOperations(ExpressionPart expressionPart, LogicalOperation logicalOperation, String readExpression)
{
expression.append(readExpression);
if (!JClassUtils.isNumeric(expressionPart.getType()))
{
throw new CruxGeneratorException("Invalid expression. property ["+widgetPropertyPath+"] "
+ "of widget ["+widgetType.getQualifiedSourceName()+"] is not a numeric type.");
}
if (logicalOperation == LogicalOperation.POSITIVE)
{
expression.append(" > 0");
}
else if (logicalOperation == LogicalOperation.NEGATIVE)
{
expression.append(" < 0");
}
else if (logicalOperation == LogicalOperation.ZERO)
{
expression.append(" == 0");
}
appendConverterDeclaration(expressionPart);
}
private void appendConverterDeclaration(ExpressionPart expressionPart)
{
String converterDeclaration = expressionPart.getConverterDeclaration();
if (converterDeclaration != null)
{
convertersDeclarations.add(converterDeclaration);
}
}
public static enum LogicalOperation{EMPTY, FILLED, IS, NEGATIVE, NULL, POSITIVE, ZERO}
}