/* * 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.repo.map; import java.lang.reflect.UndeclaredThrowableException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; import org.cojen.classfile.ClassFile; import org.cojen.classfile.CodeBuilder; import org.cojen.classfile.Label; import org.cojen.classfile.LocalVariable; import org.cojen.classfile.MethodInfo; import org.cojen.classfile.Modifiers; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; import com.amazon.carbonado.Storable; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.gen.CodeBuilderUtil; import com.amazon.carbonado.util.SoftValuedCache; /** * * * @author Brian S O'Neill */ class Key<S extends Storable> implements Comparable<Key<S>> { protected final S mStorable; protected final Comparator<S> mComparator; Key(S storable, Comparator<S> comparator) { mStorable = storable; mComparator = comparator; } @Override public String toString() { return mStorable.toString(); } public int compareTo(Key<S> other) { int result = mComparator.compare(mStorable, other.mStorable); if (result == 0) { result = tieBreaker() - other.tieBreaker(); } return result; } protected int tieBreaker() { return 0; } public static interface Assigner<S extends Storable> { void setKeyValues(S storable, Object[] identityValues); void setKeyValues(S storable, Object[] identityValues, Object rangeValue); } private static final SoftValuedCache<Class, Assigner> mAssigners; static { mAssigners = SoftValuedCache.newCache(11); } public static synchronized <S extends Storable> Assigner<S> getAssigner(Class<S> clazz) { Assigner<S> assigner = mAssigners.get(clazz); if (assigner == null) { assigner = createAssigner(clazz); mAssigners.put(clazz, assigner); } return assigner; } private static <S extends Storable> Assigner<S> createAssigner(Class<S> clazz) { final StorableInfo<S> info = StorableIntrospector.examine(clazz); ClassInjector ci = ClassInjector.create(clazz.getName(), clazz.getClassLoader()); ClassFile cf = new ClassFile(ci.getClassName()); cf.addInterface(Assigner.class); cf.markSynthetic(); cf.setSourceFile(Key.class.getName()); cf.setTarget("1.5"); cf.addDefaultConstructor(); // Define required setKeyValues methods. List<OrderedProperty<S>> pk = new ArrayList<OrderedProperty<S>>(info.getPrimaryKey().getProperties()); TypeDesc storableType = TypeDesc.forClass(Storable.class); TypeDesc storableArrayType = storableType.toArrayType(); TypeDesc userStorableType = TypeDesc.forClass(info.getStorableType()); TypeDesc objectArrayType = TypeDesc.OBJECT.toArrayType(); MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, "setKeyValues", null, new TypeDesc[] {storableType, objectArrayType}); CodeBuilder b = new CodeBuilder(mi); LocalVariable userStorableVar = b.createLocalVariable(null, userStorableType); b.loadLocal(b.getParameter(0)); b.checkCast(userStorableType); b.storeLocal(userStorableVar); // Switch on the number of values supplied. /* Switch looks like this for two pk properties: switch(identityValues.length) { default: throw new IllegalArgumentException(); case 2: storable.setPkProp2(identityValues[1]); case 1: storable.setPkProp1(identityValues[0]); case 0: } */ b.loadLocal(b.getParameter(1)); b.arrayLength(); int[] cases = new int[pk.size() + 1]; Label[] labels = new Label[pk.size() + 1]; for (int i=0; i<labels.length; i++) { cases[i] = pk.size() - i; labels[i] = b.createLabel(); } Label defaultLabel = b.createLabel(); b.switchBranch(cases, labels, defaultLabel); for (int i=0; i<labels.length; i++) { labels[i].setLocation(); int prop = cases[i] - 1; if (prop >= 0) { b.loadLocal(userStorableVar); b.loadLocal(b.getParameter(1)); b.loadConstant(prop); b.loadFromArray(storableArrayType); callSetPropertyValue(b, pk.get(prop)); } // Fall through to next case. } b.returnVoid(); defaultLabel.setLocation(); CodeBuilderUtil.throwException(b, IllegalArgumentException.class, null); // The setKeyValues method that takes a range value calls the other // setKeyValues method first, to take care of the identityValues. mi = cf.addMethod(Modifiers.PUBLIC, "setKeyValues", null, new TypeDesc[] {storableType, objectArrayType, TypeDesc.OBJECT}); b = new CodeBuilder(mi); b.loadThis(); b.loadLocal(b.getParameter(0)); b.loadLocal(b.getParameter(1)); b.invokeVirtual("setKeyValues", null, new TypeDesc[] {storableType, objectArrayType}); userStorableVar = b.createLocalVariable(null, userStorableType); b.loadLocal(b.getParameter(0)); b.checkCast(userStorableType); b.storeLocal(userStorableVar); // Switch on the number of values supplied. /* Switch looks like this for two pk properties: switch(identityValues.length) { default: throw new IllegalArgumentException(); case 0: storable.setPkProp1(rangeValue); return; case 1: storable.setPkProp2(rangeValue); return; } */ b.loadLocal(b.getParameter(1)); b.arrayLength(); cases = new int[pk.size()]; labels = new Label[pk.size()]; for (int i=0; i<labels.length; i++) { cases[i] = i; labels[i] = b.createLabel(); } defaultLabel = b.createLabel(); b.switchBranch(cases, labels, defaultLabel); for (int i=0; i<labels.length; i++) { labels[i].setLocation(); int prop = cases[i]; b.loadLocal(userStorableVar); b.loadLocal(b.getParameter(2)); callSetPropertyValue(b, pk.get(prop)); b.returnVoid(); } defaultLabel.setLocation(); CodeBuilderUtil.throwException(b, IllegalArgumentException.class, null); try { return (Assigner<S>) ci.defineClass(cf).newInstance(); } catch (IllegalAccessException e) { throw new UndeclaredThrowableException(e); } catch (InstantiationException e) { throw new UndeclaredThrowableException(e); } } /** * Creates code to call set method. Assumes Storable and property value * are already on the stack. */ private static void callSetPropertyValue(CodeBuilder b, OrderedProperty<?> op) { StorableProperty<?> property = op.getChainedProperty().getLastProperty(); TypeDesc propType = TypeDesc.forClass(property.getType()); if (propType != TypeDesc.OBJECT) { TypeDesc objectType = propType.toObjectType(); b.checkCast(objectType); // Potentially unbox primitive. b.convert(objectType, propType); } b.invoke(property.getWriteMethod()); } }