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

org.nuiton.jaxx.compiler.reflect.ClassDescriptorHelper Maven / Gradle / Ivy

There is a newer version: 3.1.5
Show newest version
/*
 * #%L
 * JAXX :: Compiler
 * %%
 * Copyright (C) 2008 - 2020 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;

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.JAXXCompilerFile;
import org.nuiton.jaxx.compiler.JAXXEngine;
import org.nuiton.jaxx.compiler.JAXXFactory;
import org.nuiton.jaxx.compiler.reflect.resolvers.ClassDescriptorResolverFromJavaClass;
import org.nuiton.jaxx.compiler.reflect.resolvers.ClassDescriptorResolverFromJavaFile;
import org.nuiton.jaxx.compiler.reflect.resolvers.ClassDescriptorResolverFromJaxxFile;
import org.nuiton.jaxx.runtime.JAXXObject;
import org.nuiton.jaxx.runtime.JAXXObjectDescriptor;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;

/**
 * Mirrors the class java.lang.ClassLoader.  JAXX uses ClassDescriptor instead of Class
 * almost everywhere so that it can handle circular dependencies (there can't be a Class object for an uncompiled
 * JAXX or Java source file, and a compiler must be allow references to symbols in uncompiled source files in order to handle
 * circular dependencies).
 *
 * Note :  Was previously {@code ClassDescriptorLoader}.
 *
 * @author Tony Chemit - [email protected]
 * @since 2.0.2
 */
public class ClassDescriptorHelper {

    /** Logger */
    private static final Logger log = LogManager.getLogger(ClassDescriptorHelper.class);

    /**
     * Constants to define who load a {@link ClassDescriptor}.
     *
     * This will be usefull in some case (for consturctor for example when some
     * descriptors are not fully loaded...
     *
     * @since 2.4
     */
    public enum ResolverType {
        JAVA_CLASS,
        JAVA_FILE,
        JAXX_FILE
    }

    private static boolean SHOW_LOADING = log.isDebugEnabled();

    private static final Map descriptors =
            new HashMap<>();

    private static Map descriptorResolvers;

    protected static Map getDescriptorResolvers() {
        if (descriptorResolvers == null) {
            descriptorResolvers = new EnumMap<>(ResolverType.class);
            descriptorResolvers.put(ResolverType.JAVA_CLASS, new ClassDescriptorResolverFromJavaClass());
            descriptorResolvers.put(ResolverType.JAVA_FILE, new ClassDescriptorResolverFromJavaFile());
            descriptorResolvers.put(ResolverType.JAXX_FILE, new ClassDescriptorResolverFromJaxxFile());
        }
        return descriptorResolvers;
    }

    private ClassDescriptorHelper() {
        // on instance
    }

    public static boolean isAssignableFrom(ClassDescriptor classDescriptor,
                                           Class awareClass) {
        ClassDescriptor awareDescriptor = getClassDescriptor(awareClass);
        return awareDescriptor.isAssignableFrom(classDescriptor);
    }

    public static ClassDescriptor getClassDescriptor(String className) throws ClassNotFoundException {

        return getClassDescriptor(className,
                                  Thread.currentThread().getContextClassLoader());
    }

    public static ClassDescriptor getClassDescriptor(Class javaClass) {
        try {
            return getClassDescriptor(javaClass.getName(),
                                      javaClass.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static URL getURL(ClassLoader classLoader, String className, String clasifier) {
        String relativePath = className.replaceAll("\\.", "/");
        String path = relativePath + "." + clasifier;
        if (log.isDebugEnabled()) {
            log.debug("Path to search : " + path);
        }
        return classLoader.getResource(path);
    }

    public static ClassDescriptor getClassDescriptor(
            String className,
            ClassLoader classLoader) throws ClassNotFoundException {

        if (classLoader == null) {
            classLoader = ClassDescriptorHelper.class.getClassLoader();
        }

        ClassDescriptor result = descriptors.get(className);
        if (result != null) {

            // found in cache
            if (log.isTraceEnabled()) {
                log.trace("resolved " + result + " from cache.");
            }
            return result;
        }

        JAXXEngine engine = JAXXFactory.isEngineRegistred() ?
                JAXXFactory.getEngine() : null;

        String relativePathPattern = ".*";// + className + ".*"; // used to ensure that the located resource has the right character cases

        if (engine != null) {

            JAXXCompilerFile file = engine.getJAXXCompilerFile(className);

            if (file != null) {

                // use the symbole table of this jaxx file on computation

                if (SHOW_LOADING) {
                    log.info("from JAXXFile " + file.getJaxxFile());
                }
                result = getClassDescriptor0(
                        ResolverType.JAXX_FILE,
                        className,
                        file.getJAXXFileURL(),
                        classLoader
                );

                if (log.isDebugEnabled()) {
                    log.debug("[" + className + "] loaded");
                }

                return result;
            }
        }

        // the class is not in this compile set, try to have it from java
        // sources or classes.

        // find the most recently updated source for the class -- Java source, JAXX source, or compiled class file
        long javaLastModified = -1;
        URL javaFile = getURL(classLoader, className, "java");
        if (javaFile != null &&
                javaFile.toString().startsWith("file:") &&
                javaFile.toString().matches(relativePathPattern)) {
            javaLastModified = JAXXCompiler.URLtoFile(javaFile).lastModified();
            if (log.isTraceEnabled()) {
                log.trace("[" + className + "] javaFile lastModified " + javaLastModified);
            }
        }

        long classLastModified = -1;
        URL classFile = getURL(classLoader, className, "class");
        if (classFile != null &&
                classFile.toString().startsWith("file:") &&
                classFile.toString().matches(relativePathPattern)) {
            classLastModified = JAXXCompiler.URLtoFile(classFile).lastModified();
            if (log.isTraceEnabled()) {
                log.trace("[" + className + "] class lastModified " + classLastModified);
            }
        }

        // there is a java source and is the last modified
        if (javaLastModified != -1 && javaLastModified > classLastModified) {

            // java file exist and it is the last modified file, so use it

            if (SHOW_LOADING) {
                log.info("from JavaFile " + javaFile);
            }

            result = getClassDescriptor0(
                    ResolverType.JAVA_FILE,
                    className,
                    javaFile,
                    classLoader
            );

            if (log.isDebugEnabled()) {
                log.debug("[" + className + "] loaded");
            }
            return result;
        }

        // use the class ...

        if (SHOW_LOADING) {
            log.info("from class " + className);
        }

        result = getClassDescriptor0(
                ResolverType.JAVA_CLASS,
                className,
                classFile,
                classLoader
        );

        if (log.isDebugEnabled()) {
            log.debug("[" + className + "] loaded");
        }

        if (result != null) {
            return result;
        }

        // can NOT come here, means could not find result...

        throw new IllegalStateException(
                "Can not find descriptor for " + className);
    }

    protected static ClassDescriptor getClassDescriptor0(
            ResolverType resolverType,
            String className,
            URL source,
            ClassLoader classLoader) throws ClassNotFoundException {

        if (log.isDebugEnabled()) {
            log.debug("Loading class descriptor for [" + className +
                              "] with " + resolverType);
        }

        Map resolvers =
                getDescriptorResolvers();

        ClassDescriptorResolver resolver = resolvers.get(resolverType);

        resolver.setClassLoader(classLoader);

        ClassDescriptor result = resolver.resolveDescriptor(className, source);
        if (result != null) {
            descriptors.put(className, result);
        }
        return result;
    }

    public static Class getPrimitiveBoxedClass(String className) {
        if (className.equals("boolean")) {
            return Boolean.class;
        }
        if (className.equals("byte")) {
            return Byte.class;
        }
        if (className.equals("short")) {
            return Short.class;
        }
        if (className.equals("int")) {
            return Integer.class;
        }
        if (className.equals("long")) {
            return Long.class;
        }
        if (className.equals("float")) {
            return Float.class;
        }
        if (className.equals("double")) {
            return Double.class;
        }
        if (className.equals("char")) {
            return Character.class;
        }
        if (className.equals("void")) {
            return Void.class;
        }
        return null;
    }

    public static Class getPrimitiveClass(
            String className) throws ClassNotFoundException {
        if (className.equals("boolean")) {
            return boolean.class;
        }
        if (className.equals("byte")) {
            return byte.class;
        }
        if (className.equals("short")) {
            return short.class;
        }
        if (className.equals("int")) {
            return int.class;
        }
        if (className.equals("long")) {
            return long.class;
        }
        if (className.equals("float")) {
            return float.class;
        }
        if (className.equals("double")) {
            return double.class;
        }
        if (className.equals("char")) {
            return char.class;
        }
        if (className.equals("void")) {
            return void.class;
        }
        // detect arrays
        int arrayCount = 0;
        while (className.endsWith("[]")) {
            arrayCount++;
            className = className.substring(0, className.length() - 2);
        }
        Class klass;
        if (arrayCount > 0) {
            klass = getPrimitiveClass(className);
            if (klass == null) {
                // none primitive array
                return null;
            }
            // must take the boxed class, other it does not works
            // to make a Class.forName("[Lchar;"); but works
            // with Class.forName("[LCharacter;"); ...
            klass = getPrimitiveBoxedClass(className);
            className = klass.getName();

            className = "L" + className + ";";
            while (arrayCount > 0) {
                className = "[" + className;
                arrayCount--;
            }
            //System.out.println("primitive array class "+className);
            return Class.forName(className);
        }
        return null;
    }

    public static Class getClass(String className,
                                    ClassLoader classLoader) throws ClassNotFoundException {
        Class klass = getPrimitiveClass(className);
        if (klass != null) {
            return klass;
        }
        // try an array of none primitive classes
        int arrayCount = 0;
        while (className.endsWith("[]")) {
            arrayCount++;
            className = className.substring(0, className.length() - 2);
        }
        if (arrayCount > 0) {
            className = "L" + className + ";";
            while (arrayCount > 0) {
                className = "[" + className;
                arrayCount--;
            }
        }
        try {
            return classLoader != null ?
                    Class.forName(className, true, classLoader) :
                    Class.forName(className);

        } catch (ClassNotFoundException e) {
            // perharps we are in a inner class ?
            int dotIndex = className.lastIndexOf(".");
            if (dotIndex > -1) {
                String parentFQN = className.substring(0, dotIndex);
                String simpleName = className.substring(dotIndex + 1);
                try {
                    Class parentClass = classLoader != null ? Class.forName(parentFQN, true, classLoader) : Class.forName(parentFQN);
                    for (Class innerClass : parentClass.getClasses()) {
                        if (simpleName.equals(innerClass.getSimpleName())) {
                            return innerClass;
                        }
                    }
                } catch (ClassNotFoundException e1) {
                    // no super class,so let the first exception throw...
                }
            }
            throw e;
        } catch (NoClassDefFoundError e) {

            throw new ClassNotFoundException(e.toString());
        }
    }

//    private static MethodDescriptor createMethodDescriptor(Method javaMethod,
//                                                           ClassLoader classLoader) {
//        String methodName = javaMethod.getName();
//        int modifiers = javaMethod.getModifiers();
//        String returnType = javaMethod.getReturnType().getName();
//        Class[] javaParameters = javaMethod.getParameterTypes();
//        String[] parameters = new String[javaParameters.length];
//        for (int i = 0; i < parameters.length; i++) {
//            parameters[i] = javaParameters[i].getName();
//        }
//        return new MethodDescriptor(methodName, modifiers, returnType, parameters, classLoader);
//    }
//
//    private static FieldDescriptor createFieldDescriptor(Field javaField,
//                                                         ClassLoader classLoader) {
//        String fieldName = javaField.getName();
//        int modifiers = javaField.getModifiers();
//        String type = javaField.getType().getName();
//        return new FieldDescriptor(fieldName, modifiers, type, classLoader);
//    }
//
//    private static JAXXObjectDescriptor getJAXXObjectDescriptor(Class jaxxClass) {
//        if (!JAXXObject.class.isAssignableFrom(jaxxClass) ||
//            JAXXObject.class.equals(jaxxClass)) {
//            return null;
//        }
//        try {
//            Method getJAXXObjectDescriptor = jaxxClass.getMethod("$getJAXXObjectDescriptor");
//            return (JAXXObjectDescriptor) getJAXXObjectDescriptor.invoke(null);
//        } catch (NoSuchMethodException e) {
//            throw new CompilerException("Expected JAXXObject " + jaxxClass.getName() + " to have a static method named $getJAXXObjectDescriptor");
//        } catch (IllegalAccessException e) {
//            throw new CompilerException("Expected JAXXObject " + jaxxClass.getName() + "'s $getJAXXObjectDescriptor method to be public");
//        } catch (InvocationTargetException e) {
//            throw new RuntimeException(e);
//        }
//    }

    public static void checkSupportClass(Class handlerClass,
                                         ClassDescriptor beanClass,
                                         Class... tagClasses) {
        for (Class tagClass : tagClasses) {
            if (getClassDescriptor(tagClass).isAssignableFrom(beanClass)) {
                return;
            }
        }
        throw new IllegalArgumentException(handlerClass.getName() + " does not support the class " + beanClass.getName());
    }

    public static void reset() {
        descriptors.clear();
    }

    public static MethodDescriptor createMethodDescriptor(Method javaMethod,
                                                          ClassLoader classLoader) {
        String methodName = javaMethod.getName();
        int modifiers = javaMethod.getModifiers();
        String returnType = javaMethod.getReturnType().getName();
        Class[] javaParameters = javaMethod.getParameterTypes();
        String[] parameters = new String[javaParameters.length];
        for (int i = 0; i < parameters.length; i++) {
            parameters[i] = javaParameters[i].getName();
        }
        return new MethodDescriptor(methodName,
                                    modifiers,
                                    returnType,
                                    parameters,
                                    classLoader
        );
    }

    public static MethodDescriptor createMethodDescriptor(Constructor javaMethod,
                                                          ClassLoader classLoader) {
        String methodName = javaMethod.getName();
        int modifiers = javaMethod.getModifiers();
        String returnType = null;
        Class[] javaParameters = javaMethod.getParameterTypes();
        String[] parameters = new String[javaParameters.length];
        for (int i = 0; i < parameters.length; i++) {
            parameters[i] = javaParameters[i].getName();
        }
        return new MethodDescriptor(methodName,
                                    modifiers,
                                    returnType,
                                    parameters,
                                    classLoader
        );
    }

    public static FieldDescriptor createFieldDescriptor(Field javaField,
                                                        ClassLoader classLoader) {
        String fieldName = javaField.getName();
        int modifiers = javaField.getModifiers();
        String type = javaField.getType().getName();
        return new FieldDescriptor(fieldName, modifiers, type, classLoader);
    }

    public static JAXXObjectDescriptor getJAXXObjectDescriptor(Class jaxxClass) {
        if (!JAXXObject.class.isAssignableFrom(jaxxClass) ||
                JAXXObject.class.equals(jaxxClass) ||
                jaxxClass.isInterface()) {
            return null;
        }
        try {
            Method getJAXXObjectDescriptor = jaxxClass.getMethod("$getJAXXObjectDescriptor");
            return (JAXXObjectDescriptor) getJAXXObjectDescriptor.invoke(null);
        } catch (NoSuchMethodException e) {
            throw new CompilerException("Expected JAXXObject " + jaxxClass.getName() + " to have a static method named $getJAXXObjectDescriptor");
        } catch (IllegalAccessException e) {
            throw new CompilerException("Expected JAXXObject " + jaxxClass.getName() + "'s $getJAXXObjectDescriptor method to be public");
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    public static void setShowLoading(boolean b) {
        SHOW_LOADING = b;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy