/* * Copyright 2016, Stuart Douglas, and individual contributors as indicated * by the @authors tag. * * 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 org.fakereplace.data; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Consumer; import java.util.function.Function; import org.fakereplace.classloading.ClassIdentifier; import org.fakereplace.core.BuiltinClassData; import org.fakereplace.com.google.common.collect.MapMaker; import org.fakereplace.manip.util.MapFunction; import org.fakereplace.reflection.FieldAccessor; public class ClassDataStore { private static final ClassDataStore INSTANCE = new ClassDataStore(); private final Map<String, Class<?>> proxyNameToReplacedClass = new ConcurrentHashMap<String, Class<?>>(); private final Map<String, FieldAccessor> proxyNameToFieldAccessor = new ConcurrentHashMap<String, FieldAccessor>(); private final Map<ClassLoader, ConcurrentMap<String, ClassData>> classData = new MapMaker().weakKeys().makeComputingMap(new MapFunction<ClassLoader, String, ClassData>(false)); private final Map<ClassLoader, ConcurrentMap<String, BaseClassData>> baseClassData = new MapMaker().weakKeys().makeComputingMap(new MapFunction<ClassLoader, String, BaseClassData>(false)); private final Map<String, MethodData> proxyNameToMethodData = new ConcurrentHashMap<String, MethodData>(); private final Set<ClassIdentifier> replacedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); /** * takes the place of the null key on ConcurrentHashMap */ private static final ClassLoader NULL_LOADER = new ClassLoader() { }; private ClassDataStore() { } public void markClassReplaced(Class<?> clazz) { replacedClasses.add(new ClassIdentifier(clazz.getName(), clazz.getClassLoader())); } public boolean isClassReplaced(Class<?> clazz) { return replacedClasses.contains(new ClassIdentifier(clazz.getName(), clazz.getClassLoader())); } public boolean isClassReplaced(String name, ClassLoader loader) { return replacedClasses.contains(new ClassIdentifier(name, loader)); } public void saveClassData(ClassLoader loader, String className, ClassDataBuilder data) { className = className.replace('/', '.'); if (loader == null) { loader = NULL_LOADER; } Map<String, ClassData> map = classData.get(loader); map.put(className, data.buildClassData()); } public void saveClassData(ClassLoader loader, String className, BaseClassData data) { className = className.replace('/', '.'); if (loader == null) { loader = NULL_LOADER; } Map<String, BaseClassData> map = baseClassData.get(loader); map.put(className, data); } public ClassData getModifiedClassData(ClassLoader loader, String className) { className = className.replace('/', '.'); if (loader == null) { loader = NULL_LOADER; } Map<String, ClassData> map = classData.get(loader); ClassData cd = map.get(className); if (cd == null) { BaseClassData dd = getBaseClassData(loader, className); if (dd == null) { return null; } ClassDataBuilder builder = new ClassDataBuilder(dd); ClassData d = builder.buildClassData(); map.put(className, d); return d; } return cd; } public BaseClassData getBaseClassData(ClassLoader loader, String className) { className = className.replace('/', '.'); if (loader == null) { loader = NULL_LOADER; } Map<String, BaseClassData> map = baseClassData.get(loader); if (!map.containsKey(className)) { // if this is a class that is not being instrumented it is safe to // load the class and get the data if (BuiltinClassData.skipInstrumentation(className)) { try { if (loader != NULL_LOADER) { Class<?> cls = loader.loadClass(className); saveClassData(loader, className, new BaseClassData(cls)); } else { Class<?> cls = Class.forName(className); saveClassData(loader, className, new BaseClassData(cls)); } } catch (ClassNotFoundException e) { return null; } } else { return null; } } BaseClassData cd = map.get(className); return cd; } public Class<?> getRealClassFromProxyName(String proxyName) { return proxyNameToReplacedClass.get(proxyName); } public void registerProxyName(Class<?> c, String proxyName) { proxyNameToReplacedClass.put(proxyName, c); } public void registerFieldAccessor(String proxyName, FieldAccessor accessor) { proxyNameToFieldAccessor.put(proxyName, accessor); } public void registerReplacedMethod(String proxyName, MethodData methodData) { proxyNameToMethodData.put(proxyName, methodData); } public MethodData getMethodInformation(String proxyName) { return proxyNameToMethodData.get(proxyName); } public FieldAccessor getFieldAccessor(String proxyName) { return proxyNameToFieldAccessor.get(proxyName); } public static ClassDataStore instance() { return INSTANCE; } /** * THIS IS A TEMPORARY METHOD * * It should only exist during the transition phase, while all rewriting is being moved into the transformers. * * Once all processing is in the transformer chain then it should be removed, and a class data builder passed through * all the transformers instead * * TODO: remove this method */ public void modifyCurrentData(ClassLoader loader, String name, Consumer<ClassDataBuilder> consumer) { ClassData current = getModifiedClassData(loader, name); ClassDataBuilder builder = new ClassDataBuilder(current, getBaseClassData(loader, name)); consumer.accept(builder); ClassDataStore.instance().saveClassData(loader, name, builder); } }