/*
* Copyright (C) 2016 Red Hat, Inc. and/or its affiliates.
*
* 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.jboss.errai.databinding.client;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.errai.databinding.client.api.DataBinder;
import org.jboss.errai.databinding.client.api.StateSync;
/**
* A custom {@link BindableProxy} allowing data-binding to a {@link Map}. This allows data-binding to be used with a
* collection of properties that is not known at compile-time.
*
* @author Max Barkley <mbarkley@redhat.com>
*/
public class MapBindableProxy implements Map<String, Object>, BindableProxy<Map<String, Object>> {
private final BindableProxyAgent<Map<String, Object>> agent;
public MapBindableProxy( final Map<String, PropertyType> propertyTypes ) {
agent = new BindableProxyAgent<>(this, new HashMap<>());
agent.propertyTypes.putAll(propertyTypes);
agent
.propertyTypes
.entrySet()
.stream()
.filter(entry -> entry.getValue() instanceof MapPropertyType)
.forEach(entry -> agent.binders.put(entry.getKey(), DataBinder.forMap(((MapPropertyType) entry.getValue()).getPropertyTypes())));
}
@Override
public Object unwrap() {
return agent.target;
}
@Override
public Object get(final String propertyName) {
if (!agent.propertyTypes.containsKey(propertyName)) {
throw new NonExistingPropertyException("Map", propertyName);
}
return agent.target.get(propertyName);
}
@Override
public void set(final String propertyName, final Object value) {
if (!agent.propertyTypes.containsKey(propertyName)) {
throw new NonExistingPropertyException("Map", propertyName);
}
agent.target.put(propertyName, value);
}
@Override
public Map<String, PropertyType> getBeanProperties() {
return Collections.unmodifiableMap(agent.propertyTypes);
}
@Override
public BindableProxyAgent<Map<String, Object>> getBindableProxyAgent() {
return agent;
}
@Override
public void updateWidgets() {
agent.updateWidgetsAndFireEvents();
}
@Override
public Map<String, Object> deepUnwrap() {
final Map<String, Object> clone = new HashMap<>();
agent.target.forEach((k,v) -> clone.put(k, (v instanceof BindableProxy ? ((BindableProxy<?>) v).deepUnwrap() : v)));
return clone;
}
@Override
public int size() {
return agent.target.size();
}
@Override
public boolean isEmpty() {
return agent.target.isEmpty();
}
@Override
public boolean containsKey(final Object key) {
return agent.target.containsKey(key);
}
@Override
public boolean containsValue(final Object value) {
return agent.target.containsValue(value);
}
@Override
public Object get(final Object key) {
return agent.target.get(key);
}
@Override
public Object put(final String key, Object value) {
final PropertyType propertyType = agent.propertyTypes.get(key);
if (propertyType == null) {
throw new NonExistingPropertyException("Map", key);
}
if (propertyType.isList() && value instanceof List) {
value = agent.ensureBoundListIsProxied(key, (List<?>) value);
}
else if (propertyType.isBindable() && !(value instanceof BindableProxy)) {
DataBinder nestedBinder = agent.binders.get(key);
if (nestedBinder == null) {
if (propertyType instanceof MapPropertyType) {
nestedBinder = DataBinder.forMap(((MapPropertyType) propertyType).getPropertyTypes());
}
else {
nestedBinder = DataBinder.forModel(value);
}
agent.binders.put(key, nestedBinder);
}
else if (propertyType instanceof MapPropertyType) {
final MapBindableProxy mapProxy = new MapBindableProxy(((MapPropertyType) propertyType).getPropertyTypes());
mapProxy.agent.target = (Map<String, Object>) value;
nestedBinder.setModel(mapProxy, StateSync.FROM_MODEL, true);
}
else {
nestedBinder.setModel(value, StateSync.FROM_MODEL, true);
}
value = nestedBinder.getModel();
}
final Object oldValue = agent.target.get(key);
final Object retVal = agent.target.put(key, value);
agent.updateWidgetsAndFireEvent(false, key, oldValue, value);
return retVal;
}
@Override
public Object remove(final Object key) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(final Map<? extends String, ? extends Object> m) {
m.forEach(this::put);
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public Set<String> keySet() {
return Collections.unmodifiableSet(agent.target.keySet());
}
@Override
public Collection<Object> values() {
return Collections.unmodifiableCollection(agent.target.values());
}
@Override
public Set<java.util.Map.Entry<String, Object>> entrySet() {
return Collections.unmodifiableSet(agent.target.entrySet());
}
}