/* * Copyright 2008-2012 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * 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.amazon.carbonado.gen; import java.lang.reflect.Method; import java.util.AbstractCollection; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import com.amazon.carbonado.Storable; import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.util.SoftValuedCache; /** * Basic implementation for {@link Storable#propertyMap} method. * * @author Brian S O'Neill */ public class StorablePropertyMap<S extends Storable> extends AbstractMap<String, Object> { private static final SoftValuedCache<Class, Set<String>> cPropertyNamesForType = SoftValuedCache.newCache(11); public static <S extends Storable> StorablePropertyMap<S> createMap(Class<S> type, S storable) { Set<String> propertyNames; synchronized (cPropertyNamesForType) { propertyNames = cPropertyNamesForType.get(type); if (propertyNames == null) { Map<String, ? extends StorableProperty<S>> properties = StorableIntrospector.examine(type).getAllProperties(); for (StorableProperty<S> property : properties.values()) { if (shouldExclude(property)) { if (propertyNames == null) { propertyNames = new LinkedHashSet<String>(properties.keySet()); } propertyNames.remove(property.getName()); continue; } } if (propertyNames == null) { propertyNames = properties.keySet(); } else { propertyNames = Collections.unmodifiableSet(propertyNames); } } } return new StorablePropertyMap(propertyNames, storable); } private static boolean shouldExclude(StorableProperty<?> property) { return throwsCheckedException(property.getReadMethod()) || throwsCheckedException(property.getWriteMethod()); } private static boolean throwsCheckedException(Method method) { if (method == null) { return false; } Class<?>[] exceptionTypes = method.getExceptionTypes(); if (exceptionTypes == null) { return false; } for (Class<?> exceptionType : exceptionTypes) { if (RuntimeException.class.isAssignableFrom(exceptionType)) { continue; } if (Error.class.isAssignableFrom(exceptionType)) { continue; } return true; } return false; } private final Set<String> mPropertyNames; private final S mStorable; private StorablePropertyMap(Set<String> propertyNames, S storable) { mPropertyNames = propertyNames; mStorable = storable; } @Override public int size() { return mPropertyNames.size(); } @Override public boolean isEmpty() { // Storables require at least a primary key. return false; } @Override public boolean containsKey(Object key) { return mPropertyNames.contains(key); } @Override public Object get(Object key) { try { return mStorable.getPropertyValue((String) key); } catch (IllegalArgumentException e) { // Return null for unknown entries, as per Map specification. return null; } } @Override public Object put(String key, Object value) { Object old = mStorable.getPropertyValue(key); mStorable.setPropertyValue(key, value); return old; } @Override public Object remove(Object key) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public Set<String> keySet() { return mPropertyNames; } @Override public Collection<Object> values() { return new AbstractCollection<Object>() { @Override public Iterator<Object> iterator() { return new Iterator<Object>() { private final Iterator<String> mPropIterator = keySet().iterator(); public boolean hasNext() { return mPropIterator.hasNext(); } public Object next() { return get(mPropIterator.next()); } public void remove() { throw new UnsupportedOperationException(); } }; } @Override public int size() { return StorablePropertyMap.this.size(); } @Override public boolean isEmpty() { // Storables require at least a primary key. return false; } @Override public boolean contains(Object v) { return containsValue(v); } @Override public boolean remove(Object e) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } }; } @Override public Set<Map.Entry<String, Object>> entrySet() { return new AbstractSet<Map.Entry<String, Object>>() { @Override public Iterator<Map.Entry<String, Object>> iterator() { return new Iterator<Map.Entry<String, Object>>() { private final Iterator<String> mPropIterator = keySet().iterator(); public boolean hasNext() { return mPropIterator.hasNext(); } public Map.Entry<String, Object> next() { final String property = mPropIterator.next(); final Object value = get(property); return new Map.Entry<String, Object>() { Object mutableValue = value; public String getKey() { return property; } public Object getValue() { return mutableValue; } public Object setValue(Object value) { Object old = StorablePropertyMap.this.put(property, value); mutableValue = value; return old; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof Map.Entry) { Map.Entry other = (Map.Entry) obj; return (this.getKey() == null ? other.getKey() == null : this.getKey().equals(other.getKey())) && (this.getValue() == null ? other.getValue() == null : this.getValue().equals(other.getValue())); } return false; } @Override public int hashCode() { return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); } @Override public String toString() { return property + "=" + mutableValue; } }; } public void remove() { throw new UnsupportedOperationException(); } }; } @Override public int size() { return StorablePropertyMap.this.size(); } @Override public boolean isEmpty() { // Storables require at least a primary key. return false; } @Override public boolean contains(Object e) { Map.Entry<String, Object> entry = (Map.Entry<String, Object>) e; String key = entry.getKey(); if (StorablePropertyMap.this.containsKey(key)) { Object value = StorablePropertyMap.this.get(key); return value == null ? entry.getValue() == null : value.equals(entry.getValue()); } return false; } @Override public boolean add(Map.Entry<String, Object> e) { StorablePropertyMap.this.put(e.getKey(), e.getValue()); return true; } @Override public boolean remove(Object e) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } }; } }