/*
* 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.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.fakereplace.com.google.common.base.Function;
import org.fakereplace.com.google.common.collect.MapMaker;
import org.fakereplace.util.DescriptorUtils;
/**
* This class holds everything there is to know about a class that has been seen
* by the transformer
*
* @author stuart
*/
public class ClassData {
private final String className;
private final String internalName;
private final Map<String, Map<String, Set<MethodData>>> methods = new MapMaker().makeMap();
private final Map<Method, MethodData> methodsByMethod = new MapMaker().makeComputingMap(new MethodResolver());
private final Map<String, FieldData> fields = new MapMaker().makeMap();
private final Set<MethodData> methodSet = new HashSet<MethodData>();
private final ClassLoader loader;
private final String superClassName;
private final boolean signitureModified;
private final boolean replaceable;
private static final MethodData NULL_METHOD_DATA = new MethodData("", "", "", null, 0, false);
ClassData(BaseClassData data, Set<MethodData> addMethods, Set<MethodData> removedMethods, Set<FieldData> addedFields, Set<FieldData> removedFields) {
className = data.getClassName();
internalName = data.getInternalName();
loader = data.getLoader();
superClassName = data.getSuperClassName();
signitureModified = removedFields.isEmpty() && removedMethods.isEmpty() && addedFields.isEmpty() && addMethods.isEmpty();
replaceable = data.isReplaceable();
for (MethodData m : data.getMethods()) {
if (!removedMethods.contains(m)) {
addMethod(m);
}
}
for (FieldData f : data.getFields()) {
if (!removedFields.contains(f)) {
addField(f);
}
}
for (FieldData f : removedFields) {
addField(f);
}
for (FieldData f : addedFields) {
addField(f);
}
for (MethodData m : removedMethods) {
addMethod(m);
}
for (MethodData m : addMethods) {
addMethod(m);
}
for (String nm : methods.keySet()) {
for (String i : methods.get(nm).keySet()) {
methodSet.addAll(methods.get(nm).get(i));
}
}
}
public MethodData getData(Method method) {
MethodData res = methodsByMethod.get(method);
if (res == NULL_METHOD_DATA) {
return null;
}
return res;
}
ClassData(BaseClassData data) {
className = data.getClassName();
internalName = data.getInternalName();
loader = data.getLoader();
superClassName = data.getSuperClassName();
replaceable = data.isReplaceable();
for (MethodData m : data.getMethods()) {
addMethod(m);
}
for (FieldData f : data.getFields()) {
addField(f);
}
signitureModified = false;
}
public boolean isSignitureModified() {
return signitureModified;
}
/**
* Searches through parent classloaders of the classes class loader to find
* the ClassData structure for the super class
*
*/
public ClassData getSuperClassInformation() {
if (superClassName == null) {
return null;
}
ClassData superClassInformation = ClassDataStore.instance().getModifiedClassData(loader, superClassName);
ClassLoader l = loader;
while (superClassInformation == null && l != null) {
l = l.getParent();
superClassInformation = ClassDataStore.instance().getModifiedClassData(l, superClassName);
}
return superClassInformation;
}
public FieldData getField(String field) {
return fields.get(field);
}
public String getSuperClassName() {
return superClassName;
}
public ClassLoader getLoader() {
return loader;
}
public String getClassName() {
return className;
}
public String getInternalName() {
return internalName;
}
public void addMethod(MethodData data) {
if (!methods.containsKey(data.getMethodName())) {
methods.put(data.getMethodName(), new HashMap<String, Set<MethodData>>());
}
Map<String, Set<MethodData>> mts = methods.get(data.getMethodName());
if (!mts.containsKey(data.getArgumentDescriptor())) {
mts.put(data.getArgumentDescriptor(), new HashSet<MethodData>());
}
Set<MethodData> rr = mts.get(data.getArgumentDescriptor());
rr.add(data);
}
/**
* replaces a method if it already exists
*
*/
public void replaceMethod(MethodData data) {
if (!methods.containsKey(data.getMethodName())) {
methods.put(data.getMethodName(), new HashMap<String, Set<MethodData>>());
}
Map<String, Set<MethodData>> mts = methods.get(data.getMethodName());
Set<MethodData> rr = new HashSet<MethodData>();
mts.put(data.getArgumentDescriptor(), rr);
rr.add(data);
}
public void addField(FieldData data) {
fields.put(data.getName(), data);
}
public Collection<MethodData> getMethods() {
return methodSet;
}
public Collection<FieldData> getFields() {
return fields.values();
}
/**
* gets the method data based on name and signature. If there is multiple
* methods with the same name and signature it is undefined which one will be
* returned
*
*/
public MethodData getMethodData(String name, String arguments) {
Map<String, Set<MethodData>> r = methods.get(name);
if (r == null) {
return null;
}
Set<MethodData> ms = r.get(arguments);
if (ms == null) {
return null;
}
return ms.iterator().next();
}
private static class MethodResolver implements Function<Method, MethodData> {
public MethodData apply(Method from) {
ClassData dta = ClassDataStore.instance().getModifiedClassData(from.getDeclaringClass().getClassLoader(), from.getDeclaringClass().getName());
if (dta == null) {
return NULL_METHOD_DATA;
}
String descriptor = DescriptorUtils.getDescriptor(from);
for (MethodData m : dta.getMethods()) {
if (m.getMethodName().equals(from.getName()) && descriptor.equals(m.getDescriptor())) {
return m;
}
}
return NULL_METHOD_DATA;
}
}
public boolean isReplaceable() {
return replaceable;
}
}