/**
* Copyright (C) 2013 DaiKit.com - daikit4gxt module (admin@daikit.com)
*
* Project home : http://code.daikit.com/daikit4gxt
*
* 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 com.daikit.daikit4gxt.client.editor;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.daikit.commons.shared.bean.AbstractDkModifiableBeanWithId;
import com.daikit.commons.shared.utils.DkObjectUtils;
import com.google.gwt.editor.client.EditorContext;
import com.google.gwt.editor.client.EditorVisitor;
import com.google.gwt.editor.client.HasEditorDelegate;
import com.google.gwt.editor.client.LeafValueEditor;
import com.sencha.gxt.data.client.editor.ListStoreEditor;
import com.sencha.gxt.data.shared.ListStore;
/**
* {@link EditorVisitor} permitting to update {@link AbstractEditedBeanAwareEditor} dirty status and
* {@link AbstractDkModifiableBeanWithId} dirty status, dirty paths and deletedChildrenIds
*
* @author tcaselli
* @author rdupuis
* @version $Revision$ Last modifier: $Author$ Last commit: $Date$
*/
@SuppressWarnings("rawtypes")
public class DkPreFlushVisitor extends EditorVisitor
{
private boolean updateModel = false;
/**
* Constructor
*
* @param updateModelDirtyStatus
* false by default
*/
public DkPreFlushVisitor(final boolean updateModelDirtyStatus)
{
this.updateModel = updateModelDirtyStatus;
}
private final List<AbstractEditedBeanAwareEditor> stack = new ArrayList<AbstractEditedBeanAwareEditor>();
@Override
public <T extends Object> boolean visit(final com.google.gwt.editor.client.EditorContext<T> ctx)
{
if (ctx.getEditor() instanceof AbstractEditedBeanAwareEditor)
{
stack.add((AbstractEditedBeanAwareEditor<T>) ctx.getEditor());
}
return true;
};
@Override
public <T> void endVisit(final EditorContext<T> ctx)
{
if (customVisit(ctx))
{
visitLeaf(ctx);
visitListRemovableEditor(ctx);
visitListStoreEditor(ctx);
visitTreeStoreEditor(ctx);
}
if (ctx.getEditor() instanceof AbstractEditedBeanAwareEditor)
{
if (!stack.isEmpty())
{
stack.remove(stack.size() - 1);
}
}
}
/**
* To be overridden to provide custom visitor
*
* @param ctx
* the {@link EditorContext}
* @return whether to apply other visitors or not. (Return true by default, and should return false if your custom
* implementation was effectively taking into account the given context)
*/
protected <T> boolean customVisit(final EditorContext<T> ctx)
{
return true;
}
/**
* Compare model value to editor value. if they are not the same all ancestor editor will be flagged as dirty.
* Moreover the direct AbstractModifiableBeanWithId parent to the leaf editor will be updated. After being updated
* the parent will contain the leafEditor attribute name.
*
* @param ctx
*/
private <T> void visitLeaf(final EditorContext<T> ctx)
{
final LeafValueEditor<T> editor = ctx.asLeafValueEditor();
if (editor != null)
{
final T modelValue = ctx.getFromModel();
final T newValue = editor.getValue();
if (!DkObjectUtils.equalsWithNull(modelValue, newValue))
{
final AbstractEditedBeanAwareEditor<T> parent = getParent();
if (parent != null)
{
// iterate through parents starting from the oldest
updateIterativelyDirtyStatusAndPath(ctx.getEditorDelegate().getPath());
}
}
}
}
/**
* If a ListStoreRemovableEditor is visited this method will update ancestor editor with dirty flag. It will also
* populate the direct parent editor with the current editor attributeName and the list of removed items id.
*
* @param ctx
*/
@SuppressWarnings("unchecked")
private <T> void visitListRemovableEditor(final EditorContext<T> ctx)
{
final HasEditorDelegate<T> listEditor = ctx.asHasEditorDelegate();
if (listEditor instanceof ListStoreRemovableEditor)
{
final List<T> removedItems = ((ListStoreRemovableEditor<T>) listEditor).getRemovedItems();
if (removedItems != null && !removedItems.isEmpty())
{
// iterate through parent editors starting from the oldest
updateIterativelyDirtyStatusAndPath(ctx.getEditorDelegate().getPath());
// updated removed ids
final AbstractEditedBeanAwareEditor<T> parent = getParent();
if (parent != null && updateModel && parent.getEditedModel() instanceof AbstractDkModifiableBeanWithId)
{
final Set<String> removedIds = new HashSet<String>();
for (final T item : removedItems)
{
if (item instanceof AbstractDkModifiableBeanWithId)
{
removedIds.add(((AbstractDkModifiableBeanWithId) item).getId());
}
}
((AbstractDkModifiableBeanWithId) parent.getEditedModel()).getDeletedChildrenIds().put(
getLastAttributePath(ctx.getEditorDelegate().getPath()), removedIds);
}
}
}
}
/**
* Visit child {@link ListStoreEditor}
*
* @param ctx
*/
@SuppressWarnings("unchecked")
private <T> void visitListStoreEditor(final EditorContext<T> ctx)
{
final HasEditorDelegate<T> editor = ctx.asHasEditorDelegate();
if (editor instanceof ListStoreEditor || editor instanceof ListStoreRemovableEditor)
{
boolean isDirty = false;
if (editor instanceof ListStoreRemovableEditor)
{
final ListStoreRemovableEditor<T> removableEditor = (ListStoreRemovableEditor<T>) editor;
if (removableEditor.getOriginalModels().size() != removableEditor.getStore().getAll().size())
{
isDirty = true;
}
else
{
final List<T> storeList = removableEditor.getStore().getAll();
for (final T model : removableEditor.getOriginalModels())
{
if (!storeList.contains(model))
{
isDirty = true;
break;
}
}
}
}
if (!isDirty)
{
final ListStore<T> listStore = editor instanceof ListStoreEditor ? ((ListStoreEditor<T>) editor).getStore()
: ((ListStoreRemovableEditor<T>) editor).getStore();
for (final T listElement : listStore.getAll())
{
if (listElement instanceof AbstractDkModifiableBeanWithId
&& ((AbstractDkModifiableBeanWithId) listElement).isDirty())
{
isDirty = true;
break;
}
}
}
if (isDirty)
{
updateIterativelyDirtyStatusAndPath(ctx.getEditorDelegate().getPath());
}
}
}
/**
* Visit child {@link TreeStoreEditor}
*
* @param ctx
*/
private <T> void visitTreeStoreEditor(final EditorContext<T> ctx)
{
final HasEditorDelegate<T> editor = ctx.asHasEditorDelegate();
if (editor instanceof TreeStoreEditor)
{
final TreeStoreEditor treeStoreEditor = (TreeStoreEditor) editor;
boolean isDirty = false;
for (final Object listElement : treeStoreEditor.getStore().getAll())
{
if (listElement instanceof AbstractDkModifiableBeanWithId && ((AbstractDkModifiableBeanWithId) listElement).isDirty())
{
isDirty = true;
break;
}
}
if (isDirty)
{
updateIterativelyDirtyStatusAndPath(ctx.getEditorDelegate().getPath());
}
}
}
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
// UTILITY METHODS
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
/**
*
* @param contextDelegatePath
* the current context delegate path
*/
protected void updateIterativelyDirtyStatusAndPath(final String contextDelegatePath)
{
// iterate through parent editors starting from the oldest
for (int i = 0; i < stack.size(); i++)
{
final AbstractEditedBeanAwareEditor ancestor = stack.get(i);
ancestor.setDirty(true);
if (updateModel && ancestor.getEditedModel() instanceof AbstractDkModifiableBeanWithId)
{
final AbstractDkModifiableBeanWithId data = (AbstractDkModifiableBeanWithId) ancestor.getEditedModel();
data.setDirty(true);
if (i == stack.size() - 1)
{
// update status in bean
data.getDirtyPaths().add(getLastAttributePath(contextDelegatePath));
}
else
{
final AbstractEditedBeanAwareEditor childAncestor = stack.get(i + 1);
data.getDirtyPaths().add(getLastAttributePath(childAncestor.getDelegate().getPath()));
}
}
}
}
private String getLastAttributePath(final String absolutePath)
{
return absolutePath.substring(absolutePath.lastIndexOf(".") + 1);
}
@SuppressWarnings("unchecked")
private <T> AbstractEditedBeanAwareEditor<T> getParent()
{
return stack.isEmpty() ? null : stack.get(stack.size() - 1);
}
}