/*
* 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.client.screen.binding;
import java.util.Date;
import org.cruxframework.crux.core.client.collection.Array;
import org.cruxframework.crux.core.client.collection.CollectionFactory;
import org.cruxframework.crux.core.client.collection.Map;
import com.google.gwt.user.client.ui.Widget;
/**
* @author Thiago da Rosa de Bustamante
*
*/
public abstract class DataObjectBinder<T>
{
private Map<Array<PropertyBinder<T, ?>>> binders = CollectionFactory.createMap();
private Map<Array<ExpressionBinder<?>>> expressionBinders = CollectionFactory.createMap();
private T dataObject;
private BindableContainer bindableContainer;
public DataObjectBinder(BindableContainer bindableContainer)
{
this.bindableContainer = bindableContainer;
}
/**
* Add a new binding between a property of a DataObject and a widget property.
*
* @param widgetId the bound widget identifier.
* @param propertyBinder the {@link PropertyBinder} that maps how this property is bound to the DataObject
* @param boundToAttribute Inform if this property binding references an HTML attribute or property.
* It is important for considerable performance improvements, as it allow us to avoid
* dirty checking to implement data binding.
*/
public void addPropertyBinder(String widgetId, PropertyBinder<T, ?> propertyBinder, boolean boundToAttribute)
{
initialize();
Array<PropertyBinder<T, ?>> properties = binders.get(widgetId);
if (properties == null)
{
properties = CollectionFactory.createArray();
binders.put(widgetId, properties);
}
propertyBinder.setDataObjectBinder(this);
propertyBinder.setBoundToAttribute(boundToAttribute);
properties.add(propertyBinder);
tryToBindWidget(widgetId, propertyBinder);
propertyBinder.copyTo(dataObject);
}
public void addExpressionBinder(String widgetId, ExpressionBinder<?> expressionBinder)
{
initialize();
Array<ExpressionBinder<?>> properties = expressionBinders.get(widgetId);
if (properties == null)
{
properties = CollectionFactory.createArray();
expressionBinders.put(widgetId, properties);
}
properties.add(expressionBinder);
tryToBindWidget(widgetId, expressionBinder);
expressionBinder.execute(new UpdatedStateBindingContext(bindableContainer, new Date().getTime()));
}
protected abstract T createDataObject();
void removeBinders(String widgetId)
{
binders.remove(widgetId);
expressionBinders.remove(widgetId);
}
@SuppressWarnings("unchecked")
void copyTo(Object object)
{
T dataObject = (T) object;
readGeneric(dataObject);
}
T read()
{
readGeneric(dataObject);
return dataObject;
}
void initialize()
{
if (dataObject == null)
{
dataObject = createDataObject();
}
}
void write(Object object)
{
write(object, true);
}
@SuppressWarnings("unchecked")
void write(Object object, boolean updateExpressions)
{
T dataObject = (T) object;
writeGeneric(dataObject);
this.dataObject = dataObject;
if (updateExpressions)
{
updateExpressions();
}
}
void updateObjectAndNotifyChanges(PropertyBinder<T, ?> propertyBinder)
{
propertyBinder.copyFrom(dataObject);
updateExpressions();
}
void updateExpressions()
{
updateExpressions(new UpdatedStateBindingContext(bindableContainer, new Date().getTime()));
}
void updateExpressions(ExpressionBinder.BindingContext context)
{
Array<String> keys = expressionBinders.keys();
int size = keys.size();
for (int i = 0; i < size; i++)
{
String id = keys.get(i);
Array<ExpressionBinder<?>> binders = expressionBinders.get(id);
int expressionsSize = binders.size();
for (int j = 0; j < expressionsSize; j++)
{
ExpressionBinder<?> expressionBinder = binders.get(j);
if (!expressionBinder.isBound())
{
tryToBindWidget(id, expressionBinder);
}
expressionBinder.execute(context);
}
}
}
private void readGeneric(T dataObject)
{
Array<String> keys = binders.keys();
int size = keys.size();
for (int i=0; i< size; i++)
{
String id = keys.get(i);
Array<PropertyBinder<T, ?>> propertyBinders = binders.get(id);
int propertiesSize = propertyBinders.size();
for (int j = 0; j < propertiesSize; j++)
{
PropertyBinder<T, ?> propertyBinder = propertyBinders.get(j);
if (!propertyBinder.isBound())
{
tryToBindWidget(id, propertyBinder);
}
if (propertyBinder.isBound())
{
propertyBinder.copyFrom(dataObject);
}
}
}
}
private void writeGeneric(T dataObject)
{
Array<String> keys = binders.keys();
int size = keys.size();
for (int i = 0; i < size; i++)
{
String id = keys.get(i);
Array<PropertyBinder<T, ?>> propertyBinders = binders.get(id);
int propertiesSize = propertyBinders.size();
for (int j = 0; j < propertiesSize; j++)
{
PropertyBinder<T, ?> propertyBinder = propertyBinders.get(j);
if (!propertyBinder.isBound())
{
tryToBindWidget(id, propertyBinder);
}
if (propertyBinder.isBound())
{
propertyBinder.copyTo(dataObject);
}
}
}
}
private void tryToBindWidget(String id, PropertyBinder<T, ?> propertyBinder)
{
Widget widget = bindableContainer.getLoadedWidget(id);
if (widget != null)
{
propertyBinder.bind(widget);
}
}
private void tryToBindWidget(String id, ExpressionBinder<?> expressionBinder)
{
Widget widget = bindableContainer.getLoadedWidget(id);
if (widget != null)
{
expressionBinder.bind(widget);
}
}
/**
* This binding context can be used only when we know that there is no write operation waiting to be
* performed on the current bindable container. It assume that the dataObjects are updated.
*
* @author Thiago da Rosa de Bustamante
*/
public static class UpdatedStateBindingContext implements ExpressionBinder.BindingContext
{
private BindableContainer bindableContainer;
private long executionTimestamp;
public UpdatedStateBindingContext(BindableContainer bindableContainer, long executionTimestamp)
{
this.bindableContainer = bindableContainer;
this.executionTimestamp = executionTimestamp;
}
@Override
public <T> T getDataObject(String dataObject)
{
DataObjectBinder<T> dataObjectBinder = bindableContainer.getDataObjectBinder(dataObject);
if (dataObjectBinder.dataObject != null)
{
return dataObjectBinder.dataObject;
}
return dataObjectBinder.read();
}
@Override
public long getExecutionTimestamp()
{
return executionTimestamp;
}
}
}