All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.grails.io.support.MainClassFinder.groovy Maven / Gradle / Ivy

package org.grails.io.support

import grails.util.BuildSettings
import groovy.transform.CompileStatic
import groovy.transform.Memoized
import groovyjarjarasm.asm.ClassReader
import groovyjarjarasm.asm.ClassVisitor
import groovyjarjarasm.asm.MethodVisitor
import groovyjarjarasm.asm.Opcodes
import groovyjarjarasm.asm.Type

import java.nio.file.Paths
import java.util.concurrent.ConcurrentHashMap

/**
 * @author Graeme Rocher
 * @since 3.0
 */
@CompileStatic
class MainClassFinder {

    private static final Type STRING_ARRAY_TYPE = Type.getType(String[].class)

    private static final Type MAIN_METHOD_TYPE = Type.getMethodType(Type.VOID_TYPE, STRING_ARRAY_TYPE)

    private static final String MAIN_METHOD_NAME = "main"

    static final Map mainClasses = new ConcurrentHashMap<>()

    /**
     * Searches for the main class relative to the give path that is within the project tree
     *
     * @param path The path as a URI
     * @return The name of the main class
     */
    static String searchMainClass(URI path) {

        def pathStr = path.toString()
        if(mainClasses.containsKey(pathStr)) {
            return mainClasses.get(pathStr)
        }

        try {
            File file = path ? Paths.get(path).toFile() : null
            def rootDir = findRootDirectory(file)

            def classesDir = BuildSettings.CLASSES_DIR
            Collection searchDirs
            if (classesDir == null) {
                searchDirs = []
            } else {
                searchDirs = [classesDir]
            }

            if(rootDir) {
                def rootClassesDir = new File(rootDir, BuildSettings.BUILD_CLASSES_PATH)
                if(rootClassesDir.exists()) {
                    searchDirs << rootClassesDir
                }
            }

            String mainClass = null

            for (File dir in searchDirs) {
                mainClass = findMainClass(dir)
                if (mainClass) break
            }
            if(mainClass != null) {
                mainClasses.put(pathStr, mainClass)
            }
            return mainClass
        } catch (Throwable e) {
            return null
        }
    }

    private static File findRootDirectory(File file) {
        if(file) {
            def parent = file.parentFile

            while(parent != null) {
                if(new File(parent, "build.gradle").exists() || new File(parent, "grails-app").exists()) {
                    return parent
                }
                else {
                    parent = parent.parentFile
                }
            }
        }
        return null
    }

    static String findMainClass(File rootFolder = BuildSettings.CLASSES_DIR) {
        if( rootFolder == null) {
            // try current directory
            rootFolder = new File("build/classes/main")
        }


        def rootFolderPath = rootFolder.canonicalPath
        if(mainClasses.containsKey(rootFolderPath)) {
            return mainClasses.get(rootFolderPath)
        }

        if (!rootFolder.exists()) {
            return null // nothing to do
        }
        if (!rootFolder.isDirectory()) {
            throw new IllegalArgumentException("Invalid root folder '$rootFolder'")
        }
        String prefix =  "${rootFolderPath}/"
        def stack = new ArrayDeque()
        stack.push rootFolder

        while (!stack.empty) {
            File file = stack.pop()
            if (file.isFile()) {
                InputStream inputStream = file.newInputStream()
                try {
                    def classReader = new ClassReader(inputStream)

                    if (isMainClass(classReader)) {
                        def mainClassName = classReader.getClassName().replace('/', '.').replace('\\', '.')
                        mainClasses.put(rootFolderPath, mainClassName)
                        return mainClassName
                    }
                } finally {
                    inputStream?.close()
                }
            }
            if (file.isDirectory()) {
                def files = file.listFiles()?.findAll { File f ->
                    (f.isDirectory() && !f.name.startsWith('.') && !f.hidden) ||
                            (f.isFile() && f.name.endsWith(GrailsResourceUtils.CLASS_EXTENSION))
                }

                if(files) {
                    for(File sub in files) {
                        stack.push(sub)
                    }
                }

            }
        }
        return null
    }


    protected static boolean isMainClass(ClassReader classReader) {
        if(classReader.superName?.startsWith('grails/boot/config/')) {
            def mainMethodFinder = new MainMethodFinder()
            classReader.accept(mainMethodFinder, ClassReader.SKIP_CODE)
            return mainMethodFinder.found
        }
        return false
    }

    @CompileStatic
    static class MainMethodFinder extends ClassVisitor  {

        boolean found = false

        MainMethodFinder() {
            super(Opcodes.ASM4)
        }

        @Override
        MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            if(!found) {
                if (isAccess(access, Opcodes.ACC_PUBLIC, Opcodes.ACC_STATIC)
                        && MAIN_METHOD_NAME.equals(name)
                        && MAIN_METHOD_TYPE.getDescriptor().equals(desc)) {


                    this.found = true
                }
            }
            return null
        }


        private boolean isAccess(int access, int... requiredOpsCodes) {
            return !requiredOpsCodes.any { int requiredOpsCode -> (access & requiredOpsCode) == 0 }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy