gt: 		if (item != null) {
context: /*
   * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
   *
   * Copyright 2008-2015 Geosparc nv, http://www.geosparc.com/, Belgium.
   *
   * The program is available in open source according to the GNU Affero
   * General Public License. All contributions in this program are covered
   * by the Geomajas Contributors License Agreement. For full licensing
   * details, see LICENSE.txt in the project root.
   */
  package org.geomajas.gwt.client.widget.attribute;
  
  import com.google.gwt.event.shared.GwtEvent;
  import com.google.gwt.event.shared.HandlerManager;
  import com.google.gwt.event.shared.HandlerRegistration;
  import com.smartgwt.client.data.DataSource;
  import com.smartgwt.client.data.DataSourceField;
  import com.smartgwt.client.types.DSDataFormat;
  import com.smartgwt.client.types.DSProtocol;
  import com.smartgwt.client.widgets.form.DynamicForm;
  import com.smartgwt.client.widgets.form.events.ItemChangedEvent;
  import com.smartgwt.client.widgets.form.events.ItemChangedHandler;
  import com.smartgwt.client.widgets.form.fields.FormItem;
  import com.smartgwt.client.widgets.form.fields.SelectItem;
  import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
  import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
  import org.geomajas.annotation.Api;
  import org.geomajas.configuration.AbstractAttributeInfo;
  import org.geomajas.configuration.AbstractReadOnlyAttributeInfo;
  import org.geomajas.configuration.AssociationAttributeInfo;
  import org.geomajas.configuration.EditableAttributeInfo;
  import org.geomajas.configuration.FeatureInfo;
  import org.geomajas.configuration.PrimitiveAttributeInfo;
  import org.geomajas.gwt.client.map.feature.Feature;
  import org.geomajas.gwt.client.map.layer.VectorLayer;
  import org.geomajas.layer.feature.Attribute;
  import org.geomajas.layer.feature.attribute.AssociationAttribute;
  import org.geomajas.layer.feature.attribute.AssociationValue;
  import org.geomajas.layer.feature.attribute.BooleanAttribute;
  import org.geomajas.layer.feature.attribute.CurrencyAttribute;
  import org.geomajas.layer.feature.attribute.DateAttribute;
  import org.geomajas.layer.feature.attribute.DoubleAttribute;
  import org.geomajas.layer.feature.attribute.FloatAttribute;
  import org.geomajas.layer.feature.attribute.ImageUrlAttribute;
  import org.geomajas.layer.feature.attribute.IntegerAttribute;
  import org.geomajas.layer.feature.attribute.LongAttribute;
  import org.geomajas.layer.feature.attribute.ManyToOneAttribute;
  import org.geomajas.layer.feature.attribute.OneToManyAttribute;
  import org.geomajas.layer.feature.attribute.PrimitiveAttribute;
  import org.geomajas.layer.feature.attribute.ShortAttribute;
  import org.geomajas.layer.feature.attribute.StringAttribute;
  import org.geomajas.layer.feature.attribute.UrlAttribute;
  
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * <p>
   * Default implementation of a {@link FeatureForm} based on a {@link DynamicForm}. The form is initialized by providing
   * a vector layer or the feature information of an association attribute. The implementation uses the
   * {@link AttributeFormFieldRegistry} to create the individual form items and fields. The
   * {@link #createField(AbstractReadOnlyAttributeInfo)} and {@link #createItem(AbstractReadOnlyAttributeInfo)} methods
   * can be overridden to create custom item and field implementations if necessary. The {@link #prepareForm(FormItemList,
   * DataSource)} method can be overridden to perform any additional actions on the form or form item list before the 
   * form is created. Attributes can be excluded from the form by overriding the {@link #isIncluded
   * (AbstractReadOnlyAttributeInfo)} method.
   * </p>
   * <p>
   * This attribute form definition is used internally in the
   * {@link org.geomajas.gwt.client.widget.FeatureAttributeEditor} widget. A code example on
   * how to override the {@link #prepareForm(FormItemList, DataSource)} method is shown below:
   * </p>
   * <code>
   * <pre>
   * 	protected void prepareForm(List<FormItem> formItems, DataSource source) {
   * 
   * 			getWidget().setGroupTitle("My Custom Attribute Form");
   * 			getWidget().setIsGroup(true);
   * 			getWidget().setNumCols(4);
   * 			getWidget().setWidth(450);
   * 			getWidget().setColWidths(100, 180, 20, 150);
   * 
   * 	}
   * 	</pre>
   * </code>
   * 
   * @author Pieter De Graef
   * @author Jan De Moerloose
   * @since 1.11.1
   */
  @Api
  public class DefaultFeatureForm implements FeatureForm<DynamicForm> {
  
  	public static final String STYLE_FEATURE_FORM = "featureForm";
  
  	private Map<String, AbstractReadOnlyAttributeInfo> attributeInfoMap =
  			new HashMap<String, AbstractReadOnlyAttributeInfo>();
  
  	private DynamicForm formWidget;
  
  	private boolean disabled;
  
  	private FeatureInfo featureInfo;
  
  	private AttributeProvider attributeProvider;
  
  	private HandlerManager manager = new HandlerManager(this);
  	
  	private Set<String> editableItemNames = new HashSet<String>(); 
  	
  	// -------------------------------------------------------------------------
  	// Constructors:
  	// -------------------------------------------------------------------------
  
  	/**
  	 * Initialize the attribute form with the given layer. Note that this constructor will NOT define all the form
  	 * items. This should still be done by some {@link FeatureFormFactory}. The reason for this is that different
  	 * implementations of these factories may want different orders or layouts. They may even want to introduce extra
  	 * form items.. who knows.
  	 * 
  	 * @param vectorLayer The vector layer that should be presented in this form.
  	 * @since 1.11.0
  	 */
  	@Api
  	public DefaultFeatureForm(VectorLayer vectorLayer) {
  		this(vectorLayer.getLayerInfo().getFeatureInfo(), new DefaultAttributeProvider(vectorLayer.getLayerInfo()
  				.getServerLayerId()));
  	}
  
  	/**
  	 * Initialize the attribute form with the given feature info. Note that this constructor will NOT define all the
  	 * form items. This should still be done by some {@link FeatureFormFactory}. The reason for this is that different
  	 * implementations of these factories may want different orders or layouts. They may even want to introduce extra
  	 * form items.. who knows.
  	 * 
  	 * @param featureInfo The feature information that should be presented in this form.
  	 * @param attributeProvider attribute provider
  	 * @since 1.11.1
  	 */
  	@Api
  	public DefaultFeatureForm(FeatureInfo featureInfo, AttributeProvider attributeProvider) {
  		this.featureInfo = featureInfo;
  		this.attributeProvider = attributeProvider;
  		for (AbstractAttributeInfo info : featureInfo.getAttributes()) {
  			if (info instanceof AbstractReadOnlyAttributeInfo) {
  				AbstractReadOnlyAttributeInfo roInfo = (AbstractReadOnlyAttributeInfo) info;
  				attributeInfoMap.put(roInfo.getName(), roInfo);
  			}
  		}
  		formWidget = new DynamicForm() {
  
  			public void setDataSource(com.smartgwt.client.data.DataSource dataSource) {
  				dataSource.setDataFormat(DSDataFormat.CUSTOM);
  				dataSource.setDataProtocol(DSProtocol.CLIENTCUSTOM);
  				dataSource.setClientOnly(false);
  				super.setDataSource(dataSource);
  			}
  		};
  		formWidget.setStyleName(STYLE_FEATURE_FORM);
  		DataSource source = new DataSource();
  		FormItemList formItems = new FormItemList();
  		for (AbstractAttributeInfo info : featureInfo.getAttributes()) {
  			if (info instanceof AbstractReadOnlyAttributeInfo) {
  				AbstractReadOnlyAttributeInfo roInfo = (AbstractReadOnlyAttributeInfo) info;
  				if (isIncluded(roInfo)) {
  					formItems.add(createItem(roInfo));
  					source.addField(createField(roInfo));
  				}
  			}
  		}
  		prepareForm(formItems, source);
  		getWidget().setDataSource(source);
  		getWidget().setFields(formItems.toArray());
  	}
  
  	/**
  	 * Creates a form item for a specific attribute.
  	 * 
  	 * @param info the attribute information.
  	 * @return the form item
  	 * @since 1.11.1
  	 */
  	@Api
  	public FormItem createItem(AbstractReadOnlyAttributeInfo info) {
  		return AttributeFormFieldRegistry.createFormItem(info, attributeProvider.createProvider(info.getName()));
  	}
  
  	/**
  	 * Create a data source field for a specific attribute.
  	 * 
  	 * @param info the attribute information.
  	 * @return the data source field
  	 * @since 1.11.1
  	 */
  	@Api
  	public DataSourceField createField(AbstractReadOnlyAttributeInfo info) {
  		return AttributeFormFieldRegistry.createDataSourceField(info);
  	}
  
  	/**
  	 * Returns whether an attribute should be included in the form.
  	 * 
  	 * @param info the attribute information
  	 * @return true if included, false otherwise
  	 * @since 1.11.1
  	 */
  	@Api
  	public boolean isIncluded(AbstractReadOnlyAttributeInfo info) {
  		return !info.isHidden();
  	}
  
  	/**
  	 * Override this method to make some additional modifications to the list of items and data source before the form
  	 * is created. The default implementation does nothing.
  	 * 
  	 * @param formItems list of items, with special insert operations
  	 * @param source data source
  	 * @since 1.11.1
  	 */
  	@Api
  	public void prepareForm(FormItemList formItems, DataSource source) { // NOSONAR empty for extension
  	}
  
  	// -------------------------------------------------------------------------
  	// Public methods:
  	// -------------------------------------------------------------------------
  
  	@Override
  	public DynamicForm getWidget() {
  		return formWidget;
  	}
  
  	@Override
  	public void setDisabled(boolean disabled) {
  		this.disabled = disabled;
  
  		// Don't set disabled on the form, but on the individual items. This way it's easier to overwrite when creating
  		// custom form items.
  		for (AbstractAttributeInfo info : featureInfo.getAttributes()) {
  			FormItem formItem = formWidget.getItem(info.getName());
  			if (formItem != null) {
  				if (editableItemNames.contains(info.getName())) {
  					formItem.setDisabled(disabled);
  				} else {
  					formItem.setDisabled(true);
  				}
  			}
  		}
  	}
  
  	@Override
  	public boolean isDisabled() {
  		return disabled;
  	}
  
  	@Override
  	public boolean validate() {
  		boolean validate = true;
  		for (FormItem item : formWidget.getFields()) {
  			// workaround for SmartGWT bug, see http://jira.geomajas.org/browse/GWT-226
  			if (!(item instanceof SelectItem)) {
  				validate = validate & item.validate();
  			}
  		}
  		return validate;
  	}
  
  	@Override
  	public boolean silentValidate() {
  		return formWidget.valuesAreValid(false);
  	}
  
  	public void fireEvent(GwtEvent<?> event) {
  		manager.fireEvent(event);
  	}
  
  	@Override
  	public HandlerRegistration addItemChangedHandler(ItemChangedHandler handler) {
  		MultiHandlerRegistration registration = new MultiHandlerRegistration();
  		// Due to custom made FormItems, we can't set the handler on the form anymore...
  		final ItemChangedHandler itemChangedHandler = handler;
  		registration.addRegistration(manager.addHandler(ItemChangedEvent.getType(), handler));
  		for (final FormItem formItem : formWidget.getFields()) {
  			ChangedHandler h = new ChangedHandler() {
  
  				public void onChanged(ChangedEvent event) {
  					itemChangedHandler.onItemChanged(new ItemChangedEvent(formItem.getJsObj()));
  				}
  			};
  			registration.addRegistration(formItem.addChangedHandler(h));
  		}
  		return registration;
  	}
  
  	@Override
  	public void toForm(Feature feature) {
  		for (AbstractAttributeInfo info : featureInfo.getAttributes()) {
  			toForm(info.getName(), feature.getAttributes().get(info.getName()));
  		}
  		setDisabled(disabled); // refresh enabled/disabled state
  	}
  
  	@Override
  	public void toForm(AssociationValue value) {
  		for (Map.Entry<String, Attribute<?>> entry : value.getAllAttributes().entrySet()) {
  			toForm(entry.getKey(), entry.getValue());
  		}
  		setDisabled(disabled); // refresh enabled/disabled state
  	}
  
  	@Override
  	public void fromForm(AssociationValue value) {
  		for (Map.Entry<String, Attribute<?>> entry : value.getAllAttributes().entrySet()) {
  			fromForm(entry.getKey(), entry.getValue());
  		}
  	}
  
  	@Override
  	public void toForm(String name, Attribute<?> attribute) {
  		AbstractReadOnlyAttributeInfo info = attributeInfoMap.get(name);
  		if (info == null || !isIncluded(info)) {
  			return;
  		}
  		FormItem item = formWidget.getField(info.getName());
  		boolean editable = attribute != null && attribute.isEditable() && info.isEditable();
  		if (editable) {
  			editableItemNames.add(info.getName());
  		} else {
  			editableItemNames.remove(info.getName());
  		}
  		item.setDisabled(isDisabled() || !editable);
  		if (attribute != null) {
  			if (attribute instanceof StringAttribute) {
  				setValue(info.getName(), (StringAttribute) attribute);
  			} else if (attribute instanceof ShortAttribute) {
  				setValue(info.getName(), (ShortAttribute) attribute);
  			} else if (attribute instanceof IntegerAttribute) {
  				setValue(info.getName(), (IntegerAttribute) attribute);
  			} else if (attribute instanceof LongAttribute) {
  				setValue(info.getName(), (LongAttribute) attribute);
  			} else if (attribute instanceof FloatAttribute) {
  				setValue(info.getName(), (FloatAttribute) attribute);
  			} else if (attribute instanceof DoubleAttribute) {
  				setValue(info.getName(), (DoubleAttribute) attribute);
  			} else if (attribute instanceof CurrencyAttribute) {
  				setValue(info.getName(), (CurrencyAttribute) attribute);
  			} else if (attribute instanceof BooleanAttribute) {
  				setValue(info.getName(), (BooleanAttribute) attribute);
  			} else if (attribute instanceof UrlAttribute) {
  				setValue(info.getName(), (UrlAttribute) attribute);
  			} else if (attribute instanceof ImageUrlAttribute) {
  				setValue(info.getName(), (ImageUrlAttribute) attribute);
  			} else if (attribute instanceof DateAttribute) {
  				setValue(info.getName(), (DateAttribute) attribute);
  
  			} else if (attribute instanceof ManyToOneAttribute) {
  				Object associationItem = item.getAttributeAsObject(AssociationItem.ASSOCIATION_ITEM_ATTRIBUTE_KEY);
  				((ManyToOneItem<?>) associationItem).toItem((ManyToOneAttribute) attribute);
  			} else if (attribute instanceof OneToManyAttribute) {
  				Object associationItem = item.getAttributeAsObject(AssociationItem.ASSOCIATION_ITEM_ATTRIBUTE_KEY);
  				((OneToManyItem<?>) associationItem).toItem((OneToManyAttribute) attribute);
  
  			} else {
  				throw new IllegalStateException("Unhandled attribute for " + name +
  						" with value " + attribute);
  			}
  			item.fireEvent(new ChangedEvent(item.getJsObj()));
  		}
  	}
  
  	@Override
  	public void fromForm(String name, Attribute<?> attribute) {
  		AbstractReadOnlyAttributeInfo info = attributeInfoMap.get(name);
  		if (null == attribute || null == info || !isIncluded(info) || !(info instanceof EditableAttributeInfo)) {
  			return;
  		}
  		if (info instanceof PrimitiveAttributeInfo) {
  			PrimitiveAttribute<?> primitive = (PrimitiveAttribute<?>) attribute;
  			switch (primitive.getType()) {
  				case BOOLEAN:
  					getValue(name, (BooleanAttribute) primitive); // NOSONAR valid cast
  					break;
  				case SHORT:
  					getValue(name, (ShortAttribute) primitive); // NOSONAR valid cast
  					break;
  				case INTEGER:
  					getValue(name, (IntegerAttribute) primitive); // NOSONAR valid cast
  					break;
  				case LONG:
  					getValue(name, (LongAttribute) primitive); // NOSONAR valid cast
  					break;
  				case FLOAT:
  					getValue(name, (FloatAttribute) primitive); // NOSONAR valid cast
  					break;
  				case DOUBLE:
  					getValue(name, (DoubleAttribute) primitive); // NOSONAR valid cast
  					break;
  				case CURRENCY:
  					getValue(name, (CurrencyAttribute) primitive); // NOSONAR valid cast
  					break;
  				case STRING:
  					getValue(name, (StringAttribute) primitive); // NOSONAR valid cast
  					break;
  				case URL:
  					getValue(name, (UrlAttribute) primitive); // NOSONAR valid cast
  					break;
  				case IMGURL:
  					getValue(name, (ImageUrlAttribute) primitive); // NOSONAR valid cast
  					break;
  				case DATE:
  					getValue(name, (DateAttribute) primitive); // NOSONAR valid cast
  					break;
  				default:
  					throw new IllegalStateException("Unhandled primitive attribute type " +
  							primitive.getType());
  			}
  		} else {
  			AssociationAttribute<?> association = (AssociationAttribute<?>) attribute;
  			FormItem item = formWidget.getItem(name);
  			Object associationItem = item.getAttributeAsObject(AssociationItem.ASSOCIATION_ITEM_ATTRIBUTE_KEY);
  			switch (association.getType()) {
  				case MANY_TO_ONE:
  					((ManyToOneItem<?>) associationItem).fromItem((ManyToOneAttribute) attribute); // NOSONAR valid cast
  					break;
  				case ONE_TO_MANY:
  					((OneToManyItem<?>) associationItem).fromItem((OneToManyAttribute) attribute); // NOSONAR valid cast
  					break;
  				default:
  					throw new IllegalStateException("Unhandled association attribute type " +
  							association.getType());
  			}
  		}
  	}
  
  	@Override
  	public void clear() {
  		formWidget.clearValues();
  		// the above does not call clearValue() on every item ?!! so do it explicitly
  		for (AbstractAttributeInfo info : featureInfo.getAttributes()) {
  			FormItem formItem = formWidget.getItem(info.getName());
  			if (formItem != null) {
  				if (info instanceof AssociationAttributeInfo) {
  					Object associationItem = formItem
  							.getAttributeAsObject(AssociationItem.ASSOCIATION_ITEM_ATTRIBUTE_KEY);
  					AssociationAttributeInfo associationInfo = (AssociationAttributeInfo) info;
  					if (associationItem != null) {
  						switch (associationInfo.getType()) {
  							case MANY_TO_ONE:
  								((ManyToOneItem<?>) associationItem).clearValue(); // NOSONAR valid cast
  								break;
  							case ONE_TO_MANY:
  								((OneToManyItem<?>) associationItem).clearValue(); // NOSONAR valid cast
  								break;
  							default:
  								throw new IllegalStateException("Unhandled association attribute type " +
  										associationInfo.getType());
  						}
  					}
  				} else {
  					formItem.clearValue();
  
  				}
  			}
  		}
  	}
  
  	// -------------------------------------------------------------------------
  	// Methods to access values on the form:
  	// -------------------------------------------------------------------------
  
  	/**
  	 * Apply a boolean attribute value on the form, with the given name.
  	 *
  	 * @param name attribute name
  	 * @param attribute attribute value
  	 * @since 1.11.1
  	 */
  	public void setValue(String name, BooleanAttribute attribute) {
  		FormItem item = formWidget.getField(name);
  		if (item != null) {
  			item.setValue(attribute.getValue());
  		}
  	}
  
  	/**
  	 * Apply a short attribute value on the form, with the given name.
  	 *
  	 * @param name attribute name
  	 * @param attribute attribute value
  	 * @since 1.11.1
  	 */
  	@Api
  	public void setValue(String name, ShortAttribute attribute) {
  		FormItem item = formWidget.getField(name);
  		if (item != null) {
  			item.setValue(attribute.getValue());
  		}
  	}
  
  	/**
  	 * Apply a integer attribute value on the form, with the given name.
  	 *
  	 * @param name attribute name
  	 * @param attribute attribute value
  	 * @since 1.11.1
  	 */
  	@Api
  	public void setValue(String name, IntegerAttribute attribute) {
  		FormItem item = formWidget.getField(name);
  		if (item != null) {
  			item.setValue(attribute.getValue());
  		}
  	}
  
  	/**
  	 * Apply a long attribute value on the form, with the given name.
  	 *
  	 * @param name attribute name
  	 * @param attribute attribute value
  	 * @since 1.11.1
  	 */
  	@Api
  	public void setValue(String name, LongAttribute attribute) {
  		FormItem item = formWidget.getField(name);
  