All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fitbur.mockito.internal.creation.bytebuddy.CachingMockBytecodeGenerator Maven / Gradle / Ivy

There is a newer version: 1.0.0
Show newest version
package com.fitbur.mockito.internal.creation.bytebuddy;

import static com.fitbur.mockito.internal.util.StringJoiner.join;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.fitbur.mockito.exceptions.base.MockitoException;

class CachingMockBytecodeGenerator {

    private final Lock avoidingClassLeakCacheLock = new ReentrantLock();
    public final WeakHashMap avoidingClassLeakageCache =
            new WeakHashMap();

    private final MockBytecodeGenerator mockBytecodeGenerator = new MockBytecodeGenerator();

    @SuppressWarnings("unchecked")
    public  Class get(MockFeatures params) {
        // TODO improves locking behavior with ReentrantReadWriteLock ?
        avoidingClassLeakCacheLock.lock();
        try {

            Class generatedMockClass = mockCachePerClassLoaderOf(params.mockedType).getOrGenerateMockClass(
                    params
            );

            return (Class) generatedMockClass;
        } finally {
          avoidingClassLeakCacheLock.unlock();
        }
    }

    private  CachedBytecodeGenerator mockCachePerClassLoaderOf(Class mockedType) {
        if (!avoidingClassLeakageCache.containsKey(mockedType.getClassLoader())) {
            avoidingClassLeakageCache.put(
                    mockedType.getClassLoader(),
                    new CachedBytecodeGenerator(mockBytecodeGenerator)
            );
        }
        return avoidingClassLeakageCache.get(mockedType.getClassLoader());
    }

    private static class CachedBytecodeGenerator {
        private ConcurrentHashMap>> generatedClassCache =
                new ConcurrentHashMap>>();
        private final MockBytecodeGenerator generator;

        private CachedBytecodeGenerator(MockBytecodeGenerator generator) {
            this.generator = generator;
        }

        public  Class getOrGenerateMockClass(MockFeatures features) {
            MockKey mockKey = MockKey.of(features.mockedType, features.interfaces);
            Class generatedMockClass = null;
            WeakReference> classWeakReference = generatedClassCache.get(mockKey);
            if(classWeakReference != null) {
                generatedMockClass = classWeakReference.get();
            }
            if(generatedMockClass == null) {
                generatedMockClass = generate(features);
            }
            generatedClassCache.put(mockKey, new WeakReference>(generatedMockClass));
            return generatedMockClass;
        }

        private  Class generate(MockFeatures mockFeatures) {
            try {
                return generator.generateMockClass(mockFeatures);
            } catch (Exception bytecodeGenerationFailed) {
                throw prettifyFailure(mockFeatures, bytecodeGenerationFailed);
            }
        }

        private RuntimeException prettifyFailure(MockFeatures mockFeatures, Exception generationFailed) {
            if (Modifier.isPrivate(mockFeatures.mockedType.getModifiers())) {
                throw new MockitoException(join(
                        "Mockito cannot mock this class: " + mockFeatures.mockedType + ".",
                        "Most likely it is a private class that is not visible by Mockito",
                        ""
                ), generationFailed);
            }
            throw new MockitoException(join(
                    "Mockito cannot mock this class: " + mockFeatures.mockedType,
                    "",
                    "Mockito can only mock visible & non-final classes.",
                    "If you're not sure why you're getting this error, please report to the mailing list.",
                    "",
                    "Underlying exception : " + generationFailed),
                    generationFailed
            );
        }

        // should be stored as a weak reference
        private static class MockKey {
            private final String mockedType;
            private final Set types = new HashSet();

            private MockKey(Class mockedType, Set> interfaces) {
                this.mockedType = mockedType.getName();
                for (Class anInterface : interfaces) {
                    types.add(anInterface.getName());
                }
                types.add(this.mockedType);
            }

            @Override
            public boolean equals(Object other) {
                if (this == other) return true;
                if (other == null || getClass() != other.getClass()) return false;

                MockKey mockKey = (MockKey) other;

                if (!mockedType.equals(mockKey.mockedType)) return false;
                if (!types.equals(mockKey.types)) return false;

                return true;
            }

            @Override
            public int hashCode() {
                int result = mockedType.hashCode();
                result = 31 * result + types.hashCode();
                return result;
            }

            public static  MockKey of(Class mockedType, Set> interfaces) {
                return new MockKey(mockedType, interfaces);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy