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;
}
}
}