/* * Copyright 2010-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.lang.reflect.Modifier; import java.lang.reflect.Constructor; import java.util.WeakHashMap; import com.amazon.carbonado.Storable; import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.info.StorableProperty; import org.cojen.classfile.ClassFile; import org.cojen.classfile.CodeBuilder; import org.cojen.classfile.Label; import org.cojen.classfile.MethodInfo; import org.cojen.classfile.Modifiers; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; /** * Copies properties between otherwise incompatible Storables. Only matched * properties are copied, and primitive types are converted. * * @author Brian S O'Neill * @since 1.2.2 */ public abstract class StorableCopier<S extends Storable, T extends Storable> { private static final WeakHashMap<Class, Object> cClassKeyCache; private static final WeakHashMap<Object, From> cFromCache; static { cClassKeyCache = new WeakHashMap<Class, Object>(); cFromCache = new WeakHashMap<Object, From>(); } static synchronized Object classKey(Class clazz) { Object key = cClassKeyCache.get(clazz); if (key == null) { key = new Object(); cClassKeyCache.put(clazz, key); } return key; } public static synchronized <S extends Storable> From<S> from(Class<S> source) { Object key = classKey(source); From<S> from = (From<S>) cFromCache.get(key); if (from == null) { from = new From<S>(source); cFromCache.put(key, from); } return from; } public static class From<S extends Storable> { private final Class<S> mSource; private final WeakHashMap<Object, StorableCopier> mCopierCache; From(Class<S> source) { mSource = source; mCopierCache = new WeakHashMap<Object, StorableCopier>(); } public synchronized <T extends Storable> StorableCopier<S, T> to(Class<T> target) { Object key = classKey(target); StorableCopier<S, T> copier = (StorableCopier<S, T>) mCopierCache.get(key); if (copier == null) { if (mSource == target) { copier = (StorableCopier<S, T>) Direct.THE; } else { copier = new Wrapped<S, T>(new Wrapper<S, T>(mSource, target).generate()); } mCopierCache.put(key, copier); } return copier; } } protected StorableCopier() { } public abstract void copyAllProperties(S source, T target); public abstract void copyPrimaryKeyProperties(S source, T target); public abstract void copyVersionProperty(S source, T target); public abstract void copyUnequalProperties(S source, T target); public abstract void copyDirtyProperties(S source, T target); private static class Wrapped<S extends Storable, T extends Storable> extends StorableCopier<S, T> { private final Constructor<? extends S> mWrapperCtor; private Wrapped(Constructor<? extends S> ctor) { mWrapperCtor = ctor; } public void copyAllProperties(S source, T target) { source.copyAllProperties(wrap(target)); } public void copyPrimaryKeyProperties(S source, T target) { source.copyPrimaryKeyProperties(wrap(target)); } public void copyVersionProperty(S source, T target) { source.copyVersionProperty(wrap(target)); } public void copyUnequalProperties(S source, T target) { source.copyUnequalProperties(wrap(target)); } public void copyDirtyProperties(S source, T target) { source.copyDirtyProperties(wrap(target)); } private S wrap(T target) { try { return mWrapperCtor.newInstance(target); } catch (Exception e) { throw new AssertionError(e); } } } private static class Direct<S extends Storable> extends StorableCopier<S, S> { static final Direct THE = new Direct(); private Direct() { } public void copyAllProperties(S source, S target) { source.copyAllProperties(target); } public void copyPrimaryKeyProperties(S source, S target) { source.copyPrimaryKeyProperties(target); } public void copyVersionProperty(S source, S target) { source.copyVersionProperty(target); } public void copyUnequalProperties(S source, S target) { source.copyUnequalProperties(target); } public void copyDirtyProperties(S source, S target) { source.copyDirtyProperties(target); } } private static class Wrapper<W extends Storable, D extends Storable> { private final StorableInfo<W> mWrapperInfo; private final StorableInfo<D> mDelegateInfo; private final ClassInjector mClassInjector; private final ClassFile mClassFile; Wrapper(Class<W> wrapper, Class<D> delegate) { mWrapperInfo = StorableIntrospector.examine(wrapper); mDelegateInfo = StorableIntrospector.examine(delegate); ClassLoader loader = wrapper.getClassLoader(); try { loader.loadClass(delegate.getName()); } catch (ClassNotFoundException e) { loader = delegate.getClassLoader(); try { loader.loadClass(wrapper.getName()); } catch (ClassNotFoundException e2) { // This could be fixed by creating an intermediate class loader, but // other issues might crop up. throw new IllegalStateException ("Unable for find common class loader for source and target types: " + wrapper.getClass() + ", " + delegate.getClass()); } } mClassInjector = ClassInjector.create(wrapper.getName(), loader); mClassFile = CodeBuilderUtil.createStorableClassFile (mClassInjector, mWrapperInfo.getStorableType(), false, StorableCopier.class.getName()); } Constructor<? extends W> generate() { TypeDesc delegateType = TypeDesc.forClass(mDelegateInfo.getStorableType()); mClassFile.addField(Modifiers.PRIVATE.toFinal(true), "delegate", delegateType); MethodInfo mi = mClassFile.addConstructor (Modifiers.PUBLIC, new TypeDesc[] {delegateType}); CodeBuilder b = new CodeBuilder(mi); b.loadThis(); b.invokeSuperConstructor(null); b.loadThis(); b.loadLocal(b.getParameter(0)); b.storeField("delegate", delegateType); b.returnVoid(); // Implement property access methods. for (StorableProperty<W> wrapperProp : mWrapperInfo.getAllProperties().values()) { if (wrapperProp.isDerived()) { continue; } TypeDesc wrapperPropType = TypeDesc.forClass(wrapperProp.getType()); StorableProperty<D> delegateProp = mDelegateInfo.getAllProperties().get(wrapperProp.getName()); if (delegateProp == null || delegateProp.isDerived()) { addUnmatchedProperty(wrapperProp, wrapperPropType); continue; } TypeDesc delegatePropType = TypeDesc.forClass(delegateProp.getType()); if (wrapperPropType.equals(delegatePropType)) { // No conversion required. Method m = canDefine(wrapperProp.getReadMethod()); if (m != null) { b = new CodeBuilder(mClassFile.addMethod(m)); if (delegateProp.getReadMethod() == null) { CodeBuilderUtil.blankValue(b, wrapperPropType); } else { b.loadThis(); b.loadField("delegate", delegateType); b.invoke(delegateProp.getReadMethod()); } b.returnValue(wrapperPropType); } m = canDefine(wrapperProp.getWriteMethod()); if (m != null) { b = new CodeBuilder(mClassFile.addMethod(m)); if (delegateProp.getWriteMethod() != null) { b.loadThis(); b.loadField("delegate", delegateType); b.loadLocal(b.getParameter(0)); b.invoke(delegateProp.getWriteMethod()); } b.returnVoid(); } continue; } TypeDesc wrapperPrimPropType = wrapperPropType.toPrimitiveType(); TypeDesc delegatePrimPropType = delegatePropType.toPrimitiveType(); if (wrapperPrimPropType == null || delegatePrimPropType == null) { addUnmatchedProperty(wrapperProp, wrapperPropType); continue; } // Convert primitive or boxed type. Method m = canDefine(wrapperProp.getReadMethod()); if (m != null) { b = new CodeBuilder(mClassFile.addMethod(m)); if (delegateProp.getReadMethod() == null) { CodeBuilderUtil.blankValue(b, wrapperPropType); } else { b.loadThis(); b.loadField("delegate", delegateType); b.invoke(delegateProp.getReadMethod()); if (wrapperPropType.isPrimitive() && !delegatePropType.isPrimitive()) { // Check for null. b.dup(); Label notNull = b.createLabel(); b.ifNullBranch(notNull, false); CodeBuilderUtil.blankValue(b, wrapperPropType); b.returnValue(wrapperPropType); notNull.setLocation(); } b.convert(delegatePropType, wrapperPropType); } b.returnValue(wrapperPropType); } m = canDefine(wrapperProp.getWriteMethod()); if (m != null) { b = new CodeBuilder(mClassFile.addMethod(m)); if (delegateProp.getWriteMethod() != null) { b.loadThis(); b.loadField("delegate", delegateType); b.loadLocal(b.getParameter(0)); if (!wrapperPropType.isPrimitive() && delegatePropType.isPrimitive()) { // Check for null. Label notNull = b.createLabel(); b.ifNullBranch(notNull, false); CodeBuilderUtil.blankValue(b, delegatePropType); b.invoke(delegateProp.getWriteMethod()); b.returnVoid(); notNull.setLocation(); b.loadLocal(b.getParameter(0)); } b.convert(wrapperPropType, delegatePropType); b.invoke(delegateProp.getWriteMethod()); } b.returnVoid(); } } mi = mClassFile.addMethod (Modifiers.PUBLIC, CommonMethodNames.IS_PROPERTY_SUPPORTED, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING}); b = new CodeBuilder(mi); b.loadConstant(true); b.returnValue(TypeDesc.BOOLEAN); try { Class<? extends W> wrapperClass = mClassInjector.defineClass(mClassFile); return wrapperClass.getConstructor(mDelegateInfo.getStorableType()); } catch (Exception e) { throw new AssertionError(e); } } private void addUnmatchedProperty(StorableProperty<W> wrapperProp, TypeDesc wrapperPropType) { Method m = canDefine(wrapperProp.getReadMethod()); if (m != null) { CodeBuilder b = new CodeBuilder(mClassFile.addMethod(m)); CodeBuilderUtil.blankValue(b, wrapperPropType); b.returnValue(wrapperPropType); } m = canDefine(wrapperProp.getWriteMethod()); if (m != null) { CodeBuilder b = new CodeBuilder(mClassFile.addMethod(m)); b.returnVoid(); } } private static Method canDefine(Method m) { return (m == null || Modifier.isFinal(m.getModifiers())) ? null : m; } } }