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

org.nuiton.jaxx.compiler.reflect.resolvers.ClassDescriptorResolverFromJavaFile Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * JAXX :: Compiler
 * %%
 * Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */
package org.nuiton.jaxx.compiler.reflect.resolvers;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.nuiton.jaxx.compiler.CompilerException;
import org.nuiton.jaxx.compiler.JAXXCompiler;
import org.nuiton.jaxx.compiler.JAXXFactory;
import org.nuiton.jaxx.compiler.java.parser.JavaParser;
import org.nuiton.jaxx.compiler.java.parser.JavaParserTreeConstants;
import org.nuiton.jaxx.compiler.java.parser.ParseException;
import org.nuiton.jaxx.compiler.java.parser.SimpleNode;
import org.nuiton.jaxx.compiler.reflect.ClassDescriptor;
import org.nuiton.jaxx.compiler.reflect.ClassDescriptorHelper;
import org.nuiton.jaxx.compiler.reflect.ClassDescriptorResolver;
import org.nuiton.jaxx.compiler.reflect.FieldDescriptor;
import org.nuiton.jaxx.compiler.reflect.MethodDescriptor;
import org.nuiton.jaxx.compiler.tags.TagManager;
import org.nuiton.jaxx.runtime.JAXXUtil;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * To obtain a class descriptor from a java source file.
 *
 * @author Tony Chemit - [email protected]
 * @since 2.0.2
 */
public class ClassDescriptorResolverFromJavaFile extends ClassDescriptorResolver {

    private static final Logger log = LogManager.getLogger(ClassDescriptorResolverFromJavaFile.class);

    private final boolean parseMethodBody;


    public ClassDescriptorResolverFromJavaFile() {
        this(false);
    }

    public ClassDescriptorResolverFromJavaFile(boolean parseMethodBody) {
        super(ClassDescriptorHelper.ResolverType.JAVA_FILE);
        this.parseMethodBody = parseMethodBody;
    }

    @Override
    public ClassDescriptor resolveDescriptor(String className, URL source) throws ClassNotFoundException {
        ClassLoader classLoader = getClassLoader();
        try {
            try (Reader reader = new InputStreamReader(source.openStream(), StandardCharsets.UTF_8)) {

                String displayName = source.toString();
                log.debug("for source " + displayName);
                JavaFileParser parser = new JavaFileParser(classLoader, parseMethodBody);
                log.debug("starting parsing : " + displayName);
                try {
                    parser.doParse(displayName, reader);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                return new JavaFileClassDescriptor(parser, classLoader);
            }
        } catch (IOException e) {
            throw new ClassNotFoundException("Could not resolve descriptor from source " + source, e);
        }
    }

    private class JavaFileClassDescriptor extends ClassDescriptor {
        private Map typeParameters;
        public JavaFileClassDescriptor(JavaFileParser parser, ClassLoader classLoader) {
            super(
                    ClassDescriptorResolverFromJavaFile.this.getResolverType(),
                    parser.className,
                    parser.packageName,
                    parser.superclass,
                    parser.interfaces.toArray(new String[0]),
                    parser.isInterface,
                    false,
                    null,
                    parser.jaxxObjectDescriptorValue == null ? null : JAXXUtil.decodeCompressedJAXXObjectDescriptor(parser.jaxxObjectDescriptorValue),
                    classLoader,
                    parser.constructors.toArray(new MethodDescriptor[0]),
                    parser.methods.toArray(new MethodDescriptor[0]),
                    parser.fields.toArray(new FieldDescriptor[0]),
                    parser.declaredFields.toArray(new FieldDescriptor[0])
            );
            typeParameters = parser.typeParameters;

        }

        @Override
        public Map getTypeParameters() {
            return typeParameters;
        }

        @Override
        public Optional tryToGetDeclaredMethodDescriptor(String name, ClassDescriptor... parameterTypes) {
            return Optional.empty();
        }

        @Override
        public MethodDescriptor getDeclaredMethodDescriptor(String name, ClassDescriptor... parameterTypes) throws NoSuchMethodException {
            throw new NoSuchMethodException(name);
        }

    }

    public static class JavaFileParser {

        /**
         * the compiler used (this is a dummy compiler with no link with any file).
         *
         * FIXME-TC20100504 We should remove this link : should not need of a
         * compiler to parse a java files.
         */
        private final JAXXCompiler compiler;

        /** fully qualified name of the class */
        private String className;

        /** package of the class */
        private String packageName;

        /** fully qualified name of the super class or {@code null} if no super class. */
        private String superclass;

        /**
         * flag to known if we deal with an enum (the state is setted in the
         * {@link #doParse(String, Reader)} method).
         */
        private boolean isEnum;

        /**
         * flag to known if we deal with an interface(the state is setted in the
         * {@link #doParse(String, Reader)} method).
         */
        private boolean isInterface;

        /** set of fully qualified names of interfaces of the class. */
        private final Set interfaces;

        /** public methods of the class */
        private final List methods;

        private final List constructors;

        /** public fields of the class */
        private final List fields;

        /** none public fields of the class */
        private final List declaredFields;

        /**
         * If sets, compressed value of the $jaxxObjectDescriptor field, this means
         * the class if a JAXXObject implementation.
         */
        private String jaxxObjectDescriptorValue;

        private final boolean parseMethodBody;

        /**
         * To test if a compilation unit was already parsed. If so, then stop
         * parsing the java file : one parsing can only give one class
         * descriptor.
         *
         * @since 2.4.2
         */
        private boolean firstTypeScanned;

        protected JavaFileParser(ClassLoader classLoader, boolean parseMethodBody) {
            this.parseMethodBody = parseMethodBody;
            //FIXME-TC-20100504 : should remove this to make the parser free of jaxx :)
            // We could imagine just to offers to the parser a list of namespaces (for class resolving)...
            compiler = JAXXFactory.newDummyCompiler(classLoader);
            methods = new ArrayList<>();
            constructors = new ArrayList<>();
            interfaces = new HashSet<>();
            fields = new ArrayList<>();
            declaredFields = new ArrayList<>();
            superclass = Object.class.getName();
        }


        public void doParse(String className, Reader src) throws ClassNotFoundException {

            // reset this internal state
            firstTypeScanned = false;

            try {
                JavaParser p = new JavaParser(src, parseMethodBody);
                p.CompilationUnit();
                SimpleNode node = p.popNode();
                if (node != null) {
                    scanCompilationUnit(node);
                    if (isInterface) {

                        // remove super class
                        superclass = null;

                        // load all super classes
                        if (!interfaces.isEmpty()) {
                            for (String anInterface : interfaces) {
                                ClassDescriptor superclassDescriptor = ClassDescriptorHelper.getClassDescriptor(anInterface, compiler.getClassLoader());
                                methods.addAll(Arrays.asList(superclassDescriptor.getMethodDescriptors()));
                                fields.addAll(Arrays.asList(superclassDescriptor.getFieldDescriptors()));
                            }
                        }
                    }

                    if (isEnum) {

                        // super class is always Enum

                        superclass = Enum.class.getName();
                    }
                    if (superclass != null) {
                        //FIXME-TC20100504 This is not good, should add nothing here
                        // and modify the algorithm of ClassDescriptor to go and seek
                        // in super classes on interfaces if required.

                        ClassDescriptor superclassDescriptor = ClassDescriptorHelper.getClassDescriptor(superclass, compiler.getClassLoader());
                        methods.addAll(Arrays.asList(superclassDescriptor.getMethodDescriptors()));
                        fields.addAll(Arrays.asList(superclassDescriptor.getFieldDescriptors()));
                        declaredFields.addAll(Arrays.asList(superclassDescriptor.getDeclaredFieldDescriptors()));
                    }
                    return;
                }
                throw new CompilerException("Internal error: null node parsing Java file from " + src);
            } catch (ParseException e) {
                log.error(e);
                throw new CompilerException("Error parsing Java source code " + className + ": " + e.getMessage());
            }
        }

        private void scanCompilationUnit(SimpleNode node) {
            for (int i = 0; i < node.jjtGetNumChildren(); i++) {
                SimpleNode child = node.getChild(i);
                scanCompilationUnitChild(child);
            }
        }

        private void scanCompilationUnitChild(SimpleNode child) {
            if (firstTypeScanned) {

                // already scan the first class of the java file
                // for the moment, can not do anything else...
                log.warn("There is more than one type in current file, skip next type...");
                return;
            }
            int nodeType = child.getId();
            switch (nodeType) {
                case JavaParserTreeConstants.JJTPACKAGEDECLARATION:
                    packageName = child.getChild(1).getText().trim();
                    compiler.addImport(packageName + ".*");

                    // add implicit java.lang namespace available
                    compiler.addImport("java.lang.*");
                    break;
                case JavaParserTreeConstants.JJTIMPORTDECLARATION:
                    String text = child.getText().trim();
                    int importIndex = text.indexOf("import");
                    if (importIndex > -1) {
                        text = text.substring(importIndex + "import".length()).trim();
                    }
                    if (text.endsWith(";")) {
                        text = text.substring(0, text.length() - 1);
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("import " + text);
                    }
                    compiler.addImport(text);
                    break;
                case JavaParserTreeConstants.JJTTYPEDECLARATION:
                    scanCompilationUnit(child);
                    break;

                case JavaParserTreeConstants.JJTCLASSORINTERFACEDECLARATION:
                    isInterface = child.firstToken.image.equals("interface");
                    scanClass(child);
                    break;
                case JavaParserTreeConstants.JJTENUMDECLARATION:
                    isEnum = child.firstToken.image.equals("enum");
                    scanClass(child);
                    break;
            }
        }

        // scans the main ClassOrInterfaceDeclaration

        Map typeParameters = new LinkedHashMap<>();
        private void scanClass(SimpleNode node) {
            firstTypeScanned = true;
//        boolean isInterface = node.firstToken.image.equals("interface");
            className = node.firstToken.next.image;
            if (packageName != null) {
                className = packageName + "." + className;
            }
            for (int i = 0; i < node.jjtGetNumChildren(); i++) {
                SimpleNode child = node.getChild(i);
                int nodeType = child.getId();
                if (nodeType == JavaParserTreeConstants.JJTTYPEPARAMETERS) {
                    for (int j = 0; j < child.jjtGetNumChildren(); j++) {
                        SimpleNode typeChild = child.getChild(j);
                        String typeTypeName = typeChild.firstToken.next.next.toString();
                        typeParameters.put(typeChild.firstToken.toString(), TagManager.resolveClassName(typeTypeName, compiler));
                    }
                    continue;
                }
                if (nodeType == JavaParserTreeConstants.JJTIMPLEMENTSLIST) {
                    log.debug("[" + className + "] Found a implements list " + child + " :: " + child.jjtGetNumChildren());
                    // obtain interfaces
                    for (int j = 0; j < child.jjtGetNumChildren(); j++) {
                        String rawName = child.getChild(j).getText().trim();
                        addInterface(rawName);
                    }
                    continue;
                }
                if (nodeType == JavaParserTreeConstants.JJTEXTENDSLIST) {
                    if (isInterface) {
                        // obtain interfaces
                        for (int j = 0; j < child.jjtGetNumChildren(); j++) {
                            String rawName = child.getChild(j).getText().trim();
                            addInterface(rawName);
                        }
                        continue;
                    }
                    // this is an extends
                    assert child.jjtGetNumChildren() == 1 : "expected ExtendsList to have exactly one child for a non-interface class";
                    String rawName = child.getChild(0).getText().trim();
                    superclass = TagManager.resolveClassName(rawName, compiler);
                    if (superclass == null) {
                        throw new CompilerException("Could not find class: " + rawName);
                    }
                    log.debug("Set superClass = " + superclass);
                } else if (nodeType == JavaParserTreeConstants.JJTCLASSORINTERFACEBODY) {
                    scanClassNode(child);
                }
            }
        }

        // scans class body nodes

        private void scanClassNode(SimpleNode node) {
            int nodeType = node.getId();

            switch (nodeType) {
                case JavaParserTreeConstants.JJTCLASSORINTERFACEDECLARATION:
                    // TODO: handle inner classes
                    break;
                case JavaParserTreeConstants.JJTCONSTRUCTORDECLARATION:
                    scanConstructorDeclaration(node);
                    break;
                case JavaParserTreeConstants.JJTMETHODDECLARATION:
                    scanMethodDeclaration(node);
                    break;
                case JavaParserTreeConstants.JJTFIELDDECLARATION:
                    scanFieldDeclaration(node);
                    break;
                default:
                    // go thought it
                    for (int i = 0; i < node.jjtGetNumChildren(); i++) {
                        SimpleNode child = node.getChild(i);
                        scanClassNode(child);
                    }
            }
        }


        protected void scanFieldDeclaration(SimpleNode node) {
            String text = node.getText();
            String declaration = text;
            String value = null;
            int equals = text.indexOf("=");
            if (equals != -1) {
                value = declaration.substring(equals + 1).trim();
                declaration = declaration.substring(0, equals);
            }
            declaration = declaration.trim();
            // get modifiers of the field
            int modifiers;
            if (isInterface) {
                modifiers = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL;
            } else {
                modifiers = getModifiers(node);
            }
            log.debug(String.format("field [%s] modifiers == %s", declaration, Modifier.toString(modifiers)));
            int lastIndexSpace = declaration.trim().lastIndexOf(" ");

            String name = declaration.substring(lastIndexSpace).trim();
            if (name.endsWith(";")) {
                name = name.substring(0, name.length() - 1).trim();
            }
            String cName = declaration.substring(0, lastIndexSpace).trim();
            String type = getType(cName);

            FieldDescriptor descriptor = new FieldDescriptor(
                    name,
                    modifiers,
                    type,
                    compiler.getClassLoader());

            if ("$jaxxObjectDescriptor".equals(name) && value != null) {
                // we are in a jaxx object, save the value of the field
                // value have this form : = "XXX";, we just want the XXX part
                int firstIndex = value.indexOf("\"");
                int lastIndex = value.lastIndexOf("\"");
                jaxxObjectDescriptorValue =
                        value.substring(firstIndex + 1, lastIndex);
                log.debug("detected a $jaxxObjectDescriptor = " + jaxxObjectDescriptorValue);
            }
            if (Modifier.isPublic(modifiers)) {
                fields.add(descriptor);
            } else {
                declaredFields.add(descriptor);
            }
        }

        protected void scanConstructorDeclaration(SimpleNode node) {
            String name = null;
            List parameterTypes = new ArrayList<>();
            for (int i = 0; i < node.jjtGetNumChildren(); i++) {
                SimpleNode child = node.getChild(i);
                int type = child.getId();
                if (type == JavaParserTreeConstants.JJTFORMALPARAMETERS && child.jjtGetNumChildren() >0) {
                    SimpleNode parameter = child.getChild(0);
                    String rawParameterType = parameter.getChild(1).getText().trim().replaceAll("\\.\\.\\.", "[]");
                    String parameterType = getType(rawParameterType);
                    if (parameterType == null && JAXXCompiler.STRICT_CHECKS) {
                        throw new CompilerException("could not find class '" + rawParameterType + "'");
                    }
                    parameterTypes.add(parameterType);
                } else if (type == JavaParserTreeConstants.JJTMETHODDECLARATOR) {
                    name = child.firstToken.image.trim();
                    SimpleNode formalParameters = child.getChild(0);
                    assert formalParameters.getId() == JavaParserTreeConstants.JJTFORMALPARAMETERS;
                    for (int j = 0; j < formalParameters.jjtGetNumChildren(); j++) {
                        SimpleNode parameter = formalParameters.getChild(j);
                        String rawParameterType = parameter.getChild(1).getText().trim().replaceAll("\\.\\.\\.", "[]");
                        String parameterType = getType(rawParameterType);
                        if (parameterType == null && JAXXCompiler.STRICT_CHECKS) {
                            throw new CompilerException("could not find class '" + rawParameterType + "'");
                        }
                        parameterTypes.add(parameterType);
                    }
                }
            }
            // determine the actual modifiers
            int modifiers = getModifiers(node);
            if (isInterface) {
                modifiers = Modifier.PUBLIC;
            }
            log.debug("method [" + name + "] modifiers == " + Modifier.toString(modifiers));
            MethodDescriptor methodDescriptor = new MethodDescriptor(
                    name,
                    modifiers,
                    null,
                    parameterTypes.toArray(new String[0]),
                    compiler.getClassLoader());
            constructors.add(methodDescriptor);

        }

        private String getType(String cName) {
            String result = compiler.getImportedTypeForSimpleName(cName);
            if (result==null) {
                result = typeParameters.getOrDefault(cName, TagManager.resolveClassName(cName, compiler));
            }
            return result;
        }

        protected void scanMethodDeclaration(SimpleNode node) {
            String returnType = null;
            String name = null;
            List parameterTypes = new ArrayList<>();
            for (int i = 0; i < node.jjtGetNumChildren(); i++) {
                SimpleNode child = node.getChild(i);
                int type = child.getId();
                if (type == JavaParserTreeConstants.JJTRESULTTYPE) {
//                    returnType = TagManager.resolveClassName(child.getText().trim(), compiler);
                    //tchemit 2011-04-21 Remove anything before return type (like javadoc and other comments)
//                    returnType = TagManager.resolveClassName(child.firstToken.image.trim(), compiler);
                    String cName = child.firstToken.image.trim();
                    returnType =  getType(cName);
                } else if (type == JavaParserTreeConstants.JJTMETHODDECLARATOR) {
                    name = child.firstToken.image.trim();
                    SimpleNode formalParameters = child.getChild(0);
                    assert formalParameters.getId() == JavaParserTreeConstants.JJTFORMALPARAMETERS;
                    for (int j = 0; j < formalParameters.jjtGetNumChildren(); j++) {
                        SimpleNode parameter = formalParameters.getChild(j);
                        String rawParameterType = parameter.getChild(1).getText().trim().replaceAll("\\.\\.\\.", "[]");
                        String parameterType = getType(rawParameterType);
                        if (parameterType == null && JAXXCompiler.STRICT_CHECKS) {
                            throw new CompilerException("could not find class '" + rawParameterType + "'");
                        }
                        parameterTypes.add(parameterType);
                    }
                }
            }
            // determine the actual modifiers
            int modifiers = getModifiers(node);
            if (isInterface) {
                modifiers = Modifier.PUBLIC;
            }
            log.debug("method [" + name + "] modifiers == " + Modifier.toString(modifiers));
            MethodDescriptor methodDescriptor = new MethodDescriptor(
                    name,
                    modifiers,
                    returnType,
                    parameterTypes.toArray(new String[0]),
                    compiler.getClassLoader());

            if (Modifier.isPublic(modifiers)) {
                methods.add(methodDescriptor);
            }
        }

        protected void addInterface(String rawName) {
            if (rawName.contains("<")) {
                // generic type
                rawName = rawName.substring(0, rawName.indexOf("<"));
            }
            log.debug("[" + className + "]  try to obtain type of interface " + rawName);
            String myInterface = resolveFullyQualifiedName(rawName);
            if (myInterface == null) {
                throw new CompilerException("Could not find interface: " + rawName);
            }
            if (!interfaces.contains(myInterface)) {
                log.debug("[" + className + "] add interface " + myInterface);
                interfaces.add(myInterface);
            }
        }

        protected String resolveFullyQualifiedName(String rawName) {
            String result;
            String realRawName = null;
            String realParentRawName = null;
            if (rawName.contains(".")) {
                // this is a inner class
                int index = rawName.lastIndexOf(".");
                realParentRawName = rawName.substring(0, index);
                realRawName = rawName.substring(index + 1);
                log.debug("inner class detected ? " + realParentRawName + "//" + realRawName);
            }
            log.debug("try fqn = " + rawName);
            result = TagManager.resolveClassName(rawName, compiler);
            if (result != null) {
                // interface is detected fine (fqn was used or in good package ?)
                return result;
            }
            String suffix = "." + rawName;
            if (realParentRawName != null) {
                suffix = "." + realParentRawName;
            }
            for (String aClass : compiler.getImportedClasses()) {
                if (aClass.endsWith(suffix)) {
                    // found the class as an already known class
                    if (realRawName != null) {
                        aClass += "." + realRawName;
                    }
                    return aClass;
                }
            }
            // try on packages
            Set importedPackages = compiler.getImportedPackages();
            for (String aClass : importedPackages) {
                String fqn = aClass + rawName;
                log.debug("try fqn = " + fqn);
                result = TagManager.resolveClassName(fqn, compiler);
                if (result != null) {
                    return result;
                }
            }
            // nothing was found
            return null;
        }

        protected int getModifiers(SimpleNode node) {
            SimpleNode parentNode = node.getParent();
            for (int i = 0; i < parentNode.jjtGetNumChildren(); i++) {
                SimpleNode child = parentNode.getChild(i);
                if (child.getId() == JavaParserTreeConstants.JJTMODIFIERS) {
                    String modifiersStr = child.getText().trim();
                    return scanModifiers(modifiersStr);
                }
            }
            return 0;
        }

        protected int scanModifiers(String modifiersStr) {
            int modifiers = 0;
            if (modifiersStr.contains("public")) {
                modifiers |= Modifier.PUBLIC;
            }
            if (modifiersStr.contains("protected")) {
                modifiers |= Modifier.PROTECTED;
            }
            if (modifiersStr.contains("private")) {
                modifiers |= Modifier.PRIVATE;
            }
            if (modifiersStr.contains("static")) {
                modifiers |= Modifier.STATIC;
            }
            if (modifiersStr.contains("final")) {
                modifiers |= Modifier.FINAL;
            }
            if (modifiersStr.contains("volatile")) {
                modifiers |= Modifier.VOLATILE;
            }
            if (modifiersStr.contains("transient")) {
                modifiers |= Modifier.TRANSIENT;
            }
            if (modifiersStr.contains("synchronized")) {
                modifiers |= Modifier.SYNCHRONIZED;
            }
            if (modifiersStr.contains("abstract")) {
                modifiers |= Modifier.ABSTRACT;
            }
            return modifiers;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy