package act.inject.param;
/*-
* #%L
* ACT Framework
* %%
* Copyright (C) 2014 - 2017 ActFramework
* %%
* 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.
* #L%
*/
import act.app.App;
import act.app.AppClassLoader;
import act.app.AppServiceBase;
import act.inject.DependencyInjector;
import org.osgl.$;
import org.osgl.exception.UnexpectedException;
import org.osgl.inject.BeanSpec;
import org.osgl.util.E;
import org.osgl.util.Generics;
import org.osgl.util.S;
import javax.inject.Inject;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class JsonDTOClassManager extends AppServiceBase<JsonDTOClassManager> {
static class DynamicClassLoader extends ClassLoader {
private DynamicClassLoader(AppClassLoader parent) {
super(parent);
}
Class<?> defineClass(String name, byte[] b) {
AppClassLoader loader = (AppClassLoader) getParent();
return loader.defineClass(name, b, 0, b.length, true);
}
}
private ConcurrentMap<String, Class<? extends JsonDTO>> dtoClasses = new ConcurrentHashMap<String, Class<? extends JsonDTO>>();
private DependencyInjector<?> injector;
private DynamicClassLoader dynamicClassLoader;
public JsonDTOClassManager(App app) {
super(app);
this.injector = app.injector();
this.dynamicClassLoader = new DynamicClassLoader(app.classLoader());
}
@Override
protected void releaseResources() {
}
public Class<? extends JsonDTO> get(Class<?> host, Method method) {
List<BeanSpec> beanSpecs = beanSpecs(host, method);
String key = key(beanSpecs);
if (S.blank(key)) {
return null;
}
Class<? extends JsonDTO> c = dtoClasses.get(key);
if (null == c) {
try {
c = generate(key, beanSpecs);
} catch (LinkageError e) {
if (e.getMessage().contains("duplicate class definition")) {
// another thread has already the DTO class
return dtoClasses.get(key);
}
}
dtoClasses.putIfAbsent(key, c);
}
return c;
}
private Class<? extends JsonDTO> generate(String name, List<BeanSpec> beanSpecs) {
return new JsonDTOClassGenerator(name, beanSpecs, dynamicClassLoader).generate();
}
public static final $.Predicate<Class<?>> CLASS_FILTER = new $.Predicate<Class<?>>() {
@Override
public boolean test(Class<?> aClass) {
if (null == aClass || Object.class == aClass) {
return false;
}
Annotation[] annotations = aClass.getDeclaredAnnotations();
if (null == annotations) {
return true;
}
for (Annotation a : annotations) {
if (a.annotationType() == NoBind.class) {
return false;
}
}
return true;
}
};
public static final $.Predicate<Field> FIELD_FILTER = new $.Predicate<Field>() {
@Override
public boolean test(Field field) {
return !Modifier.isStatic(field.getModifiers());
}
};
public List<BeanSpec> beanSpecs(Class<?> host, Method method) {
List<BeanSpec> list = new ArrayList<BeanSpec>();
if (!Modifier.isStatic(method.getModifiers())) {
extractBeanSpec(list, $.fieldsOf(host, CLASS_FILTER, FIELD_FILTER), host);
}
extractBeanSpec(list, method, host);
Collections.sort(list, CMP);
return list;
}
private void extractBeanSpec(List<BeanSpec> beanSpecs, List<Field> fields, Class<?> host) {
for (Field field : fields) {
BeanSpec spec = null;
Type genericType = field.getGenericType();
if (genericType instanceof Class || genericType instanceof ParameterizedType) {
spec = BeanSpec.of(field.getGenericType(), field.getDeclaredAnnotations(), field.getName(), injector);
} else if (genericType instanceof TypeVariable) {
// can determine type by field, check inject constructor parameter
TypeVariable tv = (TypeVariable)genericType;
Type[] bounds = tv.getBounds();
if (bounds != null && bounds.length == 1) {
Type bound = bounds[0];
if (bound instanceof ParameterizedType || bound instanceof Class) {
Class<?> boundClass = BeanSpec.rawTypeOf(bound);
Constructor<?>[] ca = host.getConstructors();
CONSTRUCTORS:
for (Constructor<?> c : ca) {
if (c.getAnnotation(Inject.class) != null) {
// check all param types
Type[] constructorParams = c.getGenericParameterTypes();
for (Type paramType : constructorParams) {
if (paramType instanceof ParameterizedType || paramType instanceof Class) {
Class<?> paramClass = BeanSpec.rawTypeOf(paramType);
if (boundClass.isAssignableFrom(paramClass)) {
spec = BeanSpec.of(paramType, field.getDeclaredAnnotations(), field.getName(), injector);
break CONSTRUCTORS;
}
}
}
}
}
}
}
}
if (null == spec) {
throw E.unexpected("Cannot determine bean spec of field: %s", field);
}
if (!ParamValueLoaderService.noBindOrProvided(spec, injector)) {
beanSpecs.add(spec);
}
}
}
private void extractBeanSpec(List<BeanSpec> beanSpecs, Method method, Class host) {
Type[] paramTypes = method.getGenericParameterTypes();
int sz = paramTypes.length;
if (0 == sz) {
return;
}
Annotation[][] annotations = method.getParameterAnnotations();
for (int i = 0; i < sz; ++i) {
Type type = paramTypes[i];
if (type instanceof TypeVariable && !Modifier.isStatic(method.getModifiers())) {
// explore type variable impl
TypeVariable typeVar = $.cast(type);
String typeVarName = typeVar.getName();
// find all generic types on host
Map<String, Class> typeVarLookup = Generics.buildTypeParamImplLookup(host);
type = typeVarLookup.get(typeVarName);
if (null == type) {
throw new UnexpectedException("Cannot determine concrete type of method parameter %s", typeVarName);
}
}
Annotation[] anno = annotations[i];
BeanSpec spec = BeanSpec.of(type, anno, injector);
if (!ParamValueLoaderService.noBindOrProvided(spec, injector)) {
beanSpecs.add(spec);
}
}
}
private static final Comparator<BeanSpec> CMP = new Comparator<BeanSpec>() {
@Override
public int compare(BeanSpec o1, BeanSpec o2) {
return o1.name().compareTo(o2.name());
}
};
private static String key(List<BeanSpec> beanSpecs) {
S.Buffer sb = S.buffer();
for (BeanSpec beanSpec : beanSpecs) {
sb.append(beanSpec.name()).append(beanSpec.type().hashCode());
}
return sb.toString();
}
}