Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright oVirt Authors
* SPDX-License-Identifier: Apache-2.0
*/
package org.ovirt.api.metamodel.analyzer;
import static java.util.stream.Collectors.toList;
import static org.ovirt.api.metamodel.analyzer.ModelNameParser.parseJavaName;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaAnnotatedElement;
import com.thoughtworks.qdox.model.JavaAnnotation;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaModel;
import com.thoughtworks.qdox.model.JavaParameter;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.ovirt.api.metamodel.annotations.InputDetail;
import org.ovirt.api.metamodel.annotations.Root;
import org.ovirt.api.metamodel.concepts.Annotation;
import org.ovirt.api.metamodel.concepts.AnnotationParameter;
import org.ovirt.api.metamodel.concepts.Attribute;
import org.ovirt.api.metamodel.concepts.Concept;
import org.ovirt.api.metamodel.concepts.Constraint;
import org.ovirt.api.metamodel.concepts.Document;
import org.ovirt.api.metamodel.concepts.EnumType;
import org.ovirt.api.metamodel.concepts.EnumValue;
import org.ovirt.api.metamodel.concepts.Expression;
import org.ovirt.api.metamodel.concepts.Link;
import org.ovirt.api.metamodel.concepts.ListType;
import org.ovirt.api.metamodel.concepts.Locator;
import org.ovirt.api.metamodel.concepts.Method;
import org.ovirt.api.metamodel.concepts.Model;
import org.ovirt.api.metamodel.concepts.Module;
import org.ovirt.api.metamodel.concepts.Name;
import org.ovirt.api.metamodel.concepts.NameParser;
import org.ovirt.api.metamodel.concepts.Parameter;
import org.ovirt.api.metamodel.concepts.Service;
import org.ovirt.api.metamodel.concepts.StructType;
import org.ovirt.api.metamodel.concepts.Type;
/**
* This class analyzes the Java sources from a directory and populates a model with the concepts extracted from it.
*/
public class ModelAnalyzer {
/**
* This suffix will removed from service names.
*/
private static final String SERVICE_SUFFIX = "Service";
/**
* Name of the {@code value} parameter of annotations created from Javadoc tags.
*/
private static final Name VALUE = NameParser.parseUsingCase("Value");
/**
* Reference to the model that will be populated.
*/
private Model model;
/**
* This list is used to remember the names of the types that haven't been defined yet, and the setters that can be
* used to change them.
*/
private List undefinedTypeUsages = new ArrayList<>();
/**
* This list is used to remember the names of the services that haven't been defined yet, and the setters that can
* be * used to change them.
*/
private List undefinedServiceUsages = new ArrayList<>();
/**
* In order to avoid creating multiple anonymous list types for the same element type we keep this index, where
* the keys are the names of the element types and the values are the list types that have been created.
*/
private Map listTypes = new HashMap<>();
/**
* The constraints can't be completely analyzed till all the types have been resolved, so we store them in this
* list in order to analyze them later.
*/
private List undefinedConstraints = new ArrayList<>();
/**
* Sets the model that will be populated by this analyzer.
*/
public void setModel(Model newModel) {
model = newModel;
}
/**
* Returns a reference to the model that is currently being populated by this analyzer.
*/
public Model getModel() {
return model;
}
/**
* Analyzes all the model source files contained in the given directory or {@code .jar file}, extracts the concepts
* and populates the model that has been previously set with the {@link #setModel(Model)} method.
*
* @param sourceFile the directory or {@code .jar} file containing the model source files
* @throws IOException if something fails while scanning the model source files
*/
public void analyzeSource(File sourceFile) throws IOException {
// Create the QDox project:
JavaProjectBuilder project = new JavaProjectBuilder();
// If the given source file is actually a directory, then we can directly analyze it, but if it is a .jar file
// we need to iterate the contents file by file, as QDox doesn't directly support loading .jar files:
if (sourceFile.isDirectory()) {
project.addSourceTree(sourceFile);
Collection documentFiles = FileUtils.listFiles(sourceFile, new String[] { "adoc" }, true);
for (File documentFile : documentFiles) {
try (InputStream documentIn = new FileInputStream(documentFile)) {
analyzeDocument(documentFile.getName(), documentIn);
}
}
}
else if (sourceFile.isFile() && sourceFile.getName().endsWith(".jar")) {
try (ZipFile zipFile = new ZipFile(sourceFile)) {
Enumeration extends ZipEntry> zipEntries = zipFile.entries();
while (zipEntries.hasMoreElements()) {
ZipEntry zipEntry = zipEntries.nextElement();
String zipEntryName = zipEntry.getName();
if (zipEntryName.endsWith(".java")) {
try (InputStream sourceIn = zipFile.getInputStream(zipEntry)) {
try (Reader sourceReader = new InputStreamReader(sourceIn, Charset.forName("UTF-8"))) {
project.addSource(sourceReader);
}
}
}
else if (zipEntryName.endsWith(".adoc")) {
try (InputStream documentIn = zipFile.getInputStream(zipEntry)) {
analyzeDocument(zipEntryName, documentIn);
}
}
}
}
}
else {
throw new IOException(
"Don't know how to parse source file \"" + sourceFile.getAbsolutePath() + "\", should be a " +
"directory or a .jar file."
);
}
//Separate classes into 'types' (Vm, Disk..) and 'services' (HostService, DisksService...)
//Types are processed before services, because they are referenced during the processing of services.
List types = new ArrayList<>();
List services = new ArrayList<>();
separateClasses(project, types, services);
//Process the types.
analyzeTypes(types);
//Process the services
analyzeServices(services);
// Analyze constraints:
parseConstraints();
}
private void analyzeServices(List services) {
services.stream().filter(x -> !x.isInner()).forEach(this::analyzeService);
//at the end of this process, some services are still 'Undefined'
//complete their definition.
redefineUndefinedServices();
}
private void analyzeTypes(List types) {
for (JavaClass javaClass : types) {
if (javaClass.isEnum()) {
analyzeEnum(javaClass);
} else {
analyzeStruct(javaClass);
}
}
//at the end of this process, some types are still 'Undefined'
//complete their definition.
redefineUndefinedTypes();
}
private void separateClasses(JavaProjectBuilder project, List types, List services) {
for (JavaClass javaClass : project.getClasses()) {
//Inner classes are discarded as they will be processed as part of
//the processing of the class containing them).
if (!javaClass.isInner()) {
if (isAnnotatedWith(javaClass, ModelAnnotations.TYPE)) {
types.add(javaClass);
} else {
services.add(javaClass);
}
}
}
}
private void analyzeEnum(JavaClass javaClass) {
// Create the type:
EnumType type = new EnumType();
analyzeSource(javaClass, type);
analyzeModule(javaClass, type);
analyzeName(javaClass, type);
analyzeAnnotations(javaClass, type);
analyzeDocumentation(javaClass, type);
// Get the values:
javaClass.getEnumConstants().forEach(x -> analyzeEnumValue(x, type));
// Add the type to the model:
model.addType(type);
}
private void analyzeEnumValue(JavaField javaField, EnumType type) {
// Create the value:
EnumValue value = new EnumValue();
analyzeSource(javaField, value);
analyzeName(javaField, value);
analyzeAnnotations(javaField, type);
analyzeDocumentation(javaField, value);
// Add the value to the type:
value.setDeclaringType(type);
type.addValue(value);
}
private void analyzeStruct(JavaClass javaClass) {
// Create the type:
StructType type = new StructType();
analyzeModule(javaClass, type);
analyzeName(javaClass, type);
analyzeAnnotations(javaClass, type);
analyzeDocumentation(javaClass, type);
// Find the mix-ins:
List javaMixins = javaClass.getInterfaces().stream()
.filter(javaInterface -> isAnnotatedWith(javaInterface, ModelAnnotations.MIXIN))
.collect(toList());
// Analyze the base type:
JavaClass javaSuperClass = null;
if (javaClass.isInterface()) {
List javaSuperInterfaces = javaClass.getInterfaces();
if (javaSuperInterfaces != null && javaSuperInterfaces.size() > 0) {
javaSuperClass = javaSuperInterfaces.get(0);
}
}
else {
javaSuperClass = javaClass.getSuperJavaClass();
}
if (javaSuperClass != null) {
String javaSuperClassName = javaSuperClass.getName();
Name baseTypeName = parseJavaName(javaSuperClassName);
assignType(baseTypeName, type::setBase);
}
// Analyze the members for the type and all the mix-ins:
analyzeStructMembers(javaClass, type);
javaMixins.forEach(javaMixin -> analyzeStructMembers(javaMixin, type));
// Add the type to the model:
model.addType(type);
}
private void analyzeStructMembers(JavaClass javaClass, StructType type) {
javaClass.getMethods(false).forEach(javaMethod -> analyzeStructMember(javaMethod, type));
}
private void analyzeStructMember(JavaMethod javaMethod, StructType type) {
if (isAnnotatedWith(javaMethod, ModelAnnotations.LINK)) {
analyzeStructLink(javaMethod, type);
}
else {
analyzeStructAttribute(javaMethod, type);
}
}
private void analyzeStructLink(JavaMethod javaMethod, StructType type) {
// Create the model:
Link link = new Link();
analyzeName(javaMethod, link);
analyzeAnnotations(javaMethod, link);
analyzeDocumentation(javaMethod, link);
// Get the type:
assignTypeReference(javaMethod.getReturns(), link::setType);
// Add the member to the struct:
link.setDeclaringType(type);
type.addLink(link);
}
private void analyzeStructAttribute(JavaMethod javaMethod, StructType type) {
// Create the model:
Attribute attribute = new Attribute();
analyzeName(javaMethod, attribute);
analyzeAnnotations(javaMethod, attribute);
analyzeDocumentation(javaMethod, attribute);
// Get the type:
assignTypeReference(javaMethod.getReturns(), attribute::setType);
// Add the member to the struct:
attribute.setDeclaringType(type);
type.addAttribute(attribute);
}
private void analyzeService(JavaClass javaClass) {
// Create the service:
Service service = new Service();
analyzeModule(javaClass, service);
analyzeName(javaClass, service);
analyzeAnnotations(javaClass, service);
analyzeDocumentation(javaClass, service);
// Find the mix-ins:
List javaMixins = javaClass.getInterfaces().stream()
.filter(javaInterface -> isAnnotatedWith(javaInterface, ModelAnnotations.MIXIN))
.collect(toList());
// Analyze the base service:
JavaClass javaSuper = null;
if (javaClass.isInterface()) {
List javaSupers = javaClass.getInterfaces();
if (!javaSupers.isEmpty()) {
javaSuper = javaSupers.get(0);
}
}
else {
javaSuper = javaClass.getSuperJavaClass();
}
if (javaSuper != null) {
String javaSuperClassName = removeSuffix(javaSuper.getName(), SERVICE_SUFFIX);
Name baseTypeName = parseJavaName(javaSuperClassName);
assignService(baseTypeName, service::setBase);
}
// Analyze the members of the service and all the mix-ins:
analyzeServiceMembers(javaClass, service);
javaMixins.forEach(javaMixin -> analyzeServiceMembers(javaMixin, service));
// Add the type to the model:
model.addService(service);
// Check if this should be the root of the tree of services of the model:
if (isAnnotatedWith(javaClass, ModelAnnotations.ROOT)) {
Service root = model.getRoot();
if (root != null) {
System.err.println(
"The current root \"" + root.getName() + "\" will be replaced with \"" + service.getName() + "\"."
);
}
model.setRoot(service);
}
}
private void analyzeServiceMembers(JavaClass javaClass, Service service) {
javaClass.getNestedClasses().forEach(x -> analyzeNestedClass(x, service));
javaClass.getMethods().forEach(x -> analyzeServiceMember(x, service));
}
private void analyzeServiceMember(JavaMethod javaMethod, Service service) {
if (isAnnotatedWith(javaMethod, ModelAnnotations.SERVICE)) {
analyzeServiceLocator(javaMethod, service);
}
}
/**
* Analyze a nested interface in a Service. A nested interface in a
* service represents a method of the service.
*/
private void analyzeNestedClass(JavaClass javaClass, Service service) {
// Create the method:
Method method = analyzeMethod(javaClass, service);
// After all other members have been analyzed, handle input detail information.
analyzeInputDetail(javaClass, method, service);
// Add the method to the model
service.addMethod(method);
}
public Method analyzeMethod(JavaClass javaClass, Service service) {
Method method = new Method();
analyzeName(javaClass, method);
analyzeAnnotations(javaClass, method);
analyzeDocumentation(javaClass, method);
// Find the mix-ins:
List javaMixins = javaClass.getInterfaces().stream()
.filter(javaInterface -> isAnnotatedWith(javaInterface, ModelAnnotations.MIXIN))
.collect(toList());
// Analyze the members of the method and the mix-ins:
analyzeMethodMembers(javaClass, method);
javaMixins.forEach(javaMixin -> analyzeMethodMembers(javaMixin, method));
// Add the member to the service:
method.setDeclaringService(service);
createSignatures(javaClass, service, method);
return method;
}
private void analyzeMethodMembers(JavaClass javaClass, Method method) {
javaClass.getMethods().forEach(x -> analyzeMethodMember(x, method));
}
public void createSignatures(JavaClass javaClass, Service service, Method method) {
for (JavaClass innerClass : javaClass.getNestedClasses()) {
//an inner class is expected to be an interface
assert innerClass.isInterface();
Method childMethod = analyzeMethod(innerClass, service);
copyParameters(childMethod, method);
analyzeInputDetail(innerClass, childMethod, service);
childMethod.setBase(method);
service.addMethod(childMethod);
}
}
/**
* For method 'signatures' (e:g FromStorageDomain, DirectLun) Parameters should
* be mostly copied from the base method (e.g: add). This method does this.
*/
private void copyParameters(Method childMethod, Method method) {
for (Parameter parameter : method.getParameters()) {
Parameter newParameter = new Parameter();
//copy from base parameter. Member-involvement-trees not copied on purpose.
newParameter.setIn(parameter.isIn());
newParameter.setOut(parameter.isOut());
newParameter.setType(parameter.getType());
newParameter.setName(parameter.getName());
newParameter.setDoc(parameter.getDoc());
newParameter.setSource(parameter.getSource());
newParameter.getAnnotations().addAll(parameter.getAnnotations());
//set the child-method as the declaring method
newParameter.setDeclaringMethod(childMethod);
childMethod.addParameter(newParameter);
}
}
public void analyzeInputDetail(JavaClass javaClass, Method method, Service service) {
JavaMethod inputDetailMethod = getInputDetailMethod(javaClass);
if (inputDetailMethod!=null) {
InputDetailAnalyzer inputDetailAnalyzer = new InputDetailAnalyzer();
inputDetailAnalyzer.analyzeInput(inputDetailMethod.getSourceCode(), method.getParameters());
}
}
private JavaMethod getInputDetailMethod(JavaClass javaClass) {
Optional method = javaClass.getMethods().stream().filter(x -> isAnnotatedWith(x, InputDetail.class.getCanonicalName())).findFirst();
return method.isPresent() ? method.get() : null;
}
private void analyzeMethodMember(JavaMethod javaMethod, Method method) {
if (isAnnotatedWith(javaMethod, ModelAnnotations.IN) || isAnnotatedWith(javaMethod, ModelAnnotations.OUT)) {
analyzeParameter(javaMethod, method);
}
if (isAnnotatedWith(javaMethod, ModelAnnotations.REQUIRED) || isAnnotatedWith(javaMethod, ModelAnnotations.ALLOWED)) {
analyzeConstraint(javaMethod, method);
}
}
private void analyzeParameter(JavaMethod javaMethod, Method method) {
// Create the parameter:
Parameter parameter = new Parameter();
analyzeName(javaMethod, parameter);
analyzeAnnotations(javaMethod, parameter);
analyzeDocumentation(javaMethod, parameter);
// Get the direction:
if (isAnnotatedWith(javaMethod, ModelAnnotations.IN)) {
parameter.setIn(true);
}
if (isAnnotatedWith(javaMethod, ModelAnnotations.OUT)) {
parameter.setOut(true);
}
// Get the type:
assignTypeReference(javaMethod.getReturns(), parameter::setType);
// Get the default value:
String javaValue = javaMethod.getSourceCode();
if (javaValue != null && !javaValue.isEmpty()) {
Expression expression = analyzeExpression(javaValue);
parameter.setDefaultValue(expression);
}
// Add the parameter to the method:
parameter.setDeclaringMethod(method);
method.addParameter(parameter);
}
private void analyzeConstraint(JavaMethod javaMethod, Method method) {
// Create the constraint:
Constraint constraint = new Constraint();
analyzeName(javaMethod, constraint);
analyzeAnnotations(javaMethod, constraint);
analyzeDocumentation(javaMethod, constraint);
// Get the direction:
if (isAnnotatedWith(javaMethod, ModelAnnotations.IN)) {
constraint.setIn(true);
}
if (isAnnotatedWith(javaMethod, ModelAnnotations.OUT)) {
constraint.setOut(true);
}
// Get the source:
String source = javaMethod.getSourceCode();
constraint.setSource(source);
// Remember to analyze the constraint source once the types and services have been completely defined:
undefinedConstraints.add(constraint);
// Add the constraint to the method:
constraint.setDeclaringMethod(method);
method.addConstraint(constraint);
}
private void analyzeServiceLocator(JavaMethod javaMethod, Service service) {
// Create the locator:
Locator locator = new Locator();
analyzeName(javaMethod, locator);
analyzeAnnotations(javaMethod, locator);
analyzeDocumentation(javaMethod, locator);
// Analyze the parameters:
javaMethod.getParameters().forEach(x -> analyzeLocatorParameter(x, locator));
// Get the referenced service:
assignServiceReference(javaMethod.getReturns(), locator::setService);
// Add the parameter to the method:
service.addLocator(locator);
}
private void analyzeLocatorParameter(JavaParameter javaParameter, Locator locator) {
// Create the parameter:
Parameter parameter = new Parameter();
analyzeName(javaParameter, parameter);
analyzeAnnotations(javaParameter, parameter);
analyzeDocumentation(javaParameter, parameter);
// Get the type:
assignTypeReference(javaParameter.getJavaClass(), parameter::setType);
// Add the parameter to the locator:
locator.addParameter(parameter);
}
private void analyzeModule(JavaClass javaClass, Type type) {
analyzeModule(javaClass, type::setModule);
}
private void analyzeModule(JavaClass javaClass, Service service) {
analyzeModule(javaClass, service::setModule);
}
private void analyzeModule(JavaClass javaClass, Consumer moduleSetter) {
String javaName = javaClass.getPackageName();
Name name = NameParser.parseUsingSeparator(javaName, '.');
Module module = model.getModule(name);
if (module == null) {
module = new Module();
module.setModel(model);
module.setName(name);
model.addModule(module);
}
moduleSetter.accept(module);
}
private void analyzeAnnotations(JavaAnnotatedElement javaElement, Concept concept) {
// Model annotations like @Type and @Service shouldn't be considered as annotations applied to concepts so
// we need to check the package name and exclude them:
javaElement.getAnnotations().stream()
.filter(x -> !Objects.equals(x.getType().getPackageName(), Root.class.getPackage().getName()))
.forEach(x -> this.analyzeAnnotation(x, concept));
}
@SuppressWarnings("unchecked")
private void analyzeAnnotation(JavaAnnotation javaAnnotation, Concept concept) {
// Create the annotation:
Annotation annotation = new Annotation();
analyzeName(javaAnnotation, annotation);
// Get the parameters and their values:
Map parameters = javaAnnotation.getNamedParameterMap();
for (Map.Entry entry : parameters.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
AnnotationParameter parameter = new AnnotationParameter();
parameter.setName(NameParser.parseUsingCase(key));
// Get the values of the parameter:
List values = new ArrayList<>();
if (value instanceof List) {
values.addAll((List) value);
}
else {
values.add(value.toString());
}
// QDox returns the values of parameters surrounded by double quotes. This is probably a bug, but we need
// to live with it, so we need to remove them.
for (int i = 0; i < values.size(); i++) {
String current = values.get(i);
if (current.startsWith("\"") && current.endsWith("\"")) {
current = current.substring(1, current.length() - 1);
values.set(i, current);
}
}
// Set the values of the parameter:
parameter.addValues(values);
annotation.addParameter(parameter);
}
// Add the annotation to the concept:
concept.addAnnotation(annotation);
}
private void analyzeName(JavaClass javaClass, Service service) {
// Get the name of the Java class:
String javaName = javaClass.getName();
javaName = removeSuffix(javaName, SERVICE_SUFFIX);
// Parse the Java name and assign it to the concept:
Name name = parseJavaName(javaName);
service.setName(name);
}
private void analyzeName(JavaClass javaClass, Concept concept) {
// Get the name of the Java class:
String javaName = javaClass.getName();
// Parse the Java name and assign it to the concept:
Name name = parseJavaName(javaName);
concept.setName(name);
}
private void analyzeName(JavaField javaField, Concept concept) {
// Fields of classes are parsed using case, but enum values are also represented as fields and they need to be
// parsed using underscore as the separator:
String javaName = javaField.getName();
Name name;
if (javaField.isEnumConstant()) {
name = NameParser.parseUsingSeparator(javaName, '_');
}
else {
name = parseJavaName(javaName);
}
concept.setName(name);
}
private void analyzeName(JavaMethod javaMethod, Concept concept) {
String javaName = javaMethod.getName();
Name name = parseJavaName(javaName);
concept.setName(name);
}
private void analyzeName(JavaParameter javaParameter, Concept concept) {
String javaName = javaParameter.getName();
Name name = parseJavaName(javaName);
concept.setName(name);
}
private void analyzeName(JavaAnnotation javaAnnotation, Annotation annotation) {
String javaName = javaAnnotation.getType().getName();
Name name = parseJavaName(javaName);
annotation.setName(name);
}
private void analyzeDocumentation(JavaAnnotatedElement javaElement, Concept concept) {
// Copy the text of the documentation (without the doclet tags):
String javaComment = javaElement.getComment();
if (javaComment != null) {
javaComment = javaComment.trim();
if (!javaComment.isEmpty()) {
concept.setDoc(javaComment);
}
}
// Make annotations for the javadoc tags:
javaElement.getTags().stream().forEach(docTag -> {
this.analyzeDocletTag(docTag, concept);
});
}
private void analyzeDocletTag(DocletTag docTag, Concept concept) {
// Calculate the name:
Name name = NameParser.parseUsingCase(docTag.getName());
// Create the annotation if it doesn't exist in the concept yet:
Annotation annotation = concept.getAnnotation(name);
if (annotation == null) {
annotation = new Annotation();
annotation.setName(name);
concept.addAnnotation(annotation);
}
// Create the "value" parameter if it doesn't exist yet:
String value = docTag.getValue();
if (value != null) {
value = value.trim();
if (!value.isEmpty()) {
AnnotationParameter parameter = annotation.getParameter(VALUE);
if (parameter == null) {
parameter = new AnnotationParameter();
parameter.setName(VALUE);
annotation.addParameter(parameter);
}
parameter.addValue(docTag.getValue());
}
}
}
private void analyzeSource(JavaModel javaModel, Concept concept) {
String javaSource = javaModel.getCodeBlock();
if (javaSource != null && !javaSource.isEmpty()) {
concept.setSource(javaSource);
}
}
/**
* Finds the type corresponding to the given reference and assigns it using the given setter. If there is no type
* corresponding to the given reference yet then a new undefined type will be created and assigned, and it will be
* remembered so that it can later be replaced with the real type.
*
* @param javaClass the reference to the type to find
* @param typeSetter the setter used to assign the type
*/
private void assignTypeReference(JavaClass javaClass, TypeSetter typeSetter) {
String javaTypeName = javaClass.getName();
Name typeName;
switch (javaTypeName) {
case "Boolean":
case "bool":
typeName = model.getBooleanType().getName();
break;
case "Double":
case "Float":
case "double":
case "float":
typeName = model.getDecimalType().getName();
break;
default:
typeName = parseJavaName(javaTypeName);
}
if (javaClass.isArray()) {
ListType listType = listTypes.get(typeName);
if (listType == null) {
listType = new ListType();
assignType(typeName, listType::setElementType);
listTypes.put(typeName, listType);
model.addType(listType);
}
typeSetter.accept(listType);
}
else {
assignType(typeName, typeSetter);
}
}
/**
* Finds the type corresponding to the given name and assigns it using the given setter. If there is no type
* corresponding to the given name yet then a new undefined type will be created and assigned, and it will be
* remembered so that it can later be replaced with the real type.
*
* @param typeName the name of the type to find
* @param typeSetter the setter used to assign the type
*/
private void assignType(Name typeName, TypeSetter typeSetter) {
// First try to find a type that has already been defined:
Type type = model.getType(typeName);
// If the type hasn't been defined then we create need to create a dummy type
if (type == null) {
type = new UndefinedType();
type.setName(typeName);
}
// If we are returning an undefined type then we need to to remember to replace it later, saving the name of
// the type and the setter provided by the calller:
if (type instanceof UndefinedType) {
TypeUsage typeUsage = new TypeUsage();
typeUsage.setName(typeName);
typeUsage.setSetter(typeSetter);
undefinedTypeUsages.add(typeUsage);
}
// Assign the type:
if (typeSetter != null) {
typeSetter.accept(type);
}
}
/**
* Finds the service corresponding to the given reference and assigns it using the given setter. If there is no
* service corresponding to the given reference yet then a new undefined service will be created and assigned, and
* it will be remembered so that it can later be replaced with the real service.
*
* @param javaClass the reference to the service to find
* @param setter the setter used to assign the type
*/
private void assignServiceReference(JavaClass javaClass, ServiceSetter setter) {
// Get the name of the Java class:
String javaName = javaClass.getName();
javaName = removeSuffix(javaName, SERVICE_SUFFIX);
// Parse the name and assign it to the service:
Name name = parseJavaName(javaName);
assignService(name, setter);
}
/**
* Finds the service corresponding to the given name and assigns it using the given setter. If there is no service
* corresponding to the given name yet then a new undefined service will be created and assigned, and it will be
* remembered so that it can later be replaced with the real service.
*
* @param serviceName the name of the service to find
* @param serviceSetter the setter used to assign the service
*/
private void assignService(Name serviceName, ServiceSetter serviceSetter) {
// First try to find a service that has already been defined:
Service service = model.getService(serviceName);
// If the service hasn't been defined then we create need to create a dummy service:
if (service == null) {
service = new UndefinedService();
service.setName(serviceName);
}
// If we are returning an undefined service then we need to to remember to replace it later, saving the name of
// the service and the setter provided by the caller:
if (service instanceof UndefinedService) {
ServiceUsage serviceUsage = new ServiceUsage();
serviceUsage.setName(serviceName);
serviceUsage.setSetter(serviceSetter);
undefinedServiceUsages.add(serviceUsage);
}
// Assign the service:
if (serviceSetter != null) {
serviceSetter.accept(service);
}
}
/**
* Creates a document with the given name and, populates it with the content read from the given input stream, and
* adds it to the model.
*
* @param file the name of file containing the document, including the extension
* @param in the input stream that will be used to populate the document
* @throws IOException if something fails while reading the content of the document
*/
private void analyzeDocument(String file, InputStream in) throws IOException {
// Create the document:
Document document = new Document();
// Remove the extension from the file name:
file = FilenameUtils.getBaseName(file);
// The name of the document can contain a prefix to explicitly indicate the order of the document relative to
// the other documents of the model. This prefix should be separated from the rest of the name using a dash, and
// that dash should be ignored.
String prefix = null;
int index = file.indexOf('-');
if (index > 0) {
prefix = file.substring(0, index);
file = file.substring(index + 1);
}
Name name = NameParser.parseUsingCase(file);
if (prefix != null && !prefix.isEmpty()) {
List words = name.getWords();
words.add(0, prefix);
name.setWords(words);
if (Character.isAlphabetic(prefix.charAt(0))) {
document.setAppendix(true);
}
}
document.setName(name);
// Read the source of the document:
String source = IOUtils.toString(in, StandardCharsets.UTF_8);
document.setSource(source);
// Add the document to the model:
model.addDocument(document);
}
/**
* Finds the references to unresolved types and replace them with the real type definitions.
*/
private void redefineUndefinedTypes() {
for (TypeUsage usage : undefinedTypeUsages) {
Name name = usage.getName();
Type type = model.getType(name);
if (type == null) {
System.err.println("Can't find type for name \"" + name + "\".");
continue;
}
TypeSetter setter = usage.getSetter();
setter.accept(type);
}
}
/**
* Finds the references to unresolved services and replace them with the real service definitions.
*/
private void redefineUndefinedServices() {
for (ServiceUsage usage : undefinedServiceUsages) {
Name name = usage.getName();
Service service = model.getService(name);
if (service == null) {
System.err.println("Can't find service for name \"" + name + "\".");
continue;
}
ServiceSetter setter = usage.getSetter();
setter.accept(service);
}
}
/**
* Parse the sources of the constraints.
*/
private void parseConstraints() {
undefinedConstraints.stream().forEach(this::parseConstraint);
}
/**
* Parse the source of the given constraint.
*/
private void parseConstraint(Constraint constraint) {
ConstraintAnalyzer analyzer = new ConstraintAnalyzer();
analyzer.setModel(model);
analyzer.setMethod(constraint.getDeclaringMethod());
analyzer.setConstraint(constraint);
analyzer.analyzeSource(constraint.getSource());
}
private Expression analyzeExpression(String javaExpression) {
// TODO: Analyze the expression tree.
Expression expression = null;
// expression.setValue(javaExpression);
return expression;
}
/**
* Check if the given Java element is annotated with the given annotation.
*
* @param javaElement the parsed element to check
* @param annotationName the name of the annotation to check
* @return {@code true} iif the given class is annotated with the given annotation
*/
private boolean isAnnotatedWith(JavaAnnotatedElement javaElement, String annotationName) {
for (JavaAnnotation annotation : javaElement.getAnnotations()) {
JavaClass currentType = annotation.getType();
String currentName = currentType.getPackageName() + "." + currentType.getName();
if (Objects.equals(currentName, annotationName)) {
return true;
}
}
return false;
}
/**
* Checks if the given string ends with the given suffixe, and if it does removes it.
*
* @param text the string to check
* @param suffix the suffix to check/remove
*/
private String removeSuffix(String text, String suffix) {
if (text.endsWith(suffix)) {
text = text.substring(0, text.length() - suffix.length());
}
return text;
}
}