pkg/infrastructure/ast/ast_java/java_full_listener.go

Summary

Maintainability
A
2 hrs
Test Coverage
package ast_java

import (
    "github.com/antlr/antlr4/runtime/Go/antlr/v4"
    "github.com/modernizing/coca/languages/java"
    "github.com/modernizing/coca/pkg/domain/core_domain"
    "github.com/modernizing/coca/pkg/infrastructure/ast/ast_java/common_listener"
    "reflect"
    "strconv"
    "strings"
)

var imports []string
var clzs []string
var currentPkg string
var currentClz string
var fields []core_domain.CodeField
var methodCalls []core_domain.CodeCall
var currentType string

var mapFields = make(map[string]string)
var localVars = make(map[string]string)
var formalParameters = make(map[string]string)

var currentClzExtend = ""
var currentMethod core_domain.CodeFunction
var methodMap = make(map[string]core_domain.CodeFunction)
var creatorMethodMap = make(map[string]core_domain.CodeFunction)

var methodQueue []core_domain.CodeFunction
var classStringQueue []string

var identMap map[string]core_domain.CodeDataStruct
var isOverrideMethod = false

var classNodeQueue []core_domain.CodeDataStruct

var currentNode *core_domain.CodeDataStruct
var classNodes []core_domain.CodeDataStruct
var currentCreatorNode core_domain.CodeDataStruct
var fileName = ""
var hasEnterClass = false

func NewJavaFullListener(nodes map[string]core_domain.CodeDataStruct, file string) *JavaFullListener {
    identMap = nodes
    imports = nil
    fileName = file
    currentPkg = ""
    classNodes = nil
    currentNode = core_domain.NewDataStruct()
    classStringQueue = nil
    classNodeQueue = nil
    methodQueue = nil

    initClass()
    return &JavaFullListener{}
}

func initClass() {
    currentClz = ""
    currentClzExtend = ""
    currentMethod = core_domain.NewJMethod()
    currentNode.FunctionCalls = nil

    methodMap = make(map[string]core_domain.CodeFunction)
    methodCalls = nil
    fields = nil
    isOverrideMethod = false
}

type JavaFullListener struct {
    parser.BaseJavaParserListener
}

func (s *JavaFullListener) GetNodeInfo() []core_domain.CodeDataStruct {
    return classNodes
}

func (s *JavaFullListener) ExitClassBody(ctx *parser.ClassBodyContext) {
    hasEnterClass = false
    s.exitBody()
}

func (s *JavaFullListener) ExitInterfaceBody(ctx *parser.InterfaceBodyContext) {
    hasEnterClass = false
    s.exitBody()
}

func (s *JavaFullListener) exitBody() {
    if currentNode.NodeName != "" {
        currentNode.Fields = fields
        currentNode.FilePath = fileName
        currentNode.SetMethodFromMap(methodMap)
    }

    if currentType == "CreatorClass" {
        currentNode.SetMethodFromMap(creatorMethodMap)
        return
    }

    if currentNode.NodeName == "" {
        currentNode = core_domain.NewDataStruct()
        initClass()
        return
    }

    if currentNode.Type == "InnerStructures" && len(classNodeQueue) >= 1 {
        classNodeQueue[0].InnerStructures = append(currentNode.InnerStructures, *currentNode)
    } else {
        classNodes = append(classNodes, *currentNode)
    }

    if len(classNodeQueue) >= 1 {
        if len(classNodeQueue) == 1 {
            currentNode = &classNodeQueue[0]
        } else {
            classNodeQueue = classNodeQueue[0 : len(classNodeQueue)-1]
            currentNode = &classNodeQueue[len(classNodeQueue)-1]
        }
    } else {
        currentNode = core_domain.NewDataStruct()
    }

    initClass()
}

func (s *JavaFullListener) EnterPackageDeclaration(ctx *parser.PackageDeclarationContext) {
    currentNode.Package = ctx.QualifiedName().GetText()
    currentPkg = ctx.QualifiedName().GetText()
}

func (s *JavaFullListener) EnterImportDeclaration(ctx *parser.ImportDeclarationContext) {
    importText := ctx.QualifiedName().GetText()
    imports = append(imports, importText)
    currentNode.Imports = append(currentNode.Imports, core_domain.NewJImport(importText))
}

func (s *JavaFullListener) EnterClassDeclaration(ctx *parser.ClassDeclarationContext) {
    if currentNode.NodeName != "" {
        classNodeQueue = append(classNodeQueue, *currentNode)
        currentType = "InnerStructures"
    } else {
        currentType = "Class"
    }

    hasEnterClass = true
    currentClzExtend = ""
    if ctx.Identifier() != nil {
        currentClz = ctx.Identifier().GetText()
        currentNode.NodeName = currentClz
    }

    if ctx.EXTENDS() != nil {
        currentClzExtend = ctx.TypeType().GetText()
        currentNode.Extend = buildExtend(currentClzExtend)
    }

    if ctx.IMPLEMENTS() != nil {
        types := ctx.TypeList(0).(*parser.TypeListContext).AllTypeType()
        for _, typ := range types {
            currentNode.Implements = append(currentNode.Implements, buildImplement(typ.GetText()))
        }
    }

    currentNode.Type = currentType
    // TODO: 支持依赖注入
}

func (s *JavaFullListener) EnterInterfaceDeclaration(ctx *parser.InterfaceDeclarationContext) {
    hasEnterClass = true
    currentType = "Interface"
    currentNode.NodeName = ctx.Identifier().GetText()

    if ctx.EXTENDS() != nil {
        types := ctx.TypeList(0).(*parser.TypeListContext).AllTypeType()
        for _, typ := range types {
            currentNode.Extend = buildExtend(typ.GetText())
        }
    }

    currentNode.Type = currentType
}

func (s *JavaFullListener) EnterInterfaceBodyDeclaration(ctx *parser.InterfaceBodyDeclarationContext) {
    hasEnterClass = true
    for _, modifier := range ctx.AllModifier() {
        modifier := modifier.(*parser.ModifierContext).GetChild(0)
        if reflect.TypeOf(modifier.GetChild(0)).String() == "*parser.AnnotationContext" {
            annotationContext := modifier.GetChild(0).(*parser.AnnotationContext)
            common_listener.BuildAnnotation(annotationContext)
        }
    }
}

func (s *JavaFullListener) EnterInterfaceMethodDeclaration(ctx *parser.InterfaceMethodDeclarationContext) {
    bodyDecl := ctx.InterfaceCommonBodyDeclaration().(*parser.InterfaceCommonBodyDeclarationContext)
    name := bodyDecl.Identifier().GetText()
    typeType := bodyDecl.TypeTypeOrVoid().GetText()

    if reflect.TypeOf(ctx.GetParent().GetParent().GetChild(0)).String() == "*parser.ModifierContext" {
        common_listener.BuildAnnotationForMethod(ctx.GetParent().GetParent().GetChild(0).(*parser.ModifierContext), &currentMethod)
    }

    position := BuildPosition(ctx.BaseParserRuleContext, name)

    method := &core_domain.CodeFunction{Name: name, ReturnType: typeType, Position: position}
    updateMethod(method)
}

func (s *JavaFullListener) EnterFormalParameter(ctx *parser.FormalParameterContext) {
    formalParameters[ctx.VariableDeclaratorId().GetText()] = ctx.TypeType().GetText()
}

func (s *JavaFullListener) EnterFieldDeclaration(ctx *parser.FieldDeclarationContext) {
    declarators := ctx.VariableDeclarators()
    typeType := declarators.GetParent().GetChild(0).(*parser.TypeTypeContext)
    for _, declarator := range declarators.(*parser.VariableDeclaratorsContext).AllVariableDeclarator() {
        var typeCtx = BuildTypeCtxByIndex(typeType, nil, 0)
        if typeType.GetChildCount() > 1 {
            typeCtx = BuildTypeCtxByIndex(typeType, typeCtx, 1)
        }

        if typeCtx == nil {
            continue
        }

        typeTypeText := typeCtx.Identifier(0).GetText()
        value := declarator.(*parser.VariableDeclaratorContext).VariableDeclaratorId().(*parser.VariableDeclaratorIdContext).Identifier().GetText()
        mapFields[value] = typeTypeText
        field := core_domain.NewJField(typeTypeText, value, "")
        fields = append(fields, field)

        buildFieldCall(typeTypeText, ctx)
    }
}

func BuildTypeCtxByIndex(typeType *parser.TypeTypeContext, typeCtx *parser.ClassOrInterfaceTypeContext, index int) *parser.ClassOrInterfaceTypeContext {
    switch x := typeType.GetChild(index).(type) {
    case *parser.ClassOrInterfaceTypeContext:
        typeCtx = x
    }
    return typeCtx
}

func (s *JavaFullListener) EnterLocalVariableDeclaration(ctx *parser.LocalVariableDeclarationContext) {
    typ := ctx.GetChild(0).(antlr.ParseTree).GetText()
    if ctx.GetChild(1) != nil {
        if ctx.GetChild(1).GetChild(0) != nil && ctx.GetChild(1).GetChild(0).GetChild(0) != nil {
            variableName := ctx.GetChild(1).GetChild(0).GetChild(0).(antlr.ParseTree).GetText()
            localVars[variableName] = typ
        }
    }
}

func (s *JavaFullListener) EnterAnnotation(ctx *parser.AnnotationContext) {
    // Todo: support override method
    if ctx.QualifiedName() == nil {
        return
    }

    annotationName := ctx.QualifiedName().GetText()
    if annotationName == "Override" {
        isOverrideMethod = true
    } else {
        isOverrideMethod = false
    }

    if !hasEnterClass {
        annotation := common_listener.BuildAnnotation(ctx)
        if currentType == "CreatorClass" {
            currentCreatorNode.Annotations = append(currentCreatorNode.Annotations, annotation)
        } else {
            currentNode.Annotations = append(currentNode.Annotations, annotation)
        }
    }
}

func (s *JavaFullListener) EnterConstructorDeclaration(ctx *parser.ConstructorDeclarationContext) {
    name := ctx.Identifier().GetText()
    position := BuildPosition(ctx.BaseParserRuleContext, name)

    method := &core_domain.CodeFunction{
        Name:          name,
        ReturnType:    "",
        Override:      isOverrideMethod,
        Parameters:    nil,
        Annotations:   currentMethod.Annotations,
        IsConstructor: true,
        Position:      position,
    }

    parameters := ctx.FormalParameters()
    if buildMethodParameters(parameters, method) {
        return
    }

    updateMethod(method)
}

func (s *JavaFullListener) ExitConstructorDeclaration(ctx *parser.ConstructorDeclarationContext) {
    currentMethod = core_domain.NewJMethod()
    isOverrideMethod = false
}

func (s *JavaFullListener) EnterMethodDeclaration(ctx *parser.MethodDeclarationContext) {
    name := ""

    if ctx.Identifier() != nil {
        name = ctx.Identifier().GetText()
    }
    typeType := ctx.TypeTypeOrVoid().GetText()

    if reflect.TypeOf(ctx.GetParent().GetParent().GetChild(0)).String() == "*parser.ModifierContext" {
        common_listener.BuildAnnotationForMethod(ctx.GetParent().GetParent().GetChild(0).(*parser.ModifierContext), &currentMethod)
    }

    // check, before your refactor
    position := core_domain.CodePosition{
        StartLine:         ctx.GetStart().GetLine(),
        StartLinePosition: ctx.Identifier().GetStart().GetColumn(), // different
        StopLine:          ctx.GetStop().GetLine(),
        StopLinePosition:  ctx.Identifier().GetStart().GetColumn() + len(name),
    }

    method := &core_domain.CodeFunction{
        Name:            name,
        ReturnType:      typeType,
        Annotations:     currentMethod.Annotations,
        Override:        isOverrideMethod,
        Parameters:      nil,
        InnerStructures: nil,
        Position:        position,
    }

    parameters := ctx.FormalParameters()
    if buildMethodParameters(parameters, method) {
        return
    }

    updateMethod(method)
}

func buildMethodParameters(parameters parser.IFormalParametersContext, method *core_domain.CodeFunction) bool {
    if parameters != nil {
        if parameters.GetChild(0) == nil || parameters.GetText() == "()" || parameters.GetChild(1) == nil {
            updateMethod(method)
            return true
        }

        method.Parameters = BuildMethodParameters(parameters)
        updateMethod(method)
    }
    return false
}

func updateMethod(method *core_domain.CodeFunction) {
    if currentType == "CreatorClass" {
        creatorMethodMap[getMethodMapName(*method)] = *method
    } else {
        currentMethod = *method
        methodQueue = append(methodQueue, *method)
        methodMap[getMethodMapName(*method)] = *method
    }
}

func (s *JavaFullListener) ExitMethodDeclaration(ctx *parser.MethodDeclarationContext) {
    exitMethod()
}

func exitMethod() {
    if currentType == "CreatorClass" {
        return
    }

    currentMethod = core_domain.NewJMethod()
}

// TODO: add inner creator examples
func (s *JavaFullListener) EnterInnerCreator(ctx *parser.InnerCreatorContext) {
    if ctx.Identifier() != nil {
        currentClz = ctx.Identifier().GetText()
        classStringQueue = append(classStringQueue, currentClz)
    }
}

// TODO: add inner creator examples
func (s *JavaFullListener) ExitInnerCreator(ctx *parser.InnerCreatorContext) {
    if classStringQueue == nil || len(classStringQueue) <= 1 {
        return
    }

    classStringQueue = classStringQueue[0 : len(classStringQueue)-1]
    currentClz = classStringQueue[len(classStringQueue)-1]
}

func getMethodMapName(method core_domain.CodeFunction) string {
    name := method.Name
    if name == "" && len(methodQueue) > 1 {
        name = methodQueue[len(methodQueue)-1].Name
    }
    return currentPkg + "." + currentClz + "." + name + ":" + strconv.Itoa(method.Position.StartLine)
}

func (s *JavaFullListener) EnterCreator(ctx *parser.CreatorContext) {
    variableName := ctx.GetParent().GetParent().GetChild(0).(antlr.ParseTree).GetText()
    allIdentifiers := ctx.CreatedName().(*parser.CreatedNameContext).AllIdentifier()

    for _, identifier := range allIdentifiers {
        createdName := identifier.GetText()
        localVars[variableName] = createdName

        buildCreatorCall(createdName, ctx)

        if currentMethod.Name == "" {
            return
        }

        if ctx.ClassCreatorRest() == nil {
            return
        }

        if ctx.ClassCreatorRest().(*parser.ClassCreatorRestContext).ClassBody() == nil {
            return
        }

        currentType = "CreatorClass"
        text := ctx.CreatedName().GetText()
        creatorNode := &core_domain.CodeDataStruct{
            Package:       currentPkg,
            NodeName:      text,
            Type:          "CreatorClass",
            FilePath:      "",
            Fields:        nil,
            Functions:     nil,
            FunctionCalls: nil,
            Extend:        "",
            Implements:    nil,
            Annotations:   nil,
        }

        currentCreatorNode = *creatorNode
    }
}

func (s *JavaFullListener) ExitCreator(ctx *parser.CreatorContext) {
    if currentCreatorNode.NodeName != "" {
        method := methodMap[getMethodMapName(currentMethod)]
        method.InnerStructures = append(method.InnerStructures, currentCreatorNode)
        methodMap[getMethodMapName(currentMethod)] = method
    }

    if currentType == "CreatorClass" {
        currentType = ""
    }
    currentCreatorNode = *core_domain.NewDataStruct()

    if classNodeQueue == nil || len(classNodeQueue) < 1 {
        return
    }
}

func buildCreatorCall(createdName string, ctx *parser.CreatorContext) {
    method := methodMap[getMethodMapName(currentMethod)]
    fullType, _ := WarpTargetFullType(createdName)

    position := BuildPosition(ctx.BaseParserRuleContext, createdName)

    jMethodCall := &core_domain.CodeCall{
        Package:  RemoveTarget(fullType),
        Type:     "CreatorClass",
        NodeName: createdName,
        Position: position,
    }

    method.FunctionCalls = append(method.FunctionCalls, *jMethodCall)
    methodMap[getMethodMapName(currentMethod)] = method
}

func BuildPosition(ctx *antlr.BaseParserRuleContext, nodeName string) core_domain.CodePosition {
    position := core_domain.CodePosition{
        StartLine:         ctx.GetStart().GetLine(),
        StartLinePosition: ctx.GetStart().GetColumn(),
        StopLine:          ctx.GetStop().GetLine(),
        StopLinePosition:  ctx.GetStop().GetColumn() + len(nodeName),
    }

    return position
}

func (s *JavaFullListener) EnterMethodCall(ctx *parser.MethodCallContext) {
    var jMethodCall = core_domain.NewCodeMethodCall()

    targetCtx := ctx.GetParent().GetChild(0).(antlr.ParseTree)
    var targetType = ParseTargetType(targetCtx.GetText())

    if targetCtx.GetChild(0) != nil {
        switch x := targetCtx.GetChild(0).(type) {
        case *parser.MethodCallContext:
            targetType = x.Identifier().GetText()
        }
    }

    callee := ctx.GetChild(0).(antlr.ParseTree).GetText()

    BuildMethodCallLocation(&jMethodCall, ctx, callee)
    BuildMethodCallMethod(&jMethodCall, callee, targetType, ctx)
    BuildMethodCallParameters(&jMethodCall, ctx)

    sendResultToMethodCallMap(jMethodCall)
}

func sendResultToMethodCallMap(jMethodCall core_domain.CodeCall) {
    methodCalls = append(methodCalls, jMethodCall)

    method := methodMap[getMethodMapName(currentMethod)]
    method.FunctionCalls = append(method.FunctionCalls, jMethodCall)
    methodMap[getMethodMapName(currentMethod)] = method
}

func isChainCall(targetType string) bool {
    return strings.Contains(targetType, "(") && strings.Contains(targetType, ")") && strings.Contains(targetType, ".")
}

func buildSelfThisTarget(targetType string) string {
    targetType = strings.ReplaceAll(targetType, "this.", "")
    for _, field := range fields {
        if field.TypeValue == targetType {
            targetType = field.TypeType
        }
    }

    return targetType
}

func (s *JavaFullListener) EnterExpression(ctx *parser.ExpressionContext) {
    // lambda BlogPO::of
    if ctx.COLONCOLON() != nil {
        if ctx.Expression(0) == nil {
            return
        }

        text := ctx.Expression(0).GetText()
        methodName := ctx.Identifier().GetText()
        targetType := ParseTargetType(text)

        fullType, _ := WarpTargetFullType(targetType)

        position := BuildPosition(ctx.BaseParserRuleContext, text)

        jMethodCall := &core_domain.CodeCall{
            Package:      RemoveTarget(fullType),
            Type:         "lambda",
            NodeName:     targetType,
            FunctionName: methodName,
            Position:     position,
        }
        sendResultToMethodCallMap(*jMethodCall)
    }
}

func (s *JavaFullListener) AppendClasses(classes []string) {
    clzs = classes
}

func buildExtend(extendName string) string {
    var extend = extendName
    target, _ := WarpTargetFullType(extendName)
    if target != "" {
        extend = target
    }

    return extend
}

func buildFieldCall(typeType string, ctx *parser.FieldDeclarationContext) {
    target, _ := WarpTargetFullType(typeType)
    if target != "" {
        position := BuildPosition(ctx.BaseParserRuleContext, target)

        jMethodCall := &core_domain.CodeCall{
            Package:  RemoveTarget(target),
            Type:     "field",
            NodeName: typeType,
            Position: position,
        }

        currentNode.FunctionCalls = append(currentNode.FunctionCalls, *jMethodCall)
    }
}

func buildImplement(text string) string {
    target, _ := WarpTargetFullType(text)
    return target
}