From f66a1afe1738206884f5661083e7e3380ab072b6 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 20 Jan 2008 03:36:58 +0000 Subject: Add Storable.propertyMap method. --- src/main/java/com/amazon/carbonado/Storable.java | 13 + .../amazon/carbonado/gen/CommonMethodNames.java | 1 + .../amazon/carbonado/gen/StorableGenerator.java | 24 +- .../amazon/carbonado/gen/StorablePropertyMap.java | 326 +++++++++++++++++++++ 4 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/amazon/carbonado/gen/StorablePropertyMap.java (limited to 'src/main/java/com/amazon/carbonado') diff --git a/src/main/java/com/amazon/carbonado/Storable.java b/src/main/java/com/amazon/carbonado/Storable.java index 9f83dee..aa0fd4e 100644 --- a/src/main/java/com/amazon/carbonado/Storable.java +++ b/src/main/java/com/amazon/carbonado/Storable.java @@ -18,6 +18,8 @@ package com.amazon.carbonado; +import java.util.Map; + /** * A data access object in a {@link Repository}. User defined storables must * either extend or implement this interface via an interface or abstract @@ -412,6 +414,17 @@ public interface Storable> { */ void setPropertyValue(String propertyName, Object value); + /** + * Returns a fixed-size map view of this Storable's properties. Properties + * which declare throwing any checked exceptions are excluded from the + * map. Removing and adding of map entries is unsupported. + * + * @return map of property name to property value; primitive property + * values are boxed + * @since 1.2 + */ + Map propertyMap(); + /** * Returns an exact shallow copy of this object, including the state. */ diff --git a/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java b/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java index 2ed5c8d..522edc6 100644 --- a/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java +++ b/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java @@ -54,6 +54,7 @@ public class CommonMethodNames { IS_PROPERTY_SUPPORTED = "isPropertySupported", GET_PROPERTY_VALUE = "getPropertyValue", SET_PROPERTY_VALUE = "setPropertyValue", + PROPERTY_MAP = "propertyMap", TO_STRING_KEY_ONLY_METHOD_NAME = "toStringKeyOnly", TO_STRING_METHOD_NAME = "toString", HASHCODE_METHOD_NAME = "hashCode", diff --git a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java index 9db49ad..5c8c93d 100644 --- a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java @@ -1618,9 +1618,10 @@ public final class StorableGenerator { b.returnValue(TypeDesc.BOOLEAN); } - // Define reflection-like method for manipulating property by name. + // Define reflection-like methods for manipulating properties by name. addGetPropertyValueMethod(); addSetPropertyValueMethod(); + addPropertyMapMethod(); // Define standard object methods. addHashCodeMethod(); @@ -2692,6 +2693,27 @@ public final class StorableGenerator { addPropertySwitch(b, SWITCH_FOR_SET); } + private void addPropertyMapMethod() { + TypeDesc mapType = TypeDesc.forClass(Map.class); + + MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, PROPERTY_MAP, mapType, null); + + if (mi == null) { + return; + } + + CodeBuilder b = new CodeBuilder(mi); + + TypeDesc propertyMapType = TypeDesc.forClass(StorablePropertyMap.class); + + b.loadConstant(TypeDesc.forClass(mStorableType)); + b.loadThis(); + b.invokeStatic(propertyMapType, "createMap", propertyMapType, + new TypeDesc[] {TypeDesc.forClass(Class.class), + TypeDesc.forClass(Storable.class)}); + b.returnValue(mapType); + } + /** * Defines a hashCode method. */ diff --git a/src/main/java/com/amazon/carbonado/gen/StorablePropertyMap.java b/src/main/java/com/amazon/carbonado/gen/StorablePropertyMap.java new file mode 100644 index 0000000..cbf0d0b --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/StorablePropertyMap.java @@ -0,0 +1,326 @@ +/* + * Copyright 2008 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 org.cojen.util.SoftValuedHashMap; + +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.info.StorableInfo; +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableProperty; + +/** + * Basic implementation for {@link Storable#propertyMap} method. + * + * @author Brian S O'Neill + */ +public class StorablePropertyMap extends AbstractMap { + private static final Map> cPropertyNamesForType = + new SoftValuedHashMap(); + + public static StorablePropertyMap createMap(Class type, S storable) + { + Set propertyNames; + synchronized (cPropertyNamesForType) { + propertyNames = cPropertyNamesForType.get(type); + + if (propertyNames == null) { + Map> properties = + StorableIntrospector.examine(type).getAllProperties(); + + for (StorableProperty property : properties.values()) { + if (shouldExclude(property)) { + if (propertyNames == null) { + propertyNames = new LinkedHashSet(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 mPropertyNames; + private final S mStorable; + + private StorablePropertyMap(Set 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 keySet() { + return mPropertyNames; + } + + @Override + public Collection values() { + return new AbstractCollection() { + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator 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> entrySet() { + return new AbstractSet>() { + @Override + public Iterator> iterator() { + return new Iterator>() { + private final Iterator mPropIterator = keySet().iterator(); + + public boolean hasNext() { + return mPropIterator.hasNext(); + } + + public Map.Entry next() { + final String property = mPropIterator.next(); + final Object value = get(property); + + return new Map.Entry() { + 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 entry = (Map.Entry) 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 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(); + } + }; + } +} -- cgit v1.2.3