Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include configurable variables in imported packages to the schema #42908

Merged
merged 1 commit into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import io.ballerina.compiler.syntax.tree.ModulePartNode;
import io.ballerina.compiler.syntax.tree.ModuleVariableDeclarationNode;
import io.ballerina.compiler.syntax.tree.Node;
import io.ballerina.compiler.syntax.tree.NodeList;
import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode;
import io.ballerina.compiler.syntax.tree.SpecificFieldNode;
import io.ballerina.compiler.syntax.tree.SyntaxKind;
Expand All @@ -44,6 +43,7 @@
import org.wso2.ballerinalang.util.Flags;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -60,60 +60,162 @@
public class ConfigReader {

/**
* Retrieve configurable variables in a package as a per module map.
* Retrieve all the configurable variables for a package.
*
* @param packageInstance to read configurable variables
* @return A map with ConfigModuleDetails(module details) as key and
* List of ConfigVariable (configurable variables in the module) as value
*/
public static Map<ConfigModuleDetails, List<ConfigVariable>> getConfigVariables(Package packageInstance) {
Map<ConfigModuleDetails, List<ConfigVariable>> configDetails = new HashMap<>();
// Get configurable variables of the current package
Set<BVarSymbol> validConfigs = getValidConfigs(packageInstance.getDefaultModule().moduleContext().
bLangPackage().symbol);
for (Module module : packageInstance.modules()) {
getConfigs(module, module.moduleContext().bLangPackage(), configDetails, validConfigs);
}
// Get configurable variables of the direct imports
Collection<ModuleDependency> dependencies = new ArrayList<>();
getValidDependencies(packageInstance, packageInstance.getDefaultModule(), dependencies);
getImportedConfigVars(packageInstance, dependencies, configDetails);
return configDetails;
}

/**
* Retrieve configurable variables for all the direct imports for a package.
*
* @param currentPackage Current package instance
* @param moduleDependencies Used dependencies of the package
* @param configDetails Map to store the configurable variables against module
*/
private static void getImportedConfigVars(Package currentPackage,
Collection<ModuleDependency> moduleDependencies,
Map<ConfigModuleDetails, List<ConfigVariable>> configDetails) {
currentPackage.modules().forEach(module ->
module.moduleContext().bLangPackage().symbol.imports.stream()
.filter(importSymbol -> isDirectDependency(
moduleDependencies,
importSymbol.descriptor.org().value(),
importSymbol.descriptor.packageName().value(),
importSymbol.descriptor.name().moduleNamePart()
))
.forEach(importSymbol -> {
String orgName = importSymbol.descriptor.org().value();
String packageName = importSymbol.descriptor.packageName().value();
String moduleName = importSymbol.descriptor.name().moduleNamePart();

// If default module
if (moduleName == null) {
moduleName = packageName;
}

List<ConfigVariable> configVariables = new ArrayList<>();
getConfigVars(module, importSymbol.scope.entries.values(), null, configVariables);

if (!configVariables.isEmpty()) {
configDetails.put(
new ConfigModuleDetails(orgName, packageName, moduleName,
ProjectKind.BALA_PROJECT), configVariables);
}
})
);
}

/**
* Checks whether it is a direct dependency.
*
* @param moduleDependencies collection of module dependencies
* @param orgName org name
* @param packageName package name
* @param moduleName module name
* @return boolean value indicating whether it is a direct dependency
*/
private static boolean isDirectDependency(Collection<ModuleDependency> moduleDependencies, String orgName,
String packageName, String moduleName) {
return moduleDependencies.stream().anyMatch(dependency ->
dependency.descriptor().org().value().equals(orgName) &&
dependency.descriptor().packageName().value().equals(packageName) &&
(moduleName == null
? dependency.descriptor().name().moduleNamePart() == null
: dependency.descriptor().name().moduleNamePart().equals(moduleName))
);
}

private static String getDescriptionValue(MetadataNode metadataNode) {
for (AnnotationNode annotation : metadataNode.annotations()) {
Node annotReference = annotation.annotReference();
if (annotReference.kind() == SyntaxKind.SIMPLE_NAME_REFERENCE &&
((SimpleNameReferenceNode) annotReference).name().text().equals("display") &&
annotation.annotValue().isPresent()) {

for (MappingFieldNode fieldNode : annotation.annotValue().get().fields()) {
if (fieldNode.kind() == SyntaxKind.SPECIFIC_FIELD) {
SpecificFieldNode specificField = (SpecificFieldNode) fieldNode;
if (specificField.fieldName().kind() == SyntaxKind.IDENTIFIER_TOKEN &&
((IdentifierToken) specificField.fieldName()).text().equals("description") &&
specificField.valueExpr().isPresent()) {

ExpressionNode valueNode = specificField.valueExpr().get();
if (valueNode instanceof BasicLiteralNode) {
return ((BasicLiteralNode) valueNode).literalToken().text();
}
}
}
}
}
}
return "";

Check warning on line 167 in compiler/ballerina-lang/src/main/java/io/ballerina/projects/ConfigReader.java

View check run for this annotation

Codecov / codecov/patch

compiler/ballerina-lang/src/main/java/io/ballerina/projects/ConfigReader.java#L166-L167

Added lines #L166 - L167 were not covered by tests
}



/**
* Update provided map with the configurable variable details for the given module.
*
* @param module Module to retrieve module details
* @param bLangPackage to retrieve configurable variable details
* @param module Module to retrieve module details
* @param bLangPackage to retrieve configurable variable details
* @param configDetails Map to store the configurable variables against module
*/
private static void getConfigs(Module module,
BLangPackage bLangPackage, Map<ConfigModuleDetails,
List<ConfigVariable>> configDetails, Set<BVarSymbol> validConfigs) {
List<ConfigVariable> configVariables = new ArrayList<>();
PackageID currentPkgId = bLangPackage.symbol.pkgID;
for (Scope.ScopeEntry entry : bLangPackage.symbol.scope.entries.values()) {
getConfigVars(module, bLangPackage.symbol.scope.entries.values(), validConfigs, configVariables);
if (!configVariables.isEmpty()) {
// Add configurable variable details for the current package
configDetails.put(getConfigModuleDetails(module.moduleName(), currentPkgId,
module.project().kind()), configVariables);
}
}

private static void getConfigVars(Module module, Collection<Scope.ScopeEntry> scopeEntries,
Set<BVarSymbol> validConfigs, List<ConfigVariable> configVariables) {
for (Scope.ScopeEntry entry : scopeEntries) {
BSymbol symbol = entry.symbol;
// Filter configurable variables
if (symbol != null && symbol.tag == SymTag.VARIABLE && Symbols.isFlagOn(symbol.flags,
Flags.CONFIGURABLE)) {
if (symbol instanceof BVarSymbol) {
BVarSymbol varSymbol = (BVarSymbol) symbol;
if (validConfigs.contains(varSymbol)) {
if ((validConfigs != null && validConfigs.contains(varSymbol)) || validConfigs == null) {
// Get description
String description = getDescription(varSymbol, module);
if (description.startsWith("\"") && description.endsWith("\"")) {
description = description.substring(1, description.length() - 1);
}
configVariables.add(new ConfigVariable(varSymbol.name.value.replace("\\", ""), varSymbol.type,
String description = getDescriptionValue(varSymbol, module);
configVariables.add(new ConfigVariable(
varSymbol.name.value.replace("\\", ""), varSymbol.type,
Symbols.isFlagOn(varSymbol.flags, Flags.REQUIRED), description));
}
}
}
}
if (!configVariables.isEmpty()) {
// Add configurable variable details for the current package
configDetails.put(getConfigModuleDetails(module.moduleName(), currentPkgId,
module.project().kind()), configVariables);
}
}

/**
* Get the used configurable variables of the package.
*
* @param packageSymbol ballerina package symbol
* @return set of valid configurable variable symbols
*/
private static Set<BVarSymbol> getValidConfigs(BPackageSymbol packageSymbol) {
Set<BVarSymbol> configVars = new HashSet<>();
populateConfigVars(packageSymbol, configVars);
Expand All @@ -125,6 +227,12 @@
return configVars;
}

/**
* Populate the configurable variables for the package.
*
* @param pkgSymbol ballerina package symbol
* @param configVars set of configurable variable symbols
*/
private static void populateConfigVars(BPackageSymbol pkgSymbol, Set<BVarSymbol> configVars) {
for (Scope.ScopeEntry entry : pkgSymbol.scope.entries.values()) {
BSymbol symbol = entry.symbol;
Expand All @@ -143,8 +251,8 @@
/**
* Get module details to store the configurable variables against.
*
* @param moduleName to retrieve module details
* @param packageID to retrieve package details
* @param moduleName to retrieve module details
* @param packageID to retrieve package details
* @param projectKind to retrieve information about the type of project
* @return module details stored in object ConfigModuleDetails
*/
Expand All @@ -163,40 +271,19 @@
* @param module to retrieve module details
* @return configurable variable description
*/
private static String getDescription(BVarSymbol symbol, Module module) {
private static String getDescriptionValue(BVarSymbol symbol, Module module) {
Map<Document, SyntaxTree> syntaxTreeMap = getSyntaxTreeMap(module);
Node variableNode = getVariableNode(symbol.getPosition().lineRange().startLine().line(), syntaxTreeMap);
if (variableNode != null) {
Optional<MetadataNode> optionalMetadataNode =
((ModuleVariableDeclarationNode) variableNode).metadata();
if (optionalMetadataNode.isPresent()) {
NodeList<AnnotationNode> annotations = optionalMetadataNode.get().annotations();
for (AnnotationNode annotation : annotations) {
Node annotReference = annotation.annotReference();
if (annotReference.kind() == SyntaxKind.SIMPLE_NAME_REFERENCE) {
SimpleNameReferenceNode simpleNameRef = (SimpleNameReferenceNode) annotReference;
if (simpleNameRef.name().text().equals("display") && annotation.annotValue().isPresent()) {
for (MappingFieldNode fieldNode : annotation.annotValue().get().fields()) {
if (fieldNode.kind() == SyntaxKind.SPECIFIC_FIELD) {
SpecificFieldNode specificField = (SpecificFieldNode) fieldNode;
if (specificField.fieldName().kind() == SyntaxKind.IDENTIFIER_TOKEN) {
if (((IdentifierToken) specificField.fieldName()).text().
equals("description")) {
if (((SpecificFieldNode) fieldNode).valueExpr().isPresent()) {
ExpressionNode valueNode =
((SpecificFieldNode) fieldNode).valueExpr().get();
if (valueNode instanceof BasicLiteralNode) {
return ((BasicLiteralNode) valueNode).literalToken().text();
}
}
}
}
}

}
}
}
String description = getDescriptionValue(optionalMetadataNode.get());
// Remove enclosing quotes if present
if (description.startsWith("\"") && description.endsWith("\"")) {
description = description.substring(1, description.length() - 1);
}
return description;
}
}
return "";
Expand All @@ -205,7 +292,7 @@
/**
* Get Syntax tree node for the configurable variable in given position.
*
* @param position position of configurable BVarSymbol
* @param position position of configurable BVarSymbol
* @param syntaxTreeMap Syntax tree map for the specific module
* @return Relevant syntax tree node for the variable
*/
Expand Down Expand Up @@ -243,4 +330,56 @@
return syntaxTreeMap;
}

/**
* Get all the valid dependencies for the package.
*
* @param packageInstance Package instance
* @param module module instance
* @param dependencies Collection of module dependencies
*/
private static void getValidDependencies(Package packageInstance, Module module,
Collection<ModuleDependency> dependencies) {
Collection<ModuleDependency> directDependencies = module.moduleContext().dependencies();
for (ModuleDependency moduleDependency : directDependencies) {
if (!isDefaultScope(moduleDependency)) {
continue;

Check warning on line 345 in compiler/ballerina-lang/src/main/java/io/ballerina/projects/ConfigReader.java

View check run for this annotation

Codecov / codecov/patch

compiler/ballerina-lang/src/main/java/io/ballerina/projects/ConfigReader.java#L345

Added line #L345 was not covered by tests
}
dependencies.add(moduleDependency);
if (isSamePackage(packageInstance, moduleDependency)) {
for (Module mod : packageInstance.modules()) {
String modName = mod.descriptor().name().moduleNamePart();
if (modName != null && modName.equals(
moduleDependency.descriptor().name().moduleNamePart())) {
getValidDependencies(packageInstance, mod, dependencies);
}
}
}
}
}

/**
* Check if the dependency has the default scope.
*
* @param moduleDependency Module dependency
* @return boolean value indicating whether the dependency has default scope or not
*/
private static boolean isDefaultScope(ModuleDependency moduleDependency) {
return moduleDependency.packageDependency().scope().getValue().equals(
PlatformLibraryScope.DEFAULT.getStringValue());
}

/**
* Check if the dependency is From the same package.
*
* @param packageInstance package instance
* @param moduleDependency Module dependency
* @return boolean value indicating whether the dependency is from the same package or not
*/
private static boolean isSamePackage(Package packageInstance, ModuleDependency moduleDependency) {
String orgValue = moduleDependency.descriptor().org().value();
String packageVal = moduleDependency.descriptor().packageName().value();
return orgValue.equals(packageInstance.packageOrg().value()) &&
packageVal.equals(packageInstance.packageName().value());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,11 @@ private static JsonArray getRequiredConfigs(List<ConfigVariable> configVariables
private JsonObject setConfigVariables(List<ConfigVariable> configVariables,
JsonObject node) {
for (ConfigVariable configVariable : configVariables) {
JsonObject typeNode = new TypeConverter().getType(configVariable.type());
typeNode.addProperty("description", configVariable.description());
node.add(configVariable.name(), typeNode);
if (configVariable.type() != null) {
JsonObject typeNode = new TypeConverter().getType(configVariable.type());
typeNode.addProperty("description", configVariable.description());
node.add(configVariable.name(), typeNode);
}
}
return node;
}
Expand Down
Loading