/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Cyclos is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.utils.binding; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import nl.strohalm.cyclos.utils.ClassHelper; import nl.strohalm.cyclos.utils.PropertyHelper; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.IteratorUtils; /** * A binder for maps * @author luis */ public class MapBinder<K, V> extends DataBinder<Map<K, V>> { private static final long serialVersionUID = -234513355349764092L; public static <K, V> MapBinder<K, V> instance(final DataBinder<K> keyBinder, final DataBinder<V> valueBinder) { return instance(keyBinder, valueBinder, null); } public static <K, V> MapBinder<K, V> instance(final DataBinder<K> keyBinder, final DataBinder<V> valueBinder, final String path) { final MapBinder<K, V> binder = new MapBinder<K, V>(keyBinder, valueBinder); binder.setPath(path); return binder; } private DataBinder<K> keyBinder; private DataBinder<V> valueBinder; public MapBinder() { final Class<Map<K, V>> clazz = ClassHelper.cast(Map.class); setType(clazz); } public MapBinder(final DataBinder<K> keyBinder, final DataBinder<V> valueBinder) { this(); setKeyBinder(keyBinder); setValueBinder(valueBinder); } public DataBinder<K> getKeyBinder() { return keyBinder; } public DataBinder<V> getValueBinder() { return valueBinder; } @Override public Map<K, V> read(final Object object) { final Map<K, V> ret = new LinkedHashMap<K, V>(); readInto(ret, object, false); return ret; } @Override public String readAsString(final Object object) { validateNestedBinders(); final StringBuilder sb = new StringBuilder(); sb.append('{'); for (final Iterator<?> it = IteratorUtils.getIterator(object); it.hasNext();) { final Object value = it.next(); sb.append(keyBinder.readAsString(value)).append('=').append(valueBinder.readAsString(value)); if (it.hasNext()) { sb.append(','); } } sb.append('}'); return sb.toString(); } @Override public Map<K, V> readFromString(final Object object) { validateNestedBinders(); final Map<K, V> ret = new LinkedHashMap<K, V>(); readInto(ret, object, true); return ret; } public void readInto(final Map<K, V> map, final Object object, final boolean asString) { validateNestedBinders(); Iterator<Object> elements = null; final Object bean = PropertyHelper.get(object, getPath()); // if (isReadFlat()) { // In this case we have separate properties, each one with a collection of values of a single property. int maxSize = 0; final List<Object> keyValues = new ArrayList<Object>(); final List<Object> valueValues = new ArrayList<Object>(); Object obj = PropertyHelper.get(bean, keyBinder.getPath()); CollectionUtils.addAll(keyValues, IteratorUtils.getIterator(obj)); obj = PropertyHelper.get(bean, valueBinder.getPath()); CollectionUtils.addAll(valueValues, IteratorUtils.getIterator(obj)); maxSize = Math.max(keyValues.size(), valueValues.size()); final List<Object> list = new ArrayList<Object>(); for (int i = 0; i < maxSize; i++) { final Map<String, Object> current = new HashMap<String, Object>(); current.put(keyBinder.getPath(), keyValues.get(i)); current.put(valueBinder.getPath(), valueValues.get(i)); list.add(current); } elements = list.iterator(); // } else { // //Here, there is a single property with a collection of values // elements = IteratorUtils.getIterator(bean); // } // Asseble the resulting collection if (elements != null) { while (elements.hasNext()) { final Object current = elements.next(); map.put(asString ? keyBinder.readFromString(current) : keyBinder.read(current), asString ? valueBinder.readFromString(current) : valueBinder.read(current)); } } } public void setKeyBinder(final DataBinder<K> keyBinder) { this.keyBinder = keyBinder; } public void setValueBinder(final DataBinder<V> valueBinder) { this.valueBinder = valueBinder; } @Override public void write(final Object object, final Map<K, V> values) { validateNestedBinders(); doWrite(object, values); } @Override public void writeAsString(final Object object, final Object value) { validateNestedBinders(); final Map<String, String> values = new HashMap<String, String>(); if (value instanceof Map<?, ?>) { for (final Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) { final String keyAsString = keyBinder.readAsString(Collections.singletonMap(keyBinder.getPath(), entry.getKey())); final String valueAsString = valueBinder.readAsString(Collections.singletonMap(valueBinder.getPath(), entry.getValue())); values.put(keyAsString, valueAsString); } } else { for (final Iterator<?> it = IteratorUtils.getIterator(value); it.hasNext();) { final Object current = it.next(); values.put(keyBinder.readAsString(current), valueBinder.readAsString(current)); } } doWrite(object, values); } private void doWrite(final Object object, final Map<?, ?> map) { final List<Object> keys = new ArrayList<Object>(); final List<Object> values = new ArrayList<Object>(); for (final Map.Entry<?, ?> entry : map.entrySet()) { keys.add(entry.getKey()); values.add(entry.getValue()); } Object bean = PropertyHelper.get(object, getPath()); if (bean == null) { bean = new HashMap<String, Object>(); PropertyHelper.set(object, getPath(), bean); } PropertyHelper.set(bean, keyBinder.getPath(), keys); PropertyHelper.set(bean, valueBinder.getPath(), values); } private void validateNestedBinders() { if (this.keyBinder == null) { throw new IllegalStateException("null.key-binder"); } if (this.valueBinder == null) { throw new IllegalStateException("null.value-binder"); } } }