Skip to content

Commit

Permalink
Include configurable variables in imported packages to the schema
Browse files Browse the repository at this point in the history
  • Loading branch information
Dilhasha committed Jun 12, 2024
1 parent 4ac37ad commit 553bcc9
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 49 deletions.
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 @@ private static Set<BVarSymbol> getValidConfigs(BPackageSymbol packageSymbol) {
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 @@ private static void populateConfigVars(BPackageSymbol pkgSymbol, Set<BVarSymbol>
/**
* 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 @@ private static ConfigModuleDetails getConfigModuleDetails(ModuleName moduleName,
* @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 @@ private static String getDescription(BVarSymbol symbol, Module module) {
/**
* 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 @@ private static Map<Document, SyntaxTree> getSyntaxTreeMap(Module module) {
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

0 comments on commit 553bcc9

Please sign in to comment.