2020년 3월 29일 일요일

KOTLIN 에서 AspectJ



https://github.com/Ibotta/gradle-aspectj-pipeline-plugin/blob/develop/sample-kotlin/src/main/kotlin/com/ibotta/gradle/aop/kotlin/KotlinAspect.kt
여기를 참고해서 설정하고.

@Aspect  
class KotlinAspect {  
    @Before("execution(* com.sugoigroup.mytvapplication*..*(..))")  
    fun before(joinPoint: JoinPoint) {  
        val methodSignature = joinPoint!!.signature as MethodSignature  
        val methodName = methodSignature.name  
  val startTime = System.currentTimeMillis();  
        val result = (joinPoint as ProceedingJoinPoint).proceed()  
        val endTime = System.currentTimeMillis() - startTime  
        Log.e("LoggingVM", "$methodName ---> $endTime")  
    }  
  
    @After("execution(* demonstrateKotlinAOP(..))")  
    fun after(joinPoint: JoinPoint) {  
  
    }  
}
또는




import android.graphics.Bitmap
import android.os.Debug
import android.os.Process
import android.util.Log
import android.view.View
import androidx.annotation.NonNull
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.After
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.aspectj.lang.annotation.Pointcut
import org.aspectj.lang.reflect.MethodSignature
import java.util.Random
import java.util.concurrent.TimeUnit

@Aspect
class KotlinAspect {

private var lastClassMethod = "Start"
private val logType1 = 1
private var argAll: String? = null
@Pointcut(POINTCUT_BEFORE_METHOD) fun pointcutDebugTraceBefore() {}
@Pointcut(POINTCUT_AROUND_METHOD) fun pointcutDebugTraceAround() {}

@Before("pointcutDebugTraceBefore()")
@Throws(Throwable::class)
fun weaveDebugTraceBefore(
joinPoint: JoinPoint
) {
val methodSignature = joinPoint.signature as MethodSignature
val currentClassName = utils.getOnlyClassName(methodSignature.declaringType.simpleName)
val currentMethodName = utils.getOnlyClassName(methodSignature.name)
//not use
argAll = utils.getArgs(joinPoint, methodSignature)
val tid = Process.myTid()
val dummyDepthText = utils.getdepthTexts(tid)
lastClassMethod = utils.getLastClassMethod(lastClassMethod, dummyDepthText, logType1)
if (utils.getdepthCounts(tid) < 1) {
lastClassMethod = "Start"
}
LogPrinter.logBefore(currentClassName, currentMethodName, lastClassMethod, tid)
LogPrinter.logBeforeArg(lastClassMethod, tid, argAll)
printTo(
type2PrintTid(tid) + ASPECT_LOG_SEQUENCE_TYPE,
utils.repeat(
verticalLine,
utils.getdepthCounts(tid) + 1
) + "▼" + "(" + joinPoint.signature.declaringType + ") "
)
printTo(
type2PrintTid(tid) + ASPECT_LOG_SEQUENCE_TYPE,
utils.repeat(verticalLine, utils.getdepthCounts(tid) + 1) + "┏ " + currentMethodName
)
dummyDepthText.add(currentClassName)
}


@Around("pointcutDebugTraceAround()")
@Throws(Throwable::class)
fun weaveDebugTraceAround(
joinPoint: ProceedingJoinPoint
):Any?{
val methodSignature = joinPoint.signature as MethodSignature
val currentClassName = utils.getOnlyClassName(methodSignature.declaringType.simpleName)
val currentMethodName = utils.getOnlyClassName(methodSignature.name)
val tid = Process.myTid()
val dummyDepthText = utils.getdepthTexts(tid)
depthCounts[tid] = utils.getdepthCounts(tid) + 1
LogPrinter.logAroundBefore(currentClassName, tid)
if (argAll!!.length > 0) {
printTo(
type2PrintTid(tid) + ASPECT_LOG_SEQUENCE_TYPE,
utils.repeat(verticalLine, utils.getdepthCounts(tid) + 1) + argAll
)
}
val stopWatch = StopWatch()
stopWatch.start()
val result = joinPoint.proceed()
stopWatch.stop()
LogPrinter.logAroundAfter(
currentClassName,
currentMethodName,
dummyDepthText,
stopWatch.totalTimeMillis,
tid,
result
)
printTo(
type2PrintTid(tid) + ASPECT_LOG_SEQUENCE_TYPE,
utils.repeat(verticalLine, utils.getdepthCounts(tid) + 1) + "result " + result
)
printTo(
type2PrintTid(tid) + ASPECT_LOG_SEQUENCE_TYPE,
utils.repeat(
verticalLine,
utils.getdepthCounts(tid)
) +
"┗ " + currentMethodName +
" : " + stopWatch.totalTimeMillis + "ms"
)

//after process
depthCounts[tid] = utils.getdepthCounts(tid) - 1
if (dummyDepthText.size > 0) {
dummyDepthText.removeAt(dummyDepthText.size - 1)
}
depthTexts[tid] = dummyDepthText

return result
}

@After("execution(* demonstrateKotlinAOP(..))")
fun after(joinPoint: JoinPoint) {

}

/**
* Class representing a StopWatch for measuring time.
*/
inner class StopWatch() {
private var startTime: Long = 0
private var endTime: Long = 0
private var elapsedTime: Long = 0
private fun reset() {
startTime = 0
endTime = 0
elapsedTime = 0
}

fun start() {
reset()
startTime = System.nanoTime()
}

fun stop() {
if (startTime != 0L) {
endTime = System.nanoTime()
elapsedTime = endTime - startTime
} else {
reset()
}
}

val totalTimeMillis: Long
get() = if ((elapsedTime != 0L)) TimeUnit.NANOSECONDS.toMillis(endTime - startTime) else 0
}

object LogPrinter {
fun logBefore(
className: String,
methodName: String,
lastClassMethod: String,
tid: Int
) {

//hidden for UML
printTo(
ASPECT_LOG_PLANTUML_TYPE,
type2PrintTidForUml(tid) + lastClassMethod + umlWithColorBlock(tid) + "->" + type2PrintTidForUml(
tid
) + className + " : " + methodName + "#" + tid
)
}

fun logBeforeArg(className: String, tid: Int, args: String?) {
// note right of Alice
// Alice <u:blue></u>側に
// <back:cadetblue>表示</back>するノート
// end note
if (args!!.isEmpty()) return
printTo(
ASPECT_LOG_PLANTUML_TYPE,
"note right of " + type2PrintTidForUml(tid) + className + "\n " + args + "\n end note"
)
}

fun logAfterResult(className: String, tid: Int, result: Any?) {
// note right of Alice
// Alice <u:blue></u>側に
// <back:cadetblue>表示</back>するノート
// end note
if (result == null) return
printTo(
ASPECT_LOG_PLANTUML_TYPE,
"note right of " + type2PrintTidForUml(tid) + className + "\n result :" + result + "\n end note"
)
}

private fun getProcessColor(tid: Int): Int {
if (!processColor.containsKey(tid)) {
val r = Random()
processColor[tid] = r.nextInt(0xffffff + 1)
}
return (processColor[tid])!!
}

private fun umlWithColor(tid: Int): String {
return String.format("#%06x", getProcessColor(tid))
}

private fun umlWithColorBlock(tid: Int): String {
return "[" + umlWithColor(tid) + "]"
}

fun logAroundBefore(className: String, tid: Int) {
//hidden for UML
printTo(
ASPECT_LOG_PLANTUML_TYPE,
ACTIVATE + type2PrintTidForUml(tid) + className + " " + umlWithColor(tid)
)
}

fun logAroundAfter(
className: String,
methodName: String,
depthText: List<String>,
estimate: Long,
tid: Int,
result: Any?
) {
var returnClassName = START
if (depthText.size > 1) {
returnClassName = depthText[depthText.size - 2]
}
//hidden for UML
//new but not goof relation 420,423 printTo(ASPECT_LOG_PLANTUML_TYPE, "return " +type2PrintTidForUml(tid) + methodName + " (" + estimate + "ms)"); //### weaveDebugTraceBefore:
val nativeMax: Long = Debug.getNativeHeapSize() / 1024
val nativeAllocated: Long = Debug.getNativeHeapAllocatedSize() / 1024
val nativeFree: Long = Debug.getNativeHeapFreeSize() / 1024
printTo(
ASPECT_LOG_PLANTUML_TYPE,
(type2PrintTidForUml(tid) + className + umlWithColorBlock(tid)
+ "-->" + type2PrintTidForUml(tid) + returnClassName + " : " + methodName + "#" + tid + " (" + estimate + "ms," + nativeMax + "MB," + nativeAllocated + "MB," + nativeFree + "MB)")
) //### weaveDebugTraceBefore:
logAfterResult(className, tid, result)
//hidden for UML
printTo(
ASPECT_LOG_PLANTUML_TYPE,
DEACTIVATE + type2PrintTidForUml(tid) + className + " " + umlWithColor(tid)
)
}
}

object utils {
@Synchronized fun repeat(string: String?, times: Int): String {
var times = times
val out = StringBuilder()
while (times-- > 0) {
out.append(string)
}
return out.toString()
}

fun getOnlyClassName(classname: String): String {
if (classname.indexOf("$") < 1) {
return isEmptyName(classname)
}
val str = classname.split("\\\\$").dropLastWhile { it.isEmpty() }
.toTypedArray()
return isEmptyName(str[0])
}

fun getLastClassMethod(
lastClassMethod: String,
dummyDepthText: List<String>,
logType: Int
): String {
return if (dummyDepthText.size > 0) {
isEmptyName(dummyDepthText.get(dummyDepthText.size - logType))
} else lastClassMethod
}

fun getdepthTexts(tid: Int): MutableList<String> {
if (!depthTexts.containsKey(tid)) {
depthTexts[tid] = ArrayList()
}
return (depthTexts[tid])!!
}

fun getdepthCounts(tid: Int): Int {
if (!depthCounts.containsKey(tid)) {
depthCounts[tid] = 0
}
return (depthCounts[tid])!!
}

private fun isEmptyName(methodName: String): String {
return if (methodName.length < 2) {
"NULL"
} else methodName
}

fun getArgs(joinPoint: JoinPoint, methodSignature: MethodSignature): String {
val objArray = joinPoint.args
val sigParamNames = methodSignature.parameterNames
var i = 0
var argName = ""
val argAll = StringBuilder()
for (obj: Any? in objArray) {
if (obj == null) continue
argName = sigParamNames[i]
i++
argAll.append("$argName:[$obj] , ")
}
if (argAll.length > 1) {
argAll.insert(0, "args ")
}
return argAll.toString()
}
}


// companiton


companion object {
//Line형태의 시퀀스 로그
private val ASPECT_LOG_SEQUENCE_TYPE = "ALOGSEQ"

//PlantUML에 이용하기 위한 로그
private val ASPECT_LOG_PLANTUML_TYPE = "ALOGUML"

//버퍼를 모아서 디비에 출력하기용.
private var logBufferCount = 1

//Limit 만큼 카운트가 되면 디비에 저장.
private val logBufferStoreCountLimit = 3000

//버퍼를 모아서 디비에 출력하기용. 프로비저닝 등의 작업에서는 로그출력이 불가능하니까 사용
private var logBufferMessages = ""
private val depthTexts: HashMap<Int, MutableList<String>> = HashMap()
private val depthCounts: HashMap<Int, Int> = HashMap()
private val processColor: HashMap<Int, Int> = HashMap()
private val verticalLine = "│"
private val START = "Start"
private val ACTIVATE = "activate "
private val DEACTIVATE = "deactivate "

private const val POINTCUT_BEFORE_METHOD = ("execution(* com.sugoigroup.mytvapplication*..*(..))")
private const val POINTCUT_AROUND_METHOD = POINTCUT_BEFORE_METHOD
@NonNull fun type2PrintTid(tid: Int): String {
return "$tid: p-"
}

@NonNull fun type2PrintTidForUml(tid: Int): String {
return ""
}

/* @Around("(execution(* android.app.Activity.onResume(..)) || execution(* android.app.Activity.onCreate(..)) || execution(* show(..)) )")
public Object postonResume(ProceedingJoinPoint joinPoint) throws Throwable {
final Activity activity = (Activity) joinPoint.getTarget();
final View rootView = activity.getWindow().getDecorView().getRootView();


final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
storeScreenshot(takescreenshotOfRootView(rootView), Calendar.getInstance().getTimeInMillis() + "-" + activity.getLocalClassName() );
}
}, 500);
Object result = joinPoint.proceed();
return result;
}*/
fun takescreenshot(v: View): Bitmap {
v.setDrawingCacheEnabled(true)
v.buildDrawingCache(true)
val b: Bitmap = Bitmap.createBitmap(v.getDrawingCache(true))
v.setDrawingCacheEnabled(false)
return b
}

fun takescreenshotOfRootView(v: View): Bitmap {
return takescreenshot(v.getRootView())
}

fun printTo(category: String, message: String) {
Log.d(category, message)
if (category != ASPECT_LOG_PLANTUML_TYPE) return
Log.d(category, message)
//--to db
if (logBufferCount < logBufferStoreCountLimit) {
logBufferCount++
logBufferMessages += "$message\\n"
return
}
// try {
// if (MainApp.getInstance() == null) {
// return
// }
// } catch (e: Exception) {
// return
// }
// if (OptimalAgentApplication.TestLogController.logHelper != null) {
// OptimalAgentApplication.TestLogController.insert("$category // $message")
// }
logBufferCount = 1
logBufferMessages = ""
}
}
}