package gueei.binding.kernel;
import gueei.binding.ConstantObservable;
import gueei.binding.Converter;
import gueei.binding.DynamicObject;
import gueei.binding.IObservable;
import gueei.binding.IPropertyContainer;
import gueei.binding.IReferenceObservableProvider;
import gueei.binding.ISyntaxResolver;
import gueei.binding.InnerFieldObservable;
import gueei.binding.Observable;
import gueei.binding.Utility;
import gueei.binding.observables.FloatObservable;
import gueei.binding.observables.IntegerObservable;
import gueei.binding.viewAttributes.templates.Layout;
import gueei.binding.viewAttributes.templates.SingleTemplateLayout;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.Context;
import android.util.TypedValue;
/**
* Updates: 8/5/2012 - Reworked exception handling
* @author andy
*
*/
public class DefaultSyntaxResolver implements ISyntaxResolver {
private static final String DEFAULT_CONVERTER_PACKAGE = "gueei.binding.converters.";
private static final Pattern converterPattern =
Pattern.compile("^([$a-zA-Z0-9._]+)\\((.+(\\s*?,\\s*.+)*)?\\)", Pattern.DOTALL);
private static final Pattern dynamicObjectPattern = Pattern.compile("^\\{(.+)\\}$");
private static final Pattern numberPattern = Pattern.compile("^(\\+|\\-)?[0-9]*(\\.[0-9]+)?$");
private static final Pattern resourcePattern = Pattern.compile("^@(([\\w\\.]+:)?(\\w+)/\\w+)$");
private static final Pattern referencePattern = Pattern.compile("^=@?((\\w+:)?(\\w+)/\\w+).(\\w+)$");
/* (non-Javadoc)
* @see gueei.binding.ISyntaxResolver#constructObservableFromStatement(android.content.Context, java.lang.String, java.lang.Object, gueei.binding.IReferenceObservableProvider)
*/
public IObservable<?> constructObservableFromStatement(
final Context context,
final String bindingStatement,
final Object model,
final IReferenceObservableProvider refProvider) throws SyntaxResolveException{
if(bindingStatement == null)return null;
if (model==null) return null;
IObservable<?> result;
String statement = bindingStatement.trim();
result = getReferenceObservable(context, statement, refProvider);
if (result!=null) return result;
result = getConverterFromStatement(context, statement, model, refProvider);
if (result!=null) return result;
result = getDynamicObjectFromStatement(context, statement, model, refProvider);
if (result!=null) return result;
result = getObservableForModel(context, statement, model);
if (result==null){
throw new SyntaxResolveException
(String.format("Error when resolving statement '%s'", statement));
}
return result;
}
/* (non-Javadoc)
* @see gueei.binding.ISyntaxResolver#constructObservableFromStatement(android.content.Context, java.lang.String, java.lang.Object)
*/
public IObservable<?> constructObservableFromStatement(
final Context context,
final String bindingStatement,
final Object model) throws SyntaxResolveException{
return constructObservableFromStatement(context, bindingStatement, model, null);
}
private IObservable<?> getReferenceObservable(
final Context context, String statement,
IReferenceObservableProvider refProvider){
if (!statement.startsWith("=")) return null;
if (refProvider==null) return null;
Matcher m = referencePattern.matcher(statement);
if ((!m.matches()) || (m.groupCount()<4))
return null;
int id = Utility.resolveResourceId(m.group(1), context, m.group(3));
if (id<=0) return null;
return refProvider.getReferenceObservable(id, m.group(4));
}
private Converter<?> getConverterFromStatement(
final Context context, String statement, Object model,
final IReferenceObservableProvider refProvider) throws SyntaxResolveException{
Matcher m = converterPattern.matcher(statement);
if (!m.matches())
return null;
String converterName = m.group(1);
if (!converterName.contains(".")){
converterName = DEFAULT_CONVERTER_PACKAGE + converterName;
}
try {
String[] arguments = splitArguments(m.group(2));
int argumentCount = arguments.length;
IObservable<?>[] obs = new IObservable[argumentCount];
for (int i=0; i<argumentCount; i++){
obs[i] = constructObservableFromStatement(context, arguments[i], model, refProvider);
if (obs[i] == null){
return null;
}
}
Constructor<?> constructor = Class.forName(converterName).getConstructor(IObservable[].class);
Converter<?> converter = (Converter<?>)constructor.newInstance((Object)(obs));
converter.setContext(context);
return converter;
} catch (SyntaxResolveException e){
throw new SyntaxResolveException
(String.format("Some argument(s) in the converter statement ('%s') has problem", statement), e);
} catch (NoSuchMethodException e) {
throw new SyntaxResolveException(
String.format(
"Converter '%s' must define the constructor Converter(IObservable<?>...)", converterName),
e);
} catch (ClassNotFoundException e) {
throw new SyntaxResolveException
(String.format("Converter '%s' not found", converterName), e);
} catch (Exception e) {
throw new SyntaxResolveException
(String.format("Error when resolving statement '%s'", statement), e);
}
}
private IObservable<?> getDynamicObjectFromStatement
(final Context context, final String statement, final Object model,
final IReferenceObservableProvider refProvider) throws SyntaxResolveException {
Matcher m = dynamicObjectPattern.matcher(statement);
if (!m.matches()) return null;
DynamicObject dynamic = new DynamicObject();
String[] arguments = splitArguments(m.group(1));
int argumentCount = arguments.length;
for (int i=0; i<argumentCount; i++){
int indexOfEqual = arguments[i].indexOf('=');
if (indexOfEqual <=0 ) return null;
String name = arguments[i].substring(0, indexOfEqual).trim();
String obsStatement = arguments[i].substring(indexOfEqual+1).trim();
IObservable<?> obs = constructObservableFromStatement(context, obsStatement, model, refProvider);
if (obs == null){
return null;
}
dynamic.putObservable(name, obs);
}
return dynamic;
}
private String[] splitArguments(String group){
if (group==null) return new String[0];
ArrayList<String> arguments = new ArrayList<String>();
int bracketCount = 0;
int curlyBraceCount = 0;
String progress = "";
int count = group.length();
boolean singleQuoteMode = false;
boolean doubleQuoteMode = false;
char[] chars = group.toCharArray();
for(int i=0; i<count; i++){
if (chars[i]=='\''){
if (singleQuoteMode) singleQuoteMode=false;
else if (!doubleQuoteMode) singleQuoteMode = true;
}
if (chars[i]=='"'){
if (doubleQuoteMode) doubleQuoteMode=false;
else if (!singleQuoteMode) doubleQuoteMode = true;
}
if (chars[i]=='(') bracketCount++;
if (chars[i]==')') bracketCount--;
if (chars[i]=='{') curlyBraceCount++;
if (chars[i]=='}') curlyBraceCount--;
if ( (chars[i] ==',') &&
(bracketCount<=0) &&
(curlyBraceCount<=0) &&
(progress.length()!=0) &&
!singleQuoteMode &&
!doubleQuoteMode
){
arguments.add(progress.trim());
progress = "";
bracketCount=0;
curlyBraceCount=0;
}else{
progress += chars[i];
}
}
if ((bracketCount<=0) && (progress.length()!=0)){
arguments.add(progress.trim());
}
return arguments.toArray(new String[0]);
}
/**
* get the Observable (either defined in model, or constants) from model
* @param fieldName
* @param model
* @return IObservable
*
* The resolving in done in following order:
* 1. String (defined in '')
* 2. Integer
* 3. Resource
* 4. InnerField if containing dot (new)
* 4. Observable
* 6. Constant from field
* 7. No more fall back
* @throws SyntaxResolveException
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private IObservable<?> getObservableForModel(
Context context,
String fieldName, Object model) throws SyntaxResolveException{
IObservable<?> result = matchString(fieldName);
if (result!=null) return result;
result = matchNumber(fieldName);
if (result!=null) return result;
result = matchResource(context, fieldName);
if (result!=null) return result;
if (model instanceof IPropertyContainer){
try{
return ((IPropertyContainer)model).getObservableByName(fieldName);
}catch(Exception e){
return null;
}
}
if (fieldName.equals(".")){
return new Observable(model.getClass(), model);
}
if (fieldName.contains(".")){
InnerFieldObservable ifo = new InnerFieldObservable(fieldName);
if (ifo.createNodes(model))
return ifo;
return null;
}
Object rawField = getFieldForModel(fieldName, model);
if (rawField instanceof IObservable<?>)
return (IObservable<?>)rawField;
if (rawField!=null){
return new ConstantObservable(rawField.getClass(), rawField);
}
// No more fall back
return null; // new ConstantObservable<String>(String.class, fieldName);
}
private IObservable<?> matchString(String fieldName){
if ((fieldName.startsWith("'") && fieldName.endsWith("'")) ||
(fieldName.startsWith("\"") && fieldName.endsWith("\"")))
return new ConstantObservable<String>(String.class,
fieldName.substring(1, fieldName.length()-1)
.replace("\'", "'")
.replace("\\\"", "\""));
return null;
}
private IObservable<?> matchNumber(String fieldName){
Matcher m = numberPattern.matcher(fieldName);
if (!m.matches()) return null;
try{
if (fieldName.contains(".")){
return new FloatObservable(Float.parseFloat(fieldName));
}else{
return new IntegerObservable(Integer.parseInt(fieldName));
}
}catch(Exception e){
return null;
}
}
private IObservable<?> matchResource(Context context, String fieldName)
throws SyntaxResolveException{
Matcher m = resourcePattern.matcher(fieldName);
if ((!m.matches())||(m.groupCount()<2)) return null;
String typeName = m.group(3);
int id = Utility.resolveResourceId(fieldName, context, typeName);
if (id<=0){
throw new SyntaxResolveException
(String.format("Resource '%s' not found. Maybe typo? ", fieldName));
}
if ("layout".equals(typeName))
return new ConstantObservable<Layout>(Layout.class, new SingleTemplateLayout(id));
if("id".equals(typeName))
return new ConstantObservable<Integer>(Integer.class, id);
TypedValue outValue = new TypedValue();
context.getResources().getValue(id, outValue, true);
if (typeName.startsWith("drawable")||typeName.startsWith("anim")||typeName.startsWith("menu")||typeName.startsWith("raw"))
return new ConstantObservable<Integer>(Integer.class, id);
switch(outValue.type){
case TypedValue.TYPE_STRING:
return new ConstantObservable<String>(String.class, outValue.string.toString());
case TypedValue.TYPE_DIMENSION:
return new ConstantObservable<TypedValue>(TypedValue.class, outValue);
case TypedValue.TYPE_FRACTION:
case TypedValue.TYPE_FLOAT:
return new ConstantObservable<Float>(Float.class, outValue.getFloat());
case TypedValue.TYPE_INT_BOOLEAN:
return new ConstantObservable<Boolean>(Boolean.class, outValue.data != 0);
default:
return new ConstantObservable<Integer>(Integer.class, outValue.data);
}
}
/**
* Utility method to get the field for model,
* it also accepts IPropertyContainer as model so the field is
* returned by it rather than reflection
* @param fieldName
* @param model
* @return field object
* @throws SyntaxResolveException
*/
@Override
public Object getFieldForModel(String fieldName, Object model)
throws SyntaxResolveException{
try{
if (model instanceof IPropertyContainer){
return ((IPropertyContainer)model).getValueByName(fieldName);
}
Field field = model.getClass().getField(fieldName);
return field.get(model);
}catch(SyntaxResolveException e){
return null;
} catch (IllegalAccessException e) {
throw new SyntaxResolveException
(String.format("Error with accessing the field '%s' in the Object '%s'. " +
"Maybe it is not public accessible. ",
fieldName, model.toString()), e);
} catch (NoSuchFieldException e) {
return null;
} catch (Exception e) {
return null;
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T tryEvaluateValue(Context context, String statement, Object model, T defaultValue) {
try{
IObservable<?> obs = this.constructObservableFromStatement(context, statement, model);
return (T)obs.get();
}catch(Exception e){
return defaultValue;
}
}
}