/*
* Copyright 2011-2013 the original author or authors.
*
* 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 kr.debop4j.core.reflect;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import java.lang.reflect.Modifier;
import static kr.debop4j.core.Guard.shouldNotBeNull;
import static org.objectweb.asm.Opcodes.*;
/**
* 객체의 생성자에 접근하여, 생성자를 생성할 수 있도록 합니다.
*
* @author 배성혁 ( sunghyouk.bae@gmail.com )
* @since 13. 1. 21
*/
@Slf4j
public abstract class ConstructorAccess<T> {
@Getter
boolean nonStaticMemberClass;
/** Constructor for top-level classes and static nested classes. */
public abstract T newInstance();
/** Constructor for inner classes (non-static nested classes) - except static nested classes */
public abstract T newInstance(Object enclosingInstance);
private static final Object syncLock = new Object();
/** 지정한 수형의 생성자에 대한 접근자를 생성합니다. */
@SuppressWarnings("unchecked")
public static <T> ConstructorAccess<T> get(Class<T> type) {
shouldNotBeNull(type, "type");
log.trace("수형[{}]의 생성자에 대한 접근자를 조회합니다.", type.getName());
Class enclosingType = type.getEnclosingClass();
boolean isNonStaticMemberClass = enclosingType != null && type.isMemberClass() && !Modifier.isStatic(type.getModifiers());
String className = type.getName();
String accessClassName = className + "ConstructorAccess";
if (accessClassName.startsWith("java."))
accessClassName = ReflectConsts.BASE_PACKAGE + "." + accessClassName;
Class accessClass = null;
AccessClassLoader loader = AccessClassLoader.get(type);
shouldNotBeNull(loader, "loader");
synchronized (syncLock) {
try {
accessClass = loader.loadClass(accessClassName);
} catch (ClassNotFoundException ignored) {
String accessClassNameInternal = accessClassName.replace('.', '/');
String classNameInternal = className.replace('.', '/');
String enclosingClassNameInternal;
if (!isNonStaticMemberClass) {
enclosingClassNameInternal = null;
try {
type.getConstructor((Class[]) null);
} catch (Exception ex) {
throw new RuntimeException("[" + type.getName() + "] 을 생성하지 못합니다. 기본 생성자를 찾지 못했습니다.", ex);
}
} else {
enclosingClassNameInternal = enclosingType.getName().replace('.', '/');
try {
type.getConstructor(enclosingType); // Inner classes should have this.
} catch (Exception ex) {
throw new RuntimeException("Non-static member class 를 생성할 수 없습니다. 기본 생성자가 없습니다. type=" + type.getName(), ex);
}
}
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, ReflectConsts.CONSTRUCTOR_ACCESS_PATH, null);
insertConstructor(cw);
insertNewInstance(cw, classNameInternal);
insertNewInstanceInner(cw, classNameInternal, enclosingClassNameInternal);
cw.visitEnd();
accessClass = loader.defineClass(accessClassName, cw.toByteArray());
}
}
try {
ConstructorAccess<T> access = (ConstructorAccess<T>) accessClass.newInstance();
access.nonStaticMemberClass = isNonStaticMemberClass;
log.trace("기본 생성자에 접근 가능한 ConstructorAccess 를 반환합니다. accessClassName=[{}]", accessClassName);
return access;
} catch (Exception ex) {
throw new RuntimeException("Error constructing constructor access class: [" + accessClassName + "]", ex);
}
}
/** 생성자를 추가한다. */
static private void insertConstructor(ClassWriter cw) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, ReflectConsts.CONSTRUCTOR_ACCESS_PATH, "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
/** 새로운 인스턴스를 추가합니다. */
static void insertNewInstance(ClassWriter cw, String classNameInternal) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "newInstance", "()Ljava/lang/Object;", null, null);
mv.visitCode();
mv.visitTypeInsn(NEW, classNameInternal);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, classNameInternal, "<init>", "()V");
mv.visitInsn(ARETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
/** inner 인스턴스를 추가한다 */
static void insertNewInstanceInner(ClassWriter cw, String classNameInternal, String enclosingClassNameInternal) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", null, null);
mv.visitCode();
if (enclosingClassNameInternal != null) {
mv.visitTypeInsn(NEW, classNameInternal);
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 1);
mv.visitTypeInsn(CHECKCAST, enclosingClassNameInternal);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
mv.visitInsn(POP);
mv.visitMethodInsn(INVOKESPECIAL, classNameInternal, "<init>", "(L" + enclosingClassNameInternal + ";)V");
mv.visitInsn(ARETURN);
mv.visitMaxs(4, 2);
} else {
mv.visitTypeInsn(NEW, "java/lang/UnsupportedOperationException");
mv.visitInsn(DUP);
mv.visitLdcInsn("Not an inner class.");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
mv.visitMaxs(3, 2);
}
mv.visitEnd();
}
}