From 23e49de68036531f042684ff79b77fe7b5d9a0a8 Mon Sep 17 00:00:00 2001 From: Vellummyilum Vinoth Date: Thu, 5 Feb 2026 10:00:11 +0530 Subject: [PATCH] Migrate to New libraries API --- .../core/ClassSymbolHandler.java | 112 ++ .../core/ConstantSymbolHandler.java | 71 + .../core/CopilotLibraryManager.java | 134 ++ .../core/EnumSymbolHandler.java | 49 + .../core/FunctionSymbolHandler.java | 71 + .../core/LibraryDatabaseAccessor.java | 151 ++ .../core/LibraryModelConverter.java | 450 +++++ .../core/ServiceLoader.java | 383 +++++ .../core/SymbolProcessor.java | 150 ++ .../core/TypeDefDataBuilder.java | 237 +++ .../core/TypeDefSymbolHandler.java | 49 + .../core/TypeLinkBuilder.java | 163 ++ .../core/TypeSymbolExtractor.java | 125 ++ .../flowmodelgenerator/core/model/Client.java | 63 + .../core/model/EnumValue.java | 53 + .../flowmodelgenerator/core/model/Field.java | 75 + .../core/model/Library.java | 88 + .../core/model/LibraryFunction.java | 100 ++ .../core/model/Listener.java | 52 + .../core/model/ModelToJsonConverter.java | 65 + .../core/model/Parameter.java | 75 + .../core/model/PathElement.java | 28 + .../core/model/PathSegment.java | 53 + .../flowmodelgenerator/core/model/Return.java | 43 + .../core/model/Service.java | 72 + .../core/model/ServiceRemoteFunction.java | 82 + .../core/model/StringPath.java | 32 + .../flowmodelgenerator/core/model/Type.java | 54 + .../core/model/TypeDef.java | 108 ++ .../core/model/TypeDefMember.java | 27 + .../core/model/TypeLink.java | 63 + .../core/model/UnionValue.java | 53 + .../model/adapters/StringPathAdapter.java | 49 + .../extension/CopilotLibraryService.java | 1525 +---------------- 34 files changed, 3397 insertions(+), 1508 deletions(-) create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ClassSymbolHandler.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ConstantSymbolHandler.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/CopilotLibraryManager.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/EnumSymbolHandler.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/FunctionSymbolHandler.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/LibraryDatabaseAccessor.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/LibraryModelConverter.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ServiceLoader.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/SymbolProcessor.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeDefDataBuilder.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeDefSymbolHandler.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeLinkBuilder.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeSymbolExtractor.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Client.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/EnumValue.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Field.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Library.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/LibraryFunction.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Listener.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/ModelToJsonConverter.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Parameter.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/PathElement.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/PathSegment.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Return.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Service.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/ServiceRemoteFunction.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/StringPath.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Type.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeDef.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeDefMember.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeLink.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/UnionValue.java create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/adapters/StringPathAdapter.java diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ClassSymbolHandler.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ClassSymbolHandler.java new file mode 100644 index 0000000000..546ef7ece6 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ClassSymbolHandler.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ClassSymbol; +import io.ballerina.compiler.api.symbols.Qualifier; +import io.ballerina.flowmodelgenerator.core.model.Client; +import io.ballerina.flowmodelgenerator.core.model.LibraryFunction; +import io.ballerina.flowmodelgenerator.core.model.TypeDef; +import io.ballerina.modelgenerator.commons.FunctionData; +import io.ballerina.modelgenerator.commons.FunctionDataBuilder; +import io.ballerina.modelgenerator.commons.ModuleInfo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Handler for processing CLASS symbols from semantic model. + * Extracts client classes and normal classes with their methods. + * + * @since 1.0.1 + */ +public class ClassSymbolHandler { + + private ClassSymbolHandler() { + // Prevent instantiation + } + + /** + * Processes a CLASS symbol and returns either a Client or TypeDef. + * + * @param classSymbol the class symbol to process + * @param semanticModel the semantic model + * @param moduleInfo the module information + * @param org the organization name + * @param packageName the package name + * @return Optional containing either Client or TypeDef, or empty if not public + */ + public static Optional process(ClassSymbol classSymbol, + SemanticModel semanticModel, + ModuleInfo moduleInfo, + String org, + String packageName) { + // Process only PUBLIC classes: CLIENT classes (connectors) and normal classes + if (!classSymbol.qualifiers().contains(Qualifier.PUBLIC)) { + return Optional.empty(); + } + + boolean isClient = classSymbol.qualifiers().contains(Qualifier.CLIENT); + String className = classSymbol.getName().orElse(isClient ? "Client" : "Class"); + + FunctionData.Kind classKind = isClient ? FunctionData.Kind.CONNECTOR : FunctionData.Kind.CLASS_INIT; + + FunctionData classData = new FunctionDataBuilder() + .semanticModel(semanticModel) + .moduleInfo(moduleInfo) + .name(className) + .parentSymbol(classSymbol) + .functionResultKind(classKind) + .build(); + + List functions = new ArrayList<>(); + + // Add the constructor/init function first + LibraryFunction constructor = LibraryModelConverter.functionDataToModel(classData, org, packageName); + constructor.setName("init"); // Override name to "init" for constructor + functions.add(constructor); + + // Then add all other methods (remote functions, resource functions, etc.) + List classMethods = new FunctionDataBuilder() + .semanticModel(semanticModel) + .moduleInfo(moduleInfo) + .parentSymbolType(className) + .parentSymbol(classSymbol) + .buildChildNodes(); + + for (FunctionData method : classMethods) { + LibraryFunction methodFunc = LibraryModelConverter.functionDataToModel(method, org, packageName); + functions.add(methodFunc); + } + + if (isClient) { + Client client = new Client(className, classData.description()); + client.setFunctions(functions); + return Optional.of(client); + } else { + TypeDef typeDef = new TypeDef(); + typeDef.setName(className); + typeDef.setDescription(classData.description()); + typeDef.setFunctions(functions); + return Optional.of(typeDef); + } + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ConstantSymbolHandler.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ConstantSymbolHandler.java new file mode 100644 index 0000000000..44bdbd5a2d --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ConstantSymbolHandler.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import io.ballerina.compiler.api.symbols.ConstantSymbol; +import io.ballerina.compiler.api.symbols.Qualifier; +import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.api.values.ConstantValue; +import io.ballerina.flowmodelgenerator.core.model.Type; +import io.ballerina.flowmodelgenerator.core.model.TypeDef; +import io.ballerina.modelgenerator.commons.TypeDefData; + +import java.util.Optional; + +/** + * Handler for processing CONSTANT symbols from semantic model. + * + * @since 1.0.1 + */ +public class ConstantSymbolHandler { + + private ConstantSymbolHandler() { + } + + public static Optional process(ConstantSymbol constantSymbol, + String org, + String packageName) { + if (!constantSymbol.qualifiers().contains(Qualifier.PUBLIC)) { + return Optional.empty(); + } + + TypeDefData constantData = TypeDefDataBuilder.buildFromConstant(constantSymbol); + TypeDef typeDef = LibraryModelConverter.typeDefDataToModel(constantData, org, packageName); + + // Add varType using ConstantValue + String varTypeName = ""; + Object constValue = constantSymbol.constValue(); + if (constValue instanceof ConstantValue constantValue) { + varTypeName = constantValue.valueType().typeKind().getName(); + } + + // Fallback to type descriptor if constValue is null or not ConstantValue + if (varTypeName.isEmpty()) { + TypeSymbol typeSymbol = constantSymbol.typeDescriptor(); + if (typeSymbol != null && !typeSymbol.signature().isEmpty()) { + varTypeName = typeSymbol.signature(); + } + } + + Type varType = new Type(varTypeName); + typeDef.setVarType(varType); + + return Optional.of(typeDef); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/CopilotLibraryManager.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/CopilotLibraryManager.java new file mode 100644 index 0000000000..12fef0a1d2 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/CopilotLibraryManager.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.flowmodelgenerator.core.model.Library; +import io.ballerina.flowmodelgenerator.core.model.Service; +import io.ballerina.modelgenerator.commons.ModuleInfo; +import io.ballerina.modelgenerator.commons.PackageUtil; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Core orchestrator for Copilot library operations. + * Coordinates between database access, symbol processing, and service loading. + * + * @since 1.0.1 + */ +public class CopilotLibraryManager { + + private static final Gson GSON = new Gson(); + + /** + * Loads all libraries from the database. + * Returns a list of libraries with name and description only. + * + * @return List of Library objects containing name and description + */ + public List loadLibrariesFromDatabase() { + List libraries = new ArrayList<>(); + + try { + Map packageToDescriptionMap = LibraryDatabaseAccessor.loadAllPackages(); + + for (Map.Entry entry : packageToDescriptionMap.entrySet()) { + Library library = new Library(entry.getKey(), entry.getValue()); + libraries.add(library); + } + } catch (IOException | SQLException e) { + throw new RuntimeException("Failed to load libraries from database: " + e.getMessage(), e); + } + + return libraries; + } + + /** + * Loads filtered libraries using the semantic model. + * Returns libraries with full details including clients, functions, typedefs, and services. + * + * @param libraryNames Array of library names in "org/package_name" format to filter + * @return List of Library objects with complete information + */ + public List loadFilteredLibraries(String[] libraryNames) { + List libraries = new ArrayList<>(); + + for (String libraryName : libraryNames) { + // Parse library name "org/package_name" + String[] parts = libraryName.split("/"); + if (parts.length != 2) { + continue; // Skip invalid format + } + String org = parts[0]; + String packageName = parts[1]; + + // Create module info (use latest version by passing null) + ModuleInfo moduleInfo = new ModuleInfo(org, packageName, org + "/" + + packageName, null); + + // Get semantic model for the module + Optional optSemanticModel = PackageUtil.getSemanticModel(org, packageName); + if (optSemanticModel.isEmpty()) { + continue; // Skip if semantic model not found + } + + SemanticModel semanticModel = optSemanticModel.get(); + + // Get the package description from database + String description = LibraryDatabaseAccessor.getPackageDescription(org, packageName).orElse(""); + + // Create library object + Library library = new Library(libraryName, description); + + // Process module symbols to extract clients, functions, and typedefs + SymbolProcessor.SymbolProcessingResult symbolResult = SymbolProcessor.processModuleSymbols( + semanticModel, + moduleInfo, + org, + packageName + ); + + library.setClients(symbolResult.getClients()); + library.setFunctions(symbolResult.getFunctions()); + library.setTypeDefs(symbolResult.getTypeDefs()); + + // Load services from both inbuilt triggers and generic services + // For now, keep using JSON and convert to Service POJOs + JsonArray servicesJson = ServiceLoader.loadAllServices(libraryName); + List services = new ArrayList<>(); + for (JsonElement serviceElement : servicesJson) { + Service service = GSON.fromJson(serviceElement, Service.class); + services.add(service); + } + library.setServices(services); + + libraries.add(library); + } + + return libraries; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/EnumSymbolHandler.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/EnumSymbolHandler.java new file mode 100644 index 0000000000..fc9ec7f879 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/EnumSymbolHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import io.ballerina.compiler.api.symbols.EnumSymbol; +import io.ballerina.compiler.api.symbols.Qualifier; +import io.ballerina.flowmodelgenerator.core.model.TypeDef; +import io.ballerina.modelgenerator.commons.TypeDefData; + +import java.util.Optional; + +/** + * Handler for processing ENUM symbols from semantic model. + * + * @since 1.0.1 + */ +public class EnumSymbolHandler { + + private EnumSymbolHandler() { + } + + public static Optional process(EnumSymbol enumSymbol, + String org, + String packageName) { + if (!enumSymbol.qualifiers().contains(Qualifier.PUBLIC)) { + return Optional.empty(); + } + + TypeDefData enumData = TypeDefDataBuilder.buildFromEnum(enumSymbol); + TypeDef typeDef = LibraryModelConverter.typeDefDataToModel(enumData, org, packageName); + return Optional.of(typeDef); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/FunctionSymbolHandler.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/FunctionSymbolHandler.java new file mode 100644 index 0000000000..b2885621bc --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/FunctionSymbolHandler.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.FunctionSymbol; +import io.ballerina.compiler.api.symbols.Qualifier; +import io.ballerina.flowmodelgenerator.core.model.LibraryFunction; +import io.ballerina.modelgenerator.commons.FunctionData; +import io.ballerina.modelgenerator.commons.FunctionDataBuilder; +import io.ballerina.modelgenerator.commons.ModuleInfo; + +import java.util.Optional; + +/** + * Handler for processing FUNCTION symbols from semantic model. + * Extracts module-level functions. + * + * @since 1.0.1 + */ +public class FunctionSymbolHandler { + + private FunctionSymbolHandler() { + // Prevent instantiation + } + + /** + * Processes a FUNCTION symbol and returns a Function model. + * + * @param functionSymbol the function symbol to process + * @param semanticModel the semantic model + * @param moduleInfo the module information + * @param org the organization name + * @param packageName the package name + * @return Optional containing Function, or empty if not public + */ + public static Optional process(FunctionSymbol functionSymbol, + SemanticModel semanticModel, + ModuleInfo moduleInfo, + String org, + String packageName) { + if (!functionSymbol.qualifiers().contains(Qualifier.PUBLIC)) { + return Optional.empty(); + } + + FunctionData functionData = new FunctionDataBuilder() + .semanticModel(semanticModel) + .moduleInfo(moduleInfo) + .functionSymbol(functionSymbol) + .build(); + + LibraryFunction function = LibraryModelConverter.functionDataToModel(functionData, org, packageName); + return Optional.of(function); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/LibraryDatabaseAccessor.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/LibraryDatabaseAccessor.java new file mode 100644 index 0000000000..75a7f55923 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/LibraryDatabaseAccessor.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Database accessor for library package information from the search-index database. + * Handles database connections, queries, and resource management. + * + * @since 1.0.1 + */ +public class LibraryDatabaseAccessor { + + private static final String INDEX_FILE_NAME = "search-index.sqlite"; + + private LibraryDatabaseAccessor() { + // Prevent instantiation + } + + /** + * Loads all distinct packages from the database. + * Returns a map with package name (org/package_name) as key and description as value. + * + * @return map of package names to descriptions + * @throws IOException if database file access fails + * @throws SQLException if database query fails + */ + public static Map loadAllPackages() throws IOException, SQLException { + Map packageToDescriptionMap = new LinkedHashMap<>(); + + String dbPath = getDatabasePath(); + String sql = """ + SELECT DISTINCT org, package_name, description + FROM Package + WHERE org IS NOT NULL AND package_name IS NOT NULL + ORDER BY org, package_name; + """; + + try (Connection conn = DriverManager.getConnection(dbPath); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + + while (rs.next()) { + String org = rs.getString("org"); + String packageName = rs.getString("package_name"); + String description = rs.getString("description"); + + // Create the full name as "org/package_name" + String fullName = org + "/" + packageName; + + // Store only if not already present (handles duplicates) + if (!packageToDescriptionMap.containsKey(fullName)) { + packageToDescriptionMap.put(fullName, description != null ? description : ""); + } + } + } + + return packageToDescriptionMap; + } + + /** + * Gets the package description from the database for a given org and package name. + * + * @param org the organization name + * @param packageName the package name + * @return Optional containing the description, or empty if not found + */ + public static Optional getPackageDescription(String org, String packageName) { + try { + String dbPath = getDatabasePath(); + String sql = """ + SELECT description + FROM Package + WHERE org = ? AND package_name = ? + ORDER BY id DESC + LIMIT 1; + """; + + try (Connection conn = DriverManager.getConnection(dbPath); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setString(1, org); + stmt.setString(2, packageName); + + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + String desc = rs.getString("description"); + return Optional.ofNullable(desc); + } + } + } + } catch (IOException | SQLException e) { + throw new RuntimeException("Error retrieving package description for " + org + "/" + packageName + ": " + + e.getMessage(), e); + } + + return Optional.empty(); + } + + /** + * Gets the JDBC database path for the search-index.sqlite file. + * + * @return the JDBC database path + * @throws IOException if database file cannot be accessed + */ + public static String getDatabasePath() throws IOException { + java.net.URL dbUrl = LibraryDatabaseAccessor.class.getClassLoader().getResource(INDEX_FILE_NAME); + + if (dbUrl == null) { + throw new IOException("Database resource not found: " + INDEX_FILE_NAME); + } + + // Copy database to temp directory + Path tempDir = Files.createTempDirectory("search-index"); + Path tempFile = tempDir.resolve(INDEX_FILE_NAME); + + try (InputStream inputStream = dbUrl.openStream()) { + Files.copy(inputStream, tempFile); + } + + return "jdbc:sqlite:" + tempFile; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/LibraryModelConverter.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/LibraryModelConverter.java new file mode 100644 index 0000000000..99a4d1a1b0 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/LibraryModelConverter.java @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.flowmodelgenerator.core.model.EnumValue; +import io.ballerina.flowmodelgenerator.core.model.Field; +import io.ballerina.flowmodelgenerator.core.model.LibraryFunction; +import io.ballerina.flowmodelgenerator.core.model.Parameter; +import io.ballerina.flowmodelgenerator.core.model.PathElement; +import io.ballerina.flowmodelgenerator.core.model.PathSegment; +import io.ballerina.flowmodelgenerator.core.model.Return; +import io.ballerina.flowmodelgenerator.core.model.StringPath; +import io.ballerina.flowmodelgenerator.core.model.Type; +import io.ballerina.flowmodelgenerator.core.model.TypeDef; +import io.ballerina.flowmodelgenerator.core.model.TypeDefMember; +import io.ballerina.flowmodelgenerator.core.model.TypeLink; +import io.ballerina.flowmodelgenerator.core.model.UnionValue; +import io.ballerina.modelgenerator.commons.FieldData; +import io.ballerina.modelgenerator.commons.FunctionData; +import io.ballerina.modelgenerator.commons.ParameterData; +import io.ballerina.modelgenerator.commons.ReturnTypeData; +import io.ballerina.modelgenerator.commons.TypeDefData; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static io.ballerina.flowmodelgenerator.core.TypeSymbolExtractor.extractImportStatements; +import static io.ballerina.flowmodelgenerator.core.TypeSymbolExtractor.extractRecordName; +import static io.ballerina.flowmodelgenerator.core.TypeSymbolExtractor.extractTypeLinks; +import static io.ballerina.modelgenerator.commons.FunctionDataBuilder.REST_RESOURCE_PATH; + +/** + * Utility class for converting data model objects to POJO representations. + * Handles conversion of FunctionData, TypeDefData, FieldData, and ParameterData to model POJOs. + * + * @since 1.0.1 + */ +public class LibraryModelConverter { + + private LibraryModelConverter() { + // Prevent instantiation + } + + /** + * Converts FunctionData to LibraryFunction POJO. + * + * @param functionData the function data to convert + * @param currentOrg the current package organization + * @param currentPackage the current package name + * @return LibraryFunction POJO + */ + public static LibraryFunction functionDataToModel(FunctionData functionData, String currentOrg, + String currentPackage) { + LibraryFunction function = new LibraryFunction(); + + // For resource functions, don't add "name" field, add "accessor" and "paths" instead + boolean isResourceFunction = functionData.kind() == FunctionData.Kind.RESOURCE; + + if (!isResourceFunction) { + function.setName(functionData.name()); + } + + // Map function kind to human-readable type + String functionType = getFunctionTypeString(functionData.kind()); + function.setType(functionType); + function.setDescription(functionData.description()); + + // Add resource-specific fields for resource functions + if (isResourceFunction) { + // Extract accessor and paths from resourcePath + String resourcePath = functionData.resourcePath(); + if (resourcePath != null && !resourcePath.isEmpty()) { + List pathsList = new ArrayList<>(); + + if (REST_RESOURCE_PATH.equals(resourcePath)) { + // For rest resource path, add a special path parameter + function.setAccessor(functionData.name()); + PathSegment pathParam = new PathSegment("path", "..."); + pathsList.add(pathParam); + } else { + // Parse normal resource paths + String[] pathParts = parseResourcePath(resourcePath); + function.setAccessor(pathParts[0]); + + // Parse paths array + String[] paths = pathParts[1].split("/"); + for (String path : paths) { + if (path.isEmpty()) { + continue; + } + // Check if it's a path parameter (starts with []) + if (path.startsWith("[") && path.endsWith("]")) { + // Path parameter: extract name and type + String paramContent = path.substring(1, path.length() - 1); + String[] paramParts = paramContent.split(":"); + String paramName = paramParts[0].trim(); + String paramType = paramParts.length > 1 ? paramParts[1].trim() : "string"; + PathSegment pathParam = new PathSegment(paramName, paramType); + pathsList.add(pathParam); + } else { + // Regular path segment - wrap in StringPath + pathsList.add(new StringPath(path)); + } + } + } + function.setPaths(pathsList); + } + } + + // Add parameters array if present + if (functionData.parameters() != null) { + List parametersList = new ArrayList<>(); + for (Map.Entry entry : functionData.parameters().entrySet()) { + Parameter param = parameterDataToModel(entry.getValue(), currentOrg, currentPackage); + parametersList.add(param); + } + function.setParameters(parametersList); + } + + // Add return object with type + Return returnInfo = new Return(); + boolean isConstructor = functionData.kind() == FunctionData.Kind.CLASS_INIT || + functionData.kind() == FunctionData.Kind.CONNECTOR || + functionData.kind() == FunctionData.Kind.LISTENER_INIT; + + // Use ReturnTypeData if available, otherwise fall back to returnType string + if (functionData.returnTypeData() != null) { + ReturnTypeData returnTypeData = functionData.returnTypeData(); + String typeName = returnTypeData.name(); + + // Extract just the type name if it has module prefix (e.g., "http:Request" -> "Request") + String simpleTypeName = typeName; + if (typeName != null && typeName.contains(":")) { + simpleTypeName = typeName.substring(typeName.lastIndexOf(':') + 1); + } + + String nameToUse = simpleTypeName; + List links = null; + + // Skip link creation for generic/parameterized types (e.g., "map", "map") + // and primitive/default types (e.g., "()", "[]", "null", "int", "string", etc.) + boolean isGenericType = simpleTypeName != null && simpleTypeName.contains("<"); + boolean isPrimitiveType = isPrimitiveOrDefaultType(simpleTypeName); + boolean shouldSkipLinks = isGenericType || isPrimitiveType; + + // For constructors, add internal link to the class itself + if (isConstructor && simpleTypeName != null && !shouldSkipLinks) { + links = TypeLinkBuilder.createInternalLinks(simpleTypeName); + } else if (!shouldSkipLinks && currentOrg != null && currentPackage != null + && returnTypeData.typeSymbol() != null) { + String recordName = extractRecordName(returnTypeData.typeSymbol()); + String importStatements = extractImportStatements(returnTypeData.typeSymbol()); + + if (importStatements != null && !importStatements.isEmpty()) { + // Has imports - determine internal/external based on import package + links = extractTypeLinks(importStatements, recordName, currentOrg, currentPackage); + links = TypeLinkBuilder.filterInternalExternal(links); + } else if (recordName != null && !recordName.isEmpty()) { + // No imports - must be internal type from same package + links = TypeLinkBuilder.createInternalLinks(recordName); + } + + // Use recordName if we have links after filtering + if (links != null && !links.isEmpty()) { + nameToUse = recordName; + } + } + + Type returnType = new Type(nameToUse); + if (links != null && !links.isEmpty()) { + returnType.setLinks(links); + } + + returnInfo.setType(returnType); + } else if (functionData.returnType() != null) { + // Fallback to old format + Type returnType = new Type(functionData.returnType()); + returnInfo.setType(returnType); + } + + function.setReturnInfo(returnInfo); + + return function; + } + + /** + * Converts TypeDefData to TypeDef POJO. + * + * @param typeDefData the typedef data to convert + * @param currentOrg the current package organization + * @param currentPackage the current package name + * @return TypeDef POJO + */ + public static TypeDef typeDefDataToModel(TypeDefData typeDefData, String currentOrg, String currentPackage) { + TypeDef typeDef = new TypeDef(); + typeDef.setName(typeDefData.name()); + typeDef.setDescription(typeDefData.description()); + typeDef.setType(typeDefData.type() != null ? typeDefData.type().getValue() : null); + + TypeDefData.TypeCategory category = typeDefData.type(); + + // Only add value for CONSTANT category + if (category == TypeDefData.TypeCategory.CONSTANT) { + typeDef.setValue(typeDefData.baseType()); + } + + // Only add fields/members for specific categories (not for CONSTANT, ERROR, or OTHER) + if (typeDefData.fields() != null && + category != TypeDefData.TypeCategory.CONSTANT && + category != TypeDefData.TypeCategory.ERROR && + category != TypeDefData.TypeCategory.OTHER) { + + if (category == TypeDefData.TypeCategory.UNION) { + // Union members: name and type + List members = new ArrayList<>(); + for (FieldData field : typeDefData.fields()) { + UnionValue unionValue = new UnionValue(); + unionValue.setName(field.name()); + if (field.type() != null) { + Type type = new Type(); + type.setName(field.type().name()); + unionValue.setType(type); + } + members.add(unionValue); + } + typeDef.setMembers(members); + } else if (category == TypeDefData.TypeCategory.ENUM) { + // Enum members: name and description + List members = new ArrayList<>(); + for (FieldData field : typeDefData.fields()) { + EnumValue enumValue = new EnumValue(); + enumValue.setName(field.name()); + enumValue.setDescription(field.description()); + members.add(enumValue); + } + typeDef.setMembers(members); + } else { + // Record/Class fields + List fields = new ArrayList<>(); + for (FieldData field : typeDefData.fields()) { + Field modelField = fieldDataToModel(field, currentOrg, currentPackage); + fields.add(modelField); + } + typeDef.setFields(fields); + } + } + + return typeDef; + } + + /** + * Converts FieldData to Field POJO. + * + * @param fieldData the field data to convert + * @param currentOrg the current package organization + * @param currentPackage the current package name + * @return Field POJO + */ + public static Field fieldDataToModel(FieldData fieldData, String currentOrg, String currentPackage) { + Field field = new Field(); + field.setName(fieldData.name()); + field.setDescription(fieldData.description()); + field.setOptional(fieldData.optional()); + + // Add type object + if (fieldData.type() != null) { + String typeName = fieldData.type().name(); + String recordName = extractRecordName(fieldData.type().typeSymbol()); + + // Skip link creation for generic/parameterized types and primitive types + boolean isGenericType = typeName != null && typeName.contains("<"); + boolean isPrimitiveType = isPrimitiveOrDefaultType(typeName); + boolean shouldSkipLinks = isGenericType || isPrimitiveType; + + // Extract type links if we have the TypeSymbol and org/package info + List links = null; + if (!shouldSkipLinks && currentOrg != null && currentPackage != null + && fieldData.type().typeSymbol() != null) { + TypeSymbol typeSymbol = fieldData.type().typeSymbol(); + String importStatements = extractImportStatements(typeSymbol); + + if (importStatements != null && !importStatements.isEmpty()) { + // Has imports - determine internal/external based on import package + links = extractTypeLinks(importStatements, recordName, currentOrg, currentPackage); + } else if (recordName != null && !recordName.isEmpty()) { + // No imports - must be internal type from same package + links = TypeLinkBuilder.createInternalLinks(recordName); + } + } + + // Use recordName if we have links, otherwise use typeName + String nameToUse = (links != null && !links.isEmpty()) ? recordName : typeName; + Type type = new Type(nameToUse); + + if (links != null && !links.isEmpty()) { + type.setLinks(links); + } + + field.setType(type); + } + + return field; + } + + /** + * Converts ParameterData to Parameter POJO. + * + * @param paramData the parameter data to convert + * @param currentOrg the current package organization + * @param currentPackage the current package name + * @return Parameter POJO + */ + public static Parameter parameterDataToModel(ParameterData paramData, + String currentOrg, String currentPackage) { + Parameter parameter = new Parameter(); + parameter.setName(paramData.name()); + parameter.setDescription(paramData.description()); + parameter.setOptional(paramData.optional()); + parameter.setDefaultValue(paramData.defaultValue()); + + // Add type object + if (paramData.type() != null) { + String typeName = paramData.type(); + + if (!typeName.isEmpty()) { + // Skip link creation for generic/parameterized types and primitive types + boolean isGenericType = typeName.contains("<"); + boolean isPrimitiveType = isPrimitiveOrDefaultType(typeName); + boolean shouldSkipLinks = isGenericType || isPrimitiveType; + + // Add type links from import statements if available + List links = null; + String recordName = null; + if (!shouldSkipLinks) { + recordName = extractRecordName(paramData.typeSymbol()); + if (paramData.importStatements() != null && !paramData.importStatements().isEmpty()) { + // Has imports - determine internal/external based on import package + links = extractTypeLinks(paramData.importStatements(), recordName, currentOrg, currentPackage); + } else if (recordName != null && !recordName.isEmpty()) { + // No imports - must be internal type from same package + links = TypeLinkBuilder.createInternalLinks(recordName); + } + } + + // Use recordName if we have links, otherwise use typeName + String nameToUse = (links != null && !links.isEmpty()) ? recordName : typeName; + Type type = new Type(nameToUse); + + if (links != null && !links.isEmpty()) { + type.setLinks(links); + } + + parameter.setType(type); + } + } + return parameter; + } + + /** + * Parses a resource path string to extract accessor and path. + * + * @param resourcePath the resource path string + * @return array with [accessor, path] + */ + private static String[] parseResourcePath(String resourcePath) { + String trimmed = resourcePath.trim(); + int firstSlash = trimmed.indexOf('/'); + + if (firstSlash > 0) { + // Format: "accessor /path" + return new String[]{ + trimmed.substring(0, firstSlash).trim(), + trimmed.substring(firstSlash) + }; + } else if (firstSlash == 0) { + // Format: "/path" (default to "get") + return new String[]{"get", trimmed}; + } else { + // No path, just accessor + return new String[]{trimmed, ""}; + } + } + + /** + * Converts FunctionData.Kind to human-readable function type string. + * + * @param kind the function kind + * @return string representation for JSON + */ + private static String getFunctionTypeString(FunctionData.Kind kind) { + if (kind == null) { + return "Normal Function"; + } + return switch (kind) { + case CLASS_INIT, CONNECTOR, LISTENER_INIT -> "Constructor"; + case REMOTE -> "Remote Function"; + case RESOURCE -> "Resource Function"; + default -> "Normal Function"; + }; + } + + /** + * Checks if a type is a primitive or default type that shouldn't have links. + * + * @param typeName the type name to check + * @return true if it's a primitive/default type, false otherwise + */ + private static boolean isPrimitiveOrDefaultType(String typeName) { + if (typeName == null || typeName.isEmpty()) { + return true; + } + + // Check for default/special types + return typeName.equals("()") || + typeName.equals("[]") || + typeName.equals("null") || + // Check for primitive types + typeName.equals("int") || + typeName.equals("string") || + typeName.equals("boolean") || + typeName.equals("float") || + typeName.equals("decimal") || + typeName.equals("byte") || + typeName.equals("any") || + typeName.equals("anydata") || + typeName.equals("never") || + typeName.equals("readonly") || + typeName.equals("json") || + typeName.equals("xml") || + typeName.equals("error"); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ServiceLoader.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ServiceLoader.java new file mode 100644 index 0000000000..c279d6d91c --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/ServiceLoader.java @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * Service loader for loading library service definitions. + * Handles loading from inbuilt-triggers and generic-services JSON files. + * + * @since 1.0.1 + */ +public class ServiceLoader { + + private static final String GENERIC_SERVICES_JSON_PATH = "/copilot/generic-services.json"; + + private ServiceLoader() { + // Prevent instantiation + } + + /** + * Loads all services for a given library from both inbuilt triggers and generic services. + * + * @param libraryName the library name (e.g., "ballerina/http", "ballerinax/kafka") + * @return JsonArray containing all services for this library + */ + public static JsonArray loadAllServices(String libraryName) { + JsonArray services = new JsonArray(); + + // Load from inbuilt triggers + JsonArray triggerServices = loadFromInbuiltTriggers(libraryName); + triggerServices.forEach(services::add); + + // Load from generic services + JsonArray genericServices = loadFromGenericServices(libraryName); + genericServices.forEach(services::add); + + return services; + } + + /** + * Loads services from inbuilt-triggers JSON files. + * These JSON files contain service definitions with listener and function information. + * + * @param libraryName the library name (e.g., "ballerinax/kafka") + * @return JsonArray containing services, or empty array if not found + */ + private static JsonArray loadFromInbuiltTriggers(String libraryName) { + JsonArray services = new JsonArray(); + + // Map library names to inbuilt-triggers file names + String triggerFileName = getInbuiltTriggerFileName(libraryName); + if (triggerFileName == null) { + return services; // No inbuilt trigger for this library + } + + try (InputStream inputStream = ServiceLoader.class.getResourceAsStream("/inbuilt-triggers/" + + triggerFileName)) { + if (inputStream == null) { + return services; // File not found + } + + try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + + JsonObject triggerData = JsonParser.parseReader(reader).getAsJsonObject(); + + // Extract listener information + JsonObject listener = triggerData.getAsJsonObject("listener"); + if (listener == null) { + return services; + } + + // Extract service types + JsonArray serviceTypes = triggerData.getAsJsonArray("serviceTypes"); + if (serviceTypes == null || serviceTypes.isEmpty()) { + return services; + } + + // For each service type, create a service object + for (JsonElement serviceTypeElement : serviceTypes) { + JsonObject serviceType = serviceTypeElement.getAsJsonObject(); + + JsonObject serviceObj = new JsonObject(); + + // Service type: "fixed" for specific listeners + serviceObj.addProperty("type", "fixed"); + + // Build listener object + JsonObject listenerObj = buildListenerFromTriggerData(listener); + serviceObj.add("listener", listenerObj); + + // Extract functions from service type and add as methods for fixed services + JsonArray functionsFromService = serviceType.getAsJsonArray("functions"); + if (functionsFromService != null && !functionsFromService.isEmpty()) { + JsonArray transformedMethods = new JsonArray(); + for (JsonElement funcElement : functionsFromService) { + JsonObject func = funcElement.getAsJsonObject(); + JsonObject transformedMethod = transformServiceMethod(func); + transformedMethods.add(transformedMethod); + } + serviceObj.add("methods", transformedMethods); + } + + services.add(serviceObj); + } + + } + } catch (IOException e) { + // If file doesn't exist or cannot be read, return empty array + return services; + } + + return services; + } + + /** + * Loads generic services for a specific library from the generic-services.json file. + * + * @param libraryName the library name (e.g., "ballerina/http") + * @return JsonArray containing services for this library, or empty array if not found + */ + private static JsonArray loadFromGenericServices(String libraryName) { + JsonArray matchingServices = new JsonArray(); + + try (InputStream inputStream = ServiceLoader.class.getResourceAsStream(GENERIC_SERVICES_JSON_PATH)) { + if (inputStream == null) { + return matchingServices; // File not found, return empty array + } + + try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + JsonObject genericServicesData = JsonParser.parseReader(reader).getAsJsonObject(); + + // Get the services array + JsonArray allServices = genericServicesData.getAsJsonArray("services"); + if (allServices == null || allServices.isEmpty()) { + return matchingServices; + } + + // Filter services by library name + for (JsonElement serviceElement : allServices) { + JsonObject service = serviceElement.getAsJsonObject(); + + // Check if this service belongs to the requested library + if (service.has("libraryName") && + service.get("libraryName").getAsString().equals(libraryName)) { + + // Create a copy of the service without the libraryName field + JsonObject serviceObj = new JsonObject(); + serviceObj.addProperty("type", service.get("type").getAsString()); + serviceObj.addProperty("instructions", service.get("instructions").getAsString()); + + // Copy listener object + if (service.has("listener")) { + serviceObj.add("listener", service.get("listener")); + } + + matchingServices.add(serviceObj); + } + } + } + } catch (IOException e) { + // If file doesn't exist or cannot be read, return empty array + return matchingServices; + } + + return matchingServices; + } + + /** + * Maps library names to inbuilt-trigger file names. + * + * @param libraryName the library name (e.g., "ballerinax/kafka") + * @return the trigger file name (e.g., "kafka.json") or null if not a trigger library + */ + private static String getInbuiltTriggerFileName(String libraryName) { + // Remove org prefix if present + String packageName = libraryName.contains("/") ? + libraryName.substring(libraryName.indexOf("/") + 1) : libraryName; + + // Map known trigger libraries to their JSON file names + return switch (packageName) { + case "kafka" -> "kafka.json"; + case "asb" -> "asb.json"; + case "jms" -> "jms.json"; + case "rabbitmq" -> "rabbitmq.json"; + case "nats" -> "nats.json"; + case "ftp" -> "ftp.json"; + case "mqtt" -> "mqtt.json"; + case "salesforce" -> "salesforce.json"; + case "trigger.github", "github" -> "github.json"; + default -> null; + }; + } + + /** + * Builds a listener object from inbuilt-triggers listener data. + * + * @param listenerData the listener JSON object from triggers file + * @return JsonObject representing the listener + */ + private static JsonObject buildListenerFromTriggerData(JsonObject listenerData) { + JsonObject listenerObj = new JsonObject(); + + // Get listener name from valueTypeConstraint + String listenerName = listenerData.has("valueTypeConstraint") ? + listenerData.get("valueTypeConstraint").getAsString() : "Listener"; + listenerObj.addProperty("name", listenerName); + + // Extract parameters from listener properties + JsonArray parametersArray = new JsonArray(); + if (listenerData.has("properties")) { + JsonObject properties = listenerData.getAsJsonObject("properties"); + for (String propKey : properties.keySet()) { + JsonObject prop = properties.getAsJsonObject(propKey); + JsonObject paramObj = buildParameterFromProperty(propKey, prop); + parametersArray.add(paramObj); + } + } + + listenerObj.add("parameters", parametersArray); + return listenerObj; + } + + /** + * Builds a parameter object from a listener property. + * + * @param propertyName the property name + * @param property the property JSON object + * @return JsonObject representing the parameter + */ + private static JsonObject buildParameterFromProperty(String propertyName, JsonObject property) { + JsonObject paramObj = new JsonObject(); + + // Parameter name + paramObj.addProperty("name", propertyName); + + // Parameter description from metadata + String description = ""; + if (property.has("metadata")) { + JsonObject metadata = property.getAsJsonObject("metadata"); + if (metadata.has("description")) { + description = metadata.get("description").getAsString(); + } + } + paramObj.addProperty("description", description); + + // Parameter type + JsonObject typeObj = new JsonObject(); + String typeName = property.has("valueTypeConstraint") ? + property.get("valueTypeConstraint").getAsString() : "string"; + typeObj.addProperty("name", typeName); + paramObj.add("type", typeObj); + + // Default value if present + if (property.has("placeholder") && !property.get("placeholder").isJsonNull()) { + paramObj.addProperty("default", property.get("placeholder").getAsString()); + } + + return paramObj; + } + + /** + * Transforms a service function from trigger data to a service method format. + * Service methods don't include the name field (unlike LibraryFunction). + * + * @param functionData the function JSON object from triggers file + * @return JsonObject representing the transformed service method + */ + private static JsonObject transformServiceMethod(JsonObject functionData) { + JsonObject method = new JsonObject(); + + // Determine method type + String methodType = "remote"; + if (functionData.has("qualifiers")) { + JsonArray qualifiers = functionData.getAsJsonArray("qualifiers"); + if (qualifiers != null && !qualifiers.isEmpty()) { + String qualifier = qualifiers.get(0).getAsString(); + methodType = qualifier.equals("resource") ? "resource" : "remote"; + } + } + method.addProperty("type", methodType); + + // Method documentation + if (functionData.has("documentation")) { + method.addProperty("description", functionData.get("documentation").getAsString()); + } + + // Parameters + if (functionData.has("parameters")) { + JsonArray parameters = functionData.getAsJsonArray("parameters"); + JsonArray transformedParams = new JsonArray(); + for (JsonElement paramElement : parameters) { + JsonObject param = paramElement.getAsJsonObject(); + JsonObject transformedParam = new JsonObject(); + + // Parameter name + if (param.has("name")) { + transformedParam.addProperty("name", param.get("name").getAsString()); + } + + // Parameter description + if (param.has("documentation")) { + transformedParam.addProperty("description", param.get("documentation").getAsString()); + } + + // Parameter type + JsonObject typeObj = new JsonObject(); + if (param.has("type")) { + JsonElement typeElement = param.get("type"); + if (typeElement.isJsonArray()) { + // If type is an array, get the first element (or default type) + JsonArray typeArray = typeElement.getAsJsonArray(); + if (!typeArray.isEmpty()) { + typeObj.addProperty("name", typeArray.get(0).getAsString()); + } + } else { + typeObj.addProperty("name", typeElement.getAsString()); + } + } else if (param.has("typeName")) { + typeObj.addProperty("name", param.get("typeName").getAsString()); + } + transformedParam.add("type", typeObj); + + // Optional flag + if (param.has("optional")) { + transformedParam.addProperty("optional", param.get("optional").getAsBoolean()); + } + + transformedParams.add(transformedParam); + } + method.add("parameters", transformedParams); + } + + // Return type + if (functionData.has("returnType")) { + JsonObject returnTypeData = functionData.getAsJsonObject("returnType"); + JsonObject returnObj = new JsonObject(); + JsonObject returnTypeObj = new JsonObject(); + + if (returnTypeData.has("typeName")) { + returnTypeObj.addProperty("name", returnTypeData.get("typeName").getAsString()); + } else if (returnTypeData.has("type")) { + JsonElement typeElement = returnTypeData.get("type"); + if (typeElement.isJsonArray()) { + JsonArray typeArray = typeElement.getAsJsonArray(); + if (!typeArray.isEmpty()) { + returnTypeObj.addProperty("name", typeArray.get(0).getAsString()); + } + } else { + returnTypeObj.addProperty("name", typeElement.getAsString()); + } + } + returnObj.add("type", returnTypeObj); + method.add("return", returnObj); + } + + return method; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/SymbolProcessor.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/SymbolProcessor.java new file mode 100644 index 0000000000..82b34e2273 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/SymbolProcessor.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ClassSymbol; +import io.ballerina.compiler.api.symbols.ConstantSymbol; +import io.ballerina.compiler.api.symbols.EnumSymbol; +import io.ballerina.compiler.api.symbols.FunctionSymbol; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; +import io.ballerina.flowmodelgenerator.core.model.Client; +import io.ballerina.flowmodelgenerator.core.model.LibraryFunction; +import io.ballerina.flowmodelgenerator.core.model.TypeDef; +import io.ballerina.modelgenerator.commons.ModuleInfo; + +import java.util.ArrayList; +import java.util.List; + +/** + * Coordinator for processing module symbols using strategy pattern. + * Dispatches symbol processing to specific handlers based on symbol kind. + * + * @since 1.0.1 + */ +public class SymbolProcessor { + + private SymbolProcessor() { + // Prevent instantiation + } + + /** + * Result class to hold processed symbols. + */ + public static class SymbolProcessingResult { + private final List clients; + private final List functions; + private final List typeDefs; + + public SymbolProcessingResult() { + this.clients = new ArrayList<>(); + this.functions = new ArrayList<>(); + this.typeDefs = new ArrayList<>(); + } + + public List getClients() { + return clients; + } + + public List getFunctions() { + return functions; + } + + public List getTypeDefs() { + return typeDefs; + } + } + + /** + * Processes module symbols and returns structured result with extracted data. + * + * @param semanticModel the semantic model containing the symbols + * @param moduleInfo the module information + * @param org the organization name + * @param packageName the package name + * @return SymbolProcessingResult containing clients, functions, and typedefs + */ + public static SymbolProcessingResult processModuleSymbols(SemanticModel semanticModel, + ModuleInfo moduleInfo, + String org, + String packageName) { + SymbolProcessingResult result = new SymbolProcessingResult(); + + for (Symbol symbol : semanticModel.moduleSymbols()) { + switch (symbol.kind()) { + case CLASS: + ClassSymbolHandler.process( + (ClassSymbol) symbol, + semanticModel, + moduleInfo, + org, + packageName + ).ifPresent(obj -> { + if (obj instanceof Client client) { + result.getClients().add(client); + } else if (obj instanceof TypeDef typeDef) { + result.getTypeDefs().add(typeDef); + } + }); + break; + + case FUNCTION: + FunctionSymbolHandler.process( + (FunctionSymbol) symbol, + semanticModel, + moduleInfo, + org, + packageName + ).ifPresent(result.getFunctions()::add); + break; + + case TYPE_DEFINITION: + TypeDefSymbolHandler.process( + (TypeDefinitionSymbol) symbol, + org, + packageName + ).ifPresent(result.getTypeDefs()::add); + break; + + case ENUM: + EnumSymbolHandler.process( + (EnumSymbol) symbol, + org, + packageName + ).ifPresent(result.getTypeDefs()::add); + break; + + case CONSTANT: + ConstantSymbolHandler.process( + (ConstantSymbol) symbol, + org, + packageName + ).ifPresent(result.getTypeDefs()::add); + break; + + default: + // Skip other symbol types + break; + } + } + + return result; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeDefDataBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeDefDataBuilder.java new file mode 100644 index 0000000000..16c7e2928e --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeDefDataBuilder.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import io.ballerina.compiler.api.symbols.ConstantSymbol; +import io.ballerina.compiler.api.symbols.Documentation; +import io.ballerina.compiler.api.symbols.EnumSymbol; +import io.ballerina.compiler.api.symbols.MapTypeSymbol; +import io.ballerina.compiler.api.symbols.RecordTypeSymbol; +import io.ballerina.compiler.api.symbols.StreamTypeSymbol; +import io.ballerina.compiler.api.symbols.TableTypeSymbol; +import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; +import io.ballerina.compiler.api.symbols.TypeDescKind; +import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.api.symbols.UnionTypeSymbol; +import io.ballerina.compiler.api.values.ConstantValue; +import io.ballerina.modelgenerator.commons.FieldData; +import io.ballerina.modelgenerator.commons.FunctionDataBuilder; +import io.ballerina.modelgenerator.commons.TypeDefData; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static io.ballerina.modelgenerator.commons.CommonUtils.getRawType; + +/** + * Builder class for constructing TypeDefData instances from various symbol types. + * Handles extraction of fields from records, unions, maps, tables, streams, enums, and constants. + * + * @since 1.0.1 + */ +public class TypeDefDataBuilder { + + private TypeDefDataBuilder() { + // Prevent instantiation + } + + /** + * Builds TypeDefData from a TypeDefinitionSymbol using FunctionDataBuilder's allMembers function. + * + * @param typeDefSymbol the type definition symbol + * @return TypeDefData instance + */ + public static TypeDefData buildFromTypeDefinition(TypeDefinitionSymbol typeDefSymbol) { + String typeName = typeDefSymbol.getName().orElse(""); + String typeDescription = typeDefSymbol.documentation() + .flatMap(Documentation::description) + .orElse(""); + + TypeSymbol typeDescriptor = typeDefSymbol.typeDescriptor(); + TypeSymbol rawType = getRawType(typeDescriptor); + TypeDescKind typeKind = rawType.typeKind(); + + // Use FunctionDataBuilder.allMembers to extract all type information + Map typeMap = new LinkedHashMap<>(); + FunctionDataBuilder.allMembers(typeMap, typeDescriptor); + + // Determine type category + TypeDefData.TypeCategory typeCategory = switch (typeKind) { + case RECORD -> TypeDefData.TypeCategory.RECORD; + case UNION -> TypeDefData.TypeCategory.UNION; + case OBJECT -> TypeDefData.TypeCategory.CLASS; + case ERROR -> TypeDefData.TypeCategory.ERROR; + default -> TypeDefData.TypeCategory.OTHER; + }; + + // Extract fields based on type + List fields = new ArrayList<>(); + String baseType = null; + + baseType = switch (typeKind) { + case RECORD -> { + extractRecordFields((RecordTypeSymbol) rawType, fields); + yield typeDescriptor.signature(); + } + case UNION -> { + extractUnionMembers((UnionTypeSymbol) rawType, fields); + yield typeDescriptor.signature(); + } + case MAP -> extractMapFields((MapTypeSymbol) rawType, fields); + case TABLE -> { + extractTableFields((TableTypeSymbol) rawType, fields); + yield typeDescriptor.signature(); + } + case STREAM -> { + extractStreamFields((StreamTypeSymbol) rawType, fields); + yield typeDescriptor.signature(); + } + default -> typeDescriptor.signature(); + }; + + return new TypeDefData(typeName, typeDescription, typeCategory, fields, baseType); + } + + /** + * Builds TypeDefData for an Enum symbol. + * + * @param enumSymbol the enum symbol + * @return TypeDefData instance + */ + public static TypeDefData buildFromEnum(EnumSymbol enumSymbol) { + String typeName = enumSymbol.getName().orElse(""); + String typeDescription = enumSymbol.documentation() + .flatMap(Documentation::description) + .orElse(""); + + List enumMembers = new ArrayList<>(); + + // Extract enum members + for (ConstantSymbol member : enumSymbol.members()) { + String memberName = member.getName().orElse(""); + String memberDescription = member.documentation() + .flatMap(Documentation::description) + .orElse(""); + + // Get the constant value if available + String memberValue = memberName; + Object constValueObj = member.constValue(); + if (constValueObj instanceof ConstantValue constantValue) { + Object value = constantValue.value(); + if (value != null) { + memberValue = value.toString(); + } + } + + FieldData.FieldType fieldType = new FieldData.FieldType(memberValue); + enumMembers.add(new FieldData(memberName, memberDescription, fieldType, false)); + } + + return new TypeDefData(typeName, typeDescription, TypeDefData.TypeCategory.ENUM, enumMembers, null); + } + + /** + * Builds TypeDefData for a Constant symbol. + * + * @param constantSymbol the constant symbol + * @return TypeDefData instance + */ + public static TypeDefData buildFromConstant(ConstantSymbol constantSymbol) { + String typeName = constantSymbol.getName().orElse(""); + String typeDescription = constantSymbol.documentation() + .flatMap(Documentation::description) + .orElse(""); + + // Get the constant's type and value + TypeSymbol typeSymbol = constantSymbol.typeDescriptor(); + + // Get the constant value if available + String constantValue = typeSymbol.signature(); + Object constValueObj = constantSymbol.constValue(); + if (constValueObj instanceof ConstantValue constantVal) { + Object value = constantVal.value(); + if (value != null) { + constantValue = value.toString(); + } + } + + return new TypeDefData(typeName, typeDescription, TypeDefData.TypeCategory.CONSTANT, + new ArrayList<>(), constantValue); + } + + private static void extractRecordFields(RecordTypeSymbol recordType, List fields) { + recordType.fieldDescriptors().forEach((key, fieldSymbol) -> { + String fieldName = fieldSymbol.getName().orElse(key); + String fieldDescription = fieldSymbol.documentation() + .flatMap(Documentation::description) + .orElse(""); + TypeSymbol fieldTypeSymbol = fieldSymbol.typeDescriptor(); + boolean optional = fieldSymbol.isOptional() || fieldSymbol.hasDefaultValue(); + + FieldData.FieldType fieldType = new FieldData.FieldType(fieldTypeSymbol.signature(), fieldTypeSymbol); + fields.add(new FieldData(fieldName, fieldDescription, fieldType, optional)); + }); + + // Handle rest field if present + recordType.restTypeDescriptor().ifPresent(restType -> { + FieldData.FieldType fieldType = new FieldData.FieldType(restType.signature(), restType); + fields.add(new FieldData("", "Rest field", fieldType, false)); + }); + } + + private static void extractUnionMembers(UnionTypeSymbol unionType, List fields) { + unionType.memberTypeDescriptors().forEach(memberType -> { + String memberTypeName = memberType.signature(); + FieldData.FieldType fieldType = new FieldData.FieldType(memberTypeName, memberType); + fields.add(new FieldData(memberTypeName, "Union member", fieldType, false)); + }); + } + + private static String extractMapFields(MapTypeSymbol mapType, List fields) { + TypeSymbol constraintType = mapType.typeParam(); + String constraintTypeName = constraintType.signature(); + FieldData.FieldType fieldType = new FieldData.FieldType(constraintTypeName, constraintType); + fields.add(new FieldData("constraint", "Map constraint type", fieldType, false)); + return "map<" + constraintTypeName + ">"; + } + + private static void extractTableFields(TableTypeSymbol tableType, List fields) { + TypeSymbol rowType = tableType.rowTypeParameter(); + FieldData.FieldType rowFieldType = new FieldData.FieldType(rowType.signature(), rowType); + fields.add(new FieldData("rowType", "Table row type", rowFieldType, false)); + + // Extract key constraint if present + tableType.keyConstraintTypeParameter().ifPresent(keyType -> { + FieldData.FieldType keyFieldType = new FieldData.FieldType(keyType.signature(), keyType); + fields.add(new FieldData("keyConstraint", "Table key constraint", keyFieldType, false)); + }); + } + + private static void extractStreamFields(StreamTypeSymbol streamType, List fields) { + TypeSymbol streamTypeParam = streamType.typeParameter(); + FieldData.FieldType streamFieldType = new FieldData.FieldType(streamTypeParam.signature(), streamTypeParam); + fields.add(new FieldData("valueType", "Stream value type", streamFieldType, false)); + + TypeSymbol completionType = streamType.completionValueTypeParameter(); + FieldData.FieldType completionFieldType = new FieldData.FieldType(completionType.signature(), completionType); + fields.add(new FieldData("completionType", "Stream completion type", completionFieldType, false)); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeDefSymbolHandler.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeDefSymbolHandler.java new file mode 100644 index 0000000000..37e1c22e85 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeDefSymbolHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import io.ballerina.compiler.api.symbols.Qualifier; +import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; +import io.ballerina.flowmodelgenerator.core.model.TypeDef; +import io.ballerina.modelgenerator.commons.TypeDefData; + +import java.util.Optional; + +/** + * Handler for processing TYPE_DEFINITION symbols from semantic model. + * + * @since 1.0.1 + */ +public class TypeDefSymbolHandler { + + private TypeDefSymbolHandler() { + } + + public static Optional process(TypeDefinitionSymbol typeDefSymbol, + String org, + String packageName) { + if (!typeDefSymbol.qualifiers().contains(Qualifier.PUBLIC)) { + return Optional.empty(); + } + + TypeDefData typeDefData = TypeDefDataBuilder.buildFromTypeDefinition(typeDefSymbol); + TypeDef typeDef = LibraryModelConverter.typeDefDataToModel(typeDefData, org, packageName); + return Optional.of(typeDef); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeLinkBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeLinkBuilder.java new file mode 100644 index 0000000000..da934cba2b --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeLinkBuilder.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import io.ballerina.flowmodelgenerator.core.model.TypeLink; + +import java.util.ArrayList; +import java.util.List; + +/** + * Builder class for creating TypeLink objects. + * Handles union types and internal/external categorization. + * + * @since 1.0.1 + */ +public class TypeLinkBuilder { + + private TypeLinkBuilder() { + // Prevent instantiation + } + + /** + * Creates internal type links for the given record name. + * Handles union types by splitting on "|" and creating separate links. + * + * @param recordName the record name (may contain union types like "TypeA|TypeB") + * @return list of internal TypeLink objects + */ + public static List createInternalLinks(String recordName) { + List links = new ArrayList<>(); + + if (recordName == null || recordName.isEmpty()) { + return links; + } + + // Split by "|" to handle union types + String[] typeNames = recordName.split("\\|"); + for (String typeName : typeNames) { + String trimmedName = typeName.trim(); + if (!trimmedName.isEmpty()) { + TypeLink link = new TypeLink(); + link.setCategory("internal"); + link.setRecordName(trimmedName); + links.add(link); + } + } + + return links; + } + + /** + * Creates type links from import statements. + * Handles union types and determines internal/external category for each type. + * + * @param importStatements comma-separated import statements + * @param recordName the record name (may contain union types like "TypeA|TypeB") + * @param currentOrg the current package organization + * @param currentPackage the current package name + * @return list of TypeLink objects with appropriate categories and library names + */ + public static List createLinksFromImports(String importStatements, + String recordName, + String currentOrg, + String currentPackage) { + List links = new ArrayList<>(); + + if (importStatements == null || importStatements.trim().isEmpty()) { + return links; + } + + // Split recordName by "|" to handle union types + String[] recordNames = recordName != null ? recordName.split("\\|") : new String[]{null}; + + // Split by comma to get individual import statements + String[] imports = importStatements.split(","); + for (String importStmt : imports) { + String packagePath = importStmt.trim(); + + if (packagePath.isEmpty()) { + continue; + } + + // Handle "as alias" part if present + int asIndex = packagePath.indexOf(" as "); + if (asIndex > 0) { + packagePath = packagePath.substring(0, asIndex).trim(); + } + + String[] parts = packagePath.split("/"); + if (parts.length >= 2) { + String org = parts[0]; + String pkgName = parts[1]; + + // Skip predefined lang libs + if (isPredefinedLangLib(org, pkgName)) { + continue; + } + + // Determine if it's internal or external + boolean isInternal = org.equals(currentOrg) && pkgName.equals(currentPackage); + String category = isInternal ? "internal" : "external"; + String libraryName = isInternal ? null : org + "/" + pkgName; + + // Create a separate link for each type in the union + for (String singleRecordName : recordNames) { + String trimmedName = singleRecordName.trim(); + if (!trimmedName.isEmpty()) { + TypeLink link = new TypeLink(category, trimmedName, libraryName); + links.add(link); + } + } + } + } + + return links; + } + + /** + * Filters links to only include internal or external categories. + * + * @param links the list of links to filter + * @return filtered list containing only internal or external links + */ + public static List filterInternalExternal(List links) { + if (links == null || links.isEmpty()) { + return new ArrayList<>(); + } + + return links.stream() + .filter(link -> "internal".equals(link.getCategory()) || + "external".equals(link.getCategory())) + .toList(); + } + + /** + * Checks if a module is a predefined language library. + * + * @param orgName the organization name + * @param packageName the package name + * @return true if it's a predefined lang lib, false otherwise + */ + private static boolean isPredefinedLangLib(String orgName, String packageName) { + return "ballerina".equals(orgName) && + packageName.startsWith("lang.") && + !packageName.equals("lang.annotations"); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeSymbolExtractor.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeSymbolExtractor.java new file mode 100644 index 0000000000..2e349c7ae2 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/TypeSymbolExtractor.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core; + +import io.ballerina.compiler.api.symbols.ArrayTypeSymbol; +import io.ballerina.compiler.api.symbols.ModuleSymbol; +import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; +import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.api.symbols.UnionTypeSymbol; +import io.ballerina.flowmodelgenerator.core.model.TypeLink; + +import java.util.List; +import java.util.Optional; + +/** + * Utility class for extracting type information from TypeSymbol instances. + * Handles type name extraction, import statement generation, and type link creation. + * + * @since 1.0.1 + */ +public class TypeSymbolExtractor { + + private TypeSymbolExtractor() { + // Prevent instantiation + } + + /** + * Extracts the record name from TypeSymbol handling Union, TypeReference, Array, and basic types. + * + * @param typeSymbol the type symbol to extract from + * @return the extracted type name, or empty string if null + */ + public static String extractRecordName(TypeSymbol typeSymbol) { + if (typeSymbol == null) { + return ""; + } + + switch (typeSymbol.typeKind()) { + case UNION: + // Handle union types + UnionTypeSymbol unionType = (UnionTypeSymbol) typeSymbol; + List memberTypes = new java.util.ArrayList<>(); + for (TypeSymbol member : unionType.memberTypeDescriptors()) { + memberTypes.add(extractRecordName(member)); + } + return String.join("|", memberTypes); + + case TYPE_REFERENCE: + // Handle type references - get the definition name from the referenced type + TypeReferenceTypeSymbol typeRef = (TypeReferenceTypeSymbol) typeSymbol; + return typeRef.definition().getName() + .or(() -> typeRef.typeDescriptor().getName()) + .orElse(typeSymbol.typeKind().getName()); + + case ARRAY: + // Handle array types - recursively get the member type name + ArrayTypeSymbol arrayType = (ArrayTypeSymbol) typeSymbol; + return extractRecordName(arrayType.memberTypeDescriptor()) + "[]"; + + default: + // For other types, use getName() directly + return typeSymbol.getName().orElse(typeSymbol.signature()); + } + } + + /** + * Extracts import statements from a TypeSymbol by analyzing its module information. + * Returns a comma-separated string of package paths (e.g., "org/package, org2/package2"). + * + * @param typeSymbol the type symbol to extract import statements from + * @return comma-separated package paths, or null if no module found + */ + public static String extractImportStatements(TypeSymbol typeSymbol) { + if (typeSymbol == null) { + return null; + } + + // Get the module information from the type symbol + Optional moduleOpt = typeSymbol.getModule(); + if (moduleOpt.isEmpty()) { + return null; + } + + ModuleSymbol moduleSymbol = moduleOpt.get(); + + // Get org and module name + String org = moduleSymbol.id().orgName(); + String moduleName = moduleSymbol.id().moduleName(); + + // Return the package path + return org + "/" + moduleName; + } + + /** + * Extracts type links from import statements string combined with type symbol. + * + * @param importStatements comma-separated import statements + * @param recordName the record name to link to + * @param currentOrg the current package organization + * @param currentPackage the current package name + * @return List of TypeLink objects + */ + public static List extractTypeLinks(String importStatements, + String recordName, + String currentOrg, + String currentPackage) { + return TypeLinkBuilder.createLinksFromImports(importStatements, recordName, currentOrg, currentPackage); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Client.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Client.java new file mode 100644 index 0000000000..60f7a7b3f7 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Client.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a client class or connector with its methods. + * + * @since 1.0.1 + */ +public class Client { + private String name; + private String description; + private List functions; + + public Client(String name, String description) { + this.name = name; + this.description = description; + this.functions = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getFunctions() { + return functions; + } + + public void setFunctions(List functions) { + this.functions = functions; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/EnumValue.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/EnumValue.java new file mode 100644 index 0000000000..ed5c5b93bf --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/EnumValue.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +/** + * Represents an enum member value. + * + * @since 1.0.1 + */ +public class EnumValue implements TypeDefMember { + private String name; + private String description; + + public EnumValue() { + } + + public EnumValue(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Field.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Field.java new file mode 100644 index 0000000000..f3624f84af --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Field.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +/** + * Represents a record field or enum member. + * + * @since 1.0.1 + */ +public class Field { + private String name; + private String description; + private Type type; + private String defaultValue; + private boolean optional; + + public Field() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public boolean isOptional() { + return optional; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Library.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Library.java new file mode 100644 index 0000000000..9378760390 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Library.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +import java.util.List; + +/** + * Represents a library/package with its metadata and symbols. + * + * @since 1.0.1 + */ +public class Library { + private String name; + private String description; + private List clients; + private List functions; + private List typeDefs; + private List services; + + public Library(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getClients() { + return clients; + } + + public void setClients(List clients) { + this.clients = clients; + } + + public List getFunctions() { + return functions; + } + + public void setFunctions(List functions) { + this.functions = functions; + } + + public List getTypeDefs() { + return typeDefs; + } + + public void setTypeDefs(List typeDefs) { + this.typeDefs = typeDefs; + } + + public List getServices() { + return services; + } + + public void setServices(List services) { + this.services = services; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/LibraryFunction.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/LibraryFunction.java new file mode 100644 index 0000000000..4f9bac94e3 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/LibraryFunction.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a function or method. + * + * @since 1.0.1 + */ +public class LibraryFunction { + private String name; + private String type; + private String description; + private String accessor; + private List paths; + private List parameters; + @SerializedName("return") + private Return returnInfo; + + public LibraryFunction() { + this.parameters = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getAccessor() { + return accessor; + } + + public void setAccessor(String accessor) { + this.accessor = accessor; + } + + public List getPaths() { + return paths; + } + + public void setPaths(List paths) { + this.paths = paths; + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } + + public Return getReturnInfo() { + return returnInfo; + } + + public void setReturnInfo(Return returnInfo) { + this.returnInfo = returnInfo; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Listener.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Listener.java new file mode 100644 index 0000000000..2447525396 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Listener.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a listener definition. + * + * @since 1.0.1 + */ +public class Listener { + private String name; + private List parameters; + + public Listener() { + this.parameters = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/ModelToJsonConverter.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/ModelToJsonConverter.java new file mode 100644 index 0000000000..675b940938 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/ModelToJsonConverter.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; + +import java.util.List; + +/** + * Utility class to convert model POJOs to JSON at the API boundary. + * + * @since 1.0.1 + */ +public class ModelToJsonConverter { + + private static final Gson GSON = new GsonBuilder() + .create(); + + private ModelToJsonConverter() { + // Prevent instantiation + } + + /** + * Converts a list of libraries to JsonArray. + * + * @param libraries the list of libraries + * @return JsonArray representation + */ + public static JsonArray librariesToJson(List libraries) { + JsonArray result = new JsonArray(); + for (Library library : libraries) { + result.add(libraryToJson(library)); + } + return result; + } + + /** + * Converts a single library to JsonElement. + * + * @param library the library + * @return JsonElement representation + */ + public static JsonElement libraryToJson(Library library) { + return GSON.toJsonTree(library); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Parameter.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Parameter.java new file mode 100644 index 0000000000..548b6591b9 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Parameter.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +/** + * Represents a function or method parameter. + * + * @since 1.0.1 + */ +public class Parameter { + private String name; + private String description; + private Type type; + private boolean optional; + private String defaultValue; + + public Parameter() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public boolean isOptional() { + return optional; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/PathElement.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/PathElement.java new file mode 100644 index 0000000000..6dd287b4fd --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/PathElement.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +/** + * Sealed interface representing a path element in a resource function. + * A path element can be either a static string path or a parameterized path segment. + * + * @since 1.0.1 + */ +public sealed interface PathElement permits StringPath, PathSegment { +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/PathSegment.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/PathSegment.java new file mode 100644 index 0000000000..cc33335298 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/PathSegment.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +/** + * Represents a path parameter in a resource function. + * + * @since 1.0.1 + */ +public final class PathSegment implements PathElement { + private String name; + private String type; + + public PathSegment() { + } + + public PathSegment(String name, String type) { + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Return.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Return.java new file mode 100644 index 0000000000..a2db0da11e --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Return.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +/** + * Represents a function return type. + * + * @since 1.0.1 + */ +public class Return { + private Type type; + + public Return() { + } + + public Return(Type type) { + this.type = type; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Service.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Service.java new file mode 100644 index 0000000000..e1949ca869 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Service.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * Represents a service definition. + * + * @since 1.0.1 + */ +public class Service { + private String type; + @SerializedName("instructions") + private String instructions; + private Listener listener; + @SerializedName("methods") + private List methods; + + public Service() { + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getInstructions() { + return instructions; + } + + public void setInstructions(String instructions) { + this.instructions = instructions; + } + + public Listener getListener() { + return listener; + } + + public void setListener(Listener listener) { + this.listener = listener; + } + + public List getMethods() { + return methods; + } + + public void setMethods(List methods) { + this.methods = methods; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/ServiceRemoteFunction.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/ServiceRemoteFunction.java new file mode 100644 index 0000000000..6a7dd5f9ef --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/ServiceRemoteFunction.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a service method (remote or resource function). + * + * @since 1.0.1 + */ +public class ServiceRemoteFunction { + private String type; + private String description; + private List parameters; + @SerializedName("return") + private Return returnInfo; + private boolean optional; + + public ServiceRemoteFunction() { + this.parameters = new ArrayList<>(); + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } + + public Return getReturnInfo() { + return returnInfo; + } + + public void setReturnInfo(Return returnInfo) { + this.returnInfo = returnInfo; + } + + public boolean isOptional() { + return optional; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/StringPath.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/StringPath.java new file mode 100644 index 0000000000..7d8ee2af1c --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/StringPath.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +import com.google.gson.annotations.JsonAdapter; +import io.ballerina.flowmodelgenerator.core.model.adapters.StringPathAdapter; + +/** + * Represents a static string path segment in a resource function. + * + * @param value the string value of the path segment + * @since 1.0.1 + */ +@JsonAdapter(StringPathAdapter.class) +public record StringPath(String value) implements PathElement { +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Type.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Type.java new file mode 100644 index 0000000000..30645a6123 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/Type.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +import java.util.List; + +/** + * Represents a type reference with optional links. + * + * @since 1.0.1 + */ +public class Type { + private String name; + private List links; + + public Type() { + } + + public Type(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getLinks() { + return links; + } + + public void setLinks(List links) { + this.links = links; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeDef.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeDef.java new file mode 100644 index 0000000000..27e53eda00 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeDef.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a type definition (record, enum, union, class, constant, etc.). + * + * @since 1.0.1 + */ +public class TypeDef { + private String name; + private String description; + private String type; + private String value; + private Type varType; + private List fields; + private List members; + private List functions; + + public TypeDef() { + this.fields = new ArrayList<>(); + this.members = new ArrayList<>(); + this.functions = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Type getVarType() { + return varType; + } + + public void setVarType(Type varType) { + this.varType = varType; + } + + public List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + + public List getMembers() { + return members; + } + + public void setMembers(List members) { + this.members = members; + } + + public List getFunctions() { + return functions; + } + + public void setFunctions(List functions) { + this.functions = functions; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeDefMember.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeDefMember.java new file mode 100644 index 0000000000..5809f46c20 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeDefMember.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +/** + * Marker interface for TypeDef member types (EnumValue or UnionValue). + * + * @since 1.0.1 + */ +public interface TypeDefMember { +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeLink.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeLink.java new file mode 100644 index 0000000000..780a4750ee --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/TypeLink.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +/** + * Represents a link to a type definition in the same or different library. + * + * @since 1.0.1 + */ +public class TypeLink { + private String category; + private String recordName; + private String libraryName; + + public TypeLink() { + } + + public TypeLink(String category, String recordName, String libraryName) { + this.category = category; + this.recordName = recordName; + this.libraryName = libraryName; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getRecordName() { + return recordName; + } + + public void setRecordName(String recordName) { + this.recordName = recordName; + } + + public String getLibraryName() { + return libraryName; + } + + public void setLibraryName(String libraryName) { + this.libraryName = libraryName; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/UnionValue.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/UnionValue.java new file mode 100644 index 0000000000..71abfd2aaa --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/UnionValue.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model; + +/** + * Represents a union member value. + * + * @since 1.0.1 + */ +public class UnionValue implements TypeDefMember { + private String name; + private Type type; + + public UnionValue() { + } + + public UnionValue(String name, Type type) { + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } +} diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/adapters/StringPathAdapter.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/adapters/StringPathAdapter.java new file mode 100644 index 0000000000..02681e3c25 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/adapters/StringPathAdapter.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model.adapters; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.ballerina.flowmodelgenerator.core.model.StringPath; + +import java.io.IOException; + +/** + * Gson TypeAdapter for StringPath that serializes it as a plain string value. + * + * @since 1.0.1 + */ +public class StringPathAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, StringPath value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + out.value(value.value()); + } + + @Override + public StringPath read(JsonReader in) throws IOException { + String value = in.nextString(); + return new StringPath(value); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/CopilotLibraryService.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/CopilotLibraryService.java index 234ca5eeda..934a224ed5 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/CopilotLibraryService.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/CopilotLibraryService.java @@ -19,41 +19,11 @@ package io.ballerina.flowmodelgenerator.extension; import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.stream.JsonReader; -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.api.symbols.ArrayTypeSymbol; -import io.ballerina.compiler.api.symbols.ClassSymbol; -import io.ballerina.compiler.api.symbols.ConstantSymbol; -import io.ballerina.compiler.api.symbols.Documentation; -import io.ballerina.compiler.api.symbols.EnumSymbol; -import io.ballerina.compiler.api.symbols.FunctionSymbol; -import io.ballerina.compiler.api.symbols.MapTypeSymbol; -import io.ballerina.compiler.api.symbols.ModuleSymbol; -import io.ballerina.compiler.api.symbols.Qualifier; -import io.ballerina.compiler.api.symbols.RecordTypeSymbol; -import io.ballerina.compiler.api.symbols.StreamTypeSymbol; -import io.ballerina.compiler.api.symbols.Symbol; -import io.ballerina.compiler.api.symbols.TableTypeSymbol; -import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; -import io.ballerina.compiler.api.symbols.TypeDescKind; -import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; -import io.ballerina.compiler.api.symbols.TypeSymbol; -import io.ballerina.compiler.api.symbols.UnionTypeSymbol; -import io.ballerina.compiler.api.values.ConstantValue; -import io.ballerina.flowmodelgenerator.extension.request.GetAllLibrariesRequest; +import io.ballerina.flowmodelgenerator.core.CopilotLibraryManager; +import io.ballerina.flowmodelgenerator.core.model.Library; +import io.ballerina.flowmodelgenerator.core.model.ModelToJsonConverter; import io.ballerina.flowmodelgenerator.extension.request.GetSelectedLibrariesRequest; import io.ballerina.flowmodelgenerator.extension.response.GetAllLibrariesResponse; -import io.ballerina.modelgenerator.commons.FieldData; -import io.ballerina.modelgenerator.commons.FunctionData; -import io.ballerina.modelgenerator.commons.FunctionDataBuilder; -import io.ballerina.modelgenerator.commons.ModuleInfo; -import io.ballerina.modelgenerator.commons.PackageUtil; -import io.ballerina.modelgenerator.commons.ParameterData; -import io.ballerina.modelgenerator.commons.ReturnTypeData; -import io.ballerina.modelgenerator.commons.TypeDefData; import org.ballerinalang.annotation.JavaSPIService; import org.ballerinalang.langserver.commons.service.spi.ExtendedLanguageServerService; import org.ballerinalang.langserver.commons.workspace.WorkspaceManager; @@ -61,30 +31,13 @@ import org.eclipse.lsp4j.jsonrpc.services.JsonSegment; import org.eclipse.lsp4j.services.LanguageServer; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -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; import java.util.concurrent.CompletableFuture; -import static io.ballerina.modelgenerator.commons.CommonUtils.getRawType; -import static io.ballerina.modelgenerator.commons.FunctionDataBuilder.REST_RESOURCE_PATH; - /** * Service for managing Copilot library operations. - * Provides streaming JSON processing for efficient memory usage. + * Provides API endpoints for loading library information. + * Works with POJOs internally and converts to JSON only at the API boundary. * * @since 1.0.1 */ @@ -92,16 +45,6 @@ @JsonSegment("copilotLibraryManager") public class CopilotLibraryService implements ExtendedLanguageServerService { - private static final String CORE_CONTEXT_JSON_PATH = "/copilot/context.json"; - private static final String HEALTHCARE_CONTEXT_JSON_PATH = "/copilot/healthcare-context.json"; - private static final String GENERIC_SERVICES_JSON_PATH = "/copilot/generic-services.json"; - private static final String MODE_CORE = "CORE"; - private static final String MODE_HEALTHCARE = "HEALTHCARE"; - - // JSON field names - private static final String FIELD_NAME = "name"; - private static final String FIELD_DESCRIPTION = "description"; - @Override public void init(LanguageServer langServer, WorkspaceManager workspaceManager) { // Initialization logic if needed @@ -109,53 +52,17 @@ public void init(LanguageServer langServer, WorkspaceManager workspaceManager) { @Override public Class getRemoteInterface() { - return null; } @JsonRequest - public CompletableFuture getLibrariesList(GetAllLibrariesRequest request) { - - return CompletableFuture.supplyAsync(() -> { - try { - String mode = request.mode() != null ? request.mode() : MODE_CORE; - JsonArray libraries = loadLibrariesFromContext(null, true, mode); - return createResponse(libraries); - } catch (Exception e) { - throw new RuntimeException("Failed to load libraries: " + e.getMessage(), e); - } - }); - } - - @JsonRequest - public CompletableFuture getFilteredLibraries(GetSelectedLibrariesRequest request) { - + public CompletableFuture getLibrariesList() { return CompletableFuture.supplyAsync(() -> { try { - String[] libraryNames = request.libNames(); - if (libraryNames == null || libraryNames.length == 0) { - // Return empty response if no library names provided - return createResponse(new JsonArray()); - } - - String mode = request.mode() != null ? request.mode() : MODE_CORE; - // Convert to Set for efficient lookup during streaming - Set requestedLibraries = new HashSet<>(Arrays.asList(libraryNames)); - JsonArray filteredLibraries = loadLibrariesFromContext(requestedLibraries, false, mode); - return createResponse(filteredLibraries); - } catch (Exception e) { - throw new RuntimeException("Failed to load filtered libraries: " + e.getMessage(), e); - } - }); - } - - @JsonRequest - public CompletableFuture getLibrariesListFromSearchIndex() { - - return CompletableFuture.supplyAsync(() -> { - try { - JsonArray libraries = loadLibrariesFromDatabase(); - return createResponse(libraries); + CopilotLibraryManager manager = new CopilotLibraryManager(); + List libraries = manager.loadLibrariesFromDatabase(); + JsonArray librariesJson = ModelToJsonConverter.librariesToJson(libraries); + return createResponse(librariesJson); } catch (Exception e) { throw new RuntimeException("Failed to load libraries from database: " + e.getMessage(), e); } @@ -163,1424 +70,26 @@ public CompletableFuture getLibrariesListFromSearchInde } @JsonRequest - public CompletableFuture getFilteredLibrariesFromSemanticModel - (GetSelectedLibrariesRequest request) { - + public CompletableFuture getFilteredLibraries( + GetSelectedLibrariesRequest request) { return CompletableFuture.supplyAsync(() -> { try { - String[] libraryNames = request.libNames(); - if (libraryNames == null || libraryNames.length == 0) { - // Return empty response if no library names provided + if (request.libNames() == null || request.libNames().length == 0) { return createResponse(new JsonArray()); } - - JsonArray filteredLibraries = loadFilteredLibrariesFromSemanticModel(libraryNames); - return createResponse(filteredLibraries); + CopilotLibraryManager manager = new CopilotLibraryManager(); + List libraries = manager.loadFilteredLibraries(request.libNames()); + JsonArray librariesJson = ModelToJsonConverter.librariesToJson(libraries); + return createResponse(librariesJson); } catch (Exception e) { throw new RuntimeException("Failed to load filtered libraries: " + e.getMessage(), e); } }); } - /** - * Loads libraries from the context.json file using streaming JSON parsing with optional filtering. - * - * @param requestedLibraries Set of library names to filter by, or null to load all libraries - * @param limitedFields whether to return only name and description fields (true) or full objects (false) - * @param mode The mode to determine which context file to read ("CORE" or "HEALTHCARE") - * @return JsonArray containing library information - * @throws IOException if file reading fails - */ - private JsonArray loadLibrariesFromContext(Set requestedLibraries, boolean limitedFields, String mode) - throws IOException { - - JsonArray libraries = new JsonArray(); - - try (InputStream inputStream = getContextInputStream(mode); - InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); - JsonReader jsonReader = new JsonReader(reader)) { - - processLibraryArray(jsonReader, libraries, requestedLibraries, limitedFields); - } - - return libraries; - } - - /** - * Gets the input stream for the context file based on the mode. - * - * @param mode The mode to determine which context file to read - * @return InputStream for the context file - * @throws IOException if file not found - */ - private InputStream getContextInputStream(String mode) throws IOException { - - String contextPath; - if (MODE_HEALTHCARE.equals(mode)) { - contextPath = HEALTHCARE_CONTEXT_JSON_PATH; - } else { - contextPath = CORE_CONTEXT_JSON_PATH; // Default to CORE - } - - InputStream inputStream = CopilotLibraryService.class.getResourceAsStream(contextPath); - if (inputStream == null) { - throw new IOException("Context file not found: " + contextPath); - } - return inputStream; - } - - /** - * Processes the JSON array containing library information with optional filtering. - * - * @param jsonReader the JSON reader - * @param libraries the array to populate with library data - * @param requestedLibraries Set of library names to filter by, or null to include all libraries - * @param limitedFields whether to return only name and description fields (true) or full objects (false) - * @throws IOException if JSON parsing fails - */ - private void processLibraryArray(JsonReader jsonReader, JsonArray libraries, Set requestedLibraries, - boolean limitedFields) throws IOException { - - jsonReader.beginArray(); - - while (jsonReader.hasNext()) { - JsonObject libraryInfo = parseLibraryObject(jsonReader, limitedFields); - if (isValidLibrary(libraryInfo) && shouldIncludeLibrary(libraryInfo, requestedLibraries)) { - libraries.add(libraryInfo); - } - } - - jsonReader.endArray(); - } - - /** - * Parses a single library object from the JSON stream. - * - * @param jsonReader the JSON reader - * @param limitedFields whether to return only name and description fields (true) or full objects (false) - * @return JsonObject representing the library - * @throws IOException if JSON parsing fails - */ - private JsonObject parseLibraryObject(JsonReader jsonReader, boolean limitedFields) throws IOException { - - JsonObject libraryInfo = new JsonObject(); - jsonReader.beginObject(); - while (jsonReader.hasNext()) { - String fieldName = jsonReader.nextName(); - processLibraryField(jsonReader, libraryInfo, fieldName, limitedFields); - } - - jsonReader.endObject(); - - return libraryInfo; - } - - /** - * Processes a single field in the library object. - * - * @param jsonReader the JSON reader - * @param libraryInfo the library object to populate - * @param fieldName the current field name - * @throws IOException if JSON parsing fails - */ - private void processLibraryField(JsonReader jsonReader, JsonObject libraryInfo, String fieldName, - boolean limitedFields) throws IOException { - - if (limitedFields) { - // For limited fields, only process name and description - switch (fieldName) { - case FIELD_NAME: - String name = jsonReader.nextString(); - libraryInfo.addProperty(FIELD_NAME, name); - break; - case FIELD_DESCRIPTION: - String description = jsonReader.nextString(); - libraryInfo.addProperty(FIELD_DESCRIPTION, description); - break; - default: - jsonReader.skipValue(); // Skip other fields - break; - } - } else { - // Add all fields to the libraryInfo object as a exact copy. - JsonElement element = JsonParser.parseReader(jsonReader); - libraryInfo.add(fieldName, element); - } - - } - - /** - * Validates if a library object contains required information. - * - * @param libraryInfo the library object to validate - * @return true if valid, false otherwise - */ - private boolean isValidLibrary(JsonObject libraryInfo) { - - return libraryInfo.has(FIELD_NAME) && - libraryInfo.get(FIELD_NAME).getAsString() != null && - !libraryInfo.get(FIELD_NAME).getAsString().trim().isEmpty(); - } - - /** - * Determines if a library should be included based on the filter criteria. - * - * @param libraryInfo the library object to check - * @param requestedLibraries Set of library names to filter by, or null to include all libraries - * @return true if the library should be included, false otherwise - */ - private boolean shouldIncludeLibrary(JsonObject libraryInfo, Set requestedLibraries) { - - if (requestedLibraries == null || requestedLibraries.isEmpty()) { - return true; // Include all libraries if no filter specified - } - - if (!libraryInfo.has(FIELD_NAME)) { - return false; // Skip libraries without names - } - - String libraryName = libraryInfo.get(FIELD_NAME).getAsString(); - return requestedLibraries.contains(libraryName); - } - - /** - * Creates the response object with the loaded libraries. - * - * @param libraries the loaded libraries - * @return the response object - */ private GetAllLibrariesResponse createResponse(JsonArray libraries) { - GetAllLibrariesResponse response = new GetAllLibrariesResponse(); response.setLibraries(libraries); return response; } - - /** - * Loads libraries from the search-index database. - * Returns a JSON array with format: [{ "name": "org/package_name", "description": "..." }] - * Note: The same package may occur multiple times in the Package table with different versions. - * This method returns distinct org/package_name and description combinations. - * - * @return JsonArray containing libraries with name (org/package_name) and description - * @throws IOException if database file access fails - * @throws SQLException if database query fails - */ - private JsonArray loadLibrariesFromDatabase() throws IOException, SQLException { - - JsonArray result = new JsonArray(); - // Use LinkedHashMap to maintain insertion order and track unique packages - Map packageToDescriptionMap = new LinkedHashMap<>(); - - String dbPath = getDatabasePath(); - String sql = """ - SELECT DISTINCT org, package_name, description - FROM Package - WHERE org IS NOT NULL AND package_name IS NOT NULL - ORDER BY org, package_name; - """; - - try (Connection conn = DriverManager.getConnection(dbPath); - PreparedStatement stmt = conn.prepareStatement(sql); - ResultSet rs = stmt.executeQuery()) { - - while (rs.next()) { - String org = rs.getString("org"); - String packageName = rs.getString("package_name"); - String description = rs.getString("description"); - - // Create the full name as "org/package_name" - String fullName = org + "/" + packageName; - - // Store only if not already present (handles duplicates) - if (!packageToDescriptionMap.containsKey(fullName)) { - packageToDescriptionMap.put(fullName, description != null ? description : ""); - } - } - } - - // Convert the map to JSON array format - for (Map.Entry entry : packageToDescriptionMap.entrySet()) { - JsonObject packageObj = new JsonObject(); - packageObj.addProperty(FIELD_NAME, entry.getKey()); - packageObj.addProperty(FIELD_DESCRIPTION, entry.getValue()); - result.add(packageObj); - } - - return result; - } - - /** - * Loads filtered libraries using the semantic model. - * Returns a JSON array with package information including: - * - clients (client classes) with methods - * - Functions (module-level) - * - * @param libraryNames Array of library names in "org/package_name" format to filter - * @return JsonArray containing library details with clients and functions - */ - private JsonArray loadFilteredLibrariesFromSemanticModel(String[] libraryNames) { - - JsonArray result = new JsonArray(); - - for (String libraryName : libraryNames) { - // Parse library name "org/package_name" - String[] parts = libraryName.split("/"); - if (parts.length != 2) { - continue; // Skip invalid format - } - String org = parts[0]; - String packageName = parts[1]; - - // Create module info (use latest version by passing null) - ModuleInfo moduleInfo = new ModuleInfo(org, packageName, org + "/" + - packageName, null); - - // Get semantic model for the module - Optional optSemanticModel = PackageUtil.getSemanticModel(org, packageName); - if (optSemanticModel.isEmpty()) { - continue; // Skip if semantic model not found - } - - SemanticModel semanticModel = optSemanticModel.get(); - - // Try to get the package description from database - String description = getPackageDescriptionFromDatabase(org, packageName); - - // Create package object - JsonObject packageObj = new JsonObject(); - packageObj.addProperty(FIELD_NAME, libraryName); - packageObj.addProperty(FIELD_DESCRIPTION, description); - - // Arrays to hold clients, functions, typedefs, and services - JsonArray clients = new JsonArray(); - JsonArray functions = new JsonArray(); - JsonArray typedefs = new JsonArray(); - JsonArray services = new JsonArray(); - - // Load services from inbuilt triggers - JsonArray triggerServices = loadServicesFromInbuiltTriggers(libraryName); - triggerServices.forEach(services::add); - - // Load generic services - JsonArray genericServices = loadGenericServicesForLibrary(libraryName); - genericServices.forEach(services::add); - - for (Symbol symbol : semanticModel.moduleSymbols()) { - switch (symbol.kind()) { - case CLASS: - ClassSymbol classSymbol = (ClassSymbol) symbol; - - // Process only PUBLIC classes: CLIENT classes (connectors) and normal classes - if (classSymbol.qualifiers().contains(Qualifier.PUBLIC)) { - boolean isClient = classSymbol.qualifiers().contains(Qualifier.CLIENT); - String className = classSymbol.getName().orElse(isClient ? "Client" : "Class"); - - FunctionData.Kind classKind; - if (isClient) { - classKind = FunctionData.Kind.CONNECTOR; - } else { - classKind = FunctionData.Kind.CLASS_INIT; - } - - FunctionData classData = new FunctionDataBuilder() - .semanticModel(semanticModel) - .moduleInfo(moduleInfo) - .name(className) - .parentSymbol(classSymbol) - .functionResultKind(classKind) - .build(); - - JsonObject classObj = new JsonObject(); - classObj.addProperty("name", className); - classObj.addProperty("description", classData.description()); - - JsonArray methods = new JsonArray(); - - // Add the constructor/init function first - JsonObject constructorObj = functionDataToJson(classData, org, packageName); - constructorObj.addProperty("name", "init"); // Override name to "init" for constructor - methods.add(constructorObj); - - // Then add all other methods (remote functions, resource functions, etc.) - List classMethods = new FunctionDataBuilder() - .semanticModel(semanticModel) - .moduleInfo(moduleInfo) - .parentSymbolType(className) - .parentSymbol(classSymbol) - .buildChildNodes(); - - for (FunctionData method : classMethods) { - JsonObject methodObj = functionDataToJson(method, org, packageName); - methods.add(methodObj); - } - classObj.add("functions", methods); - - if (isClient) { - clients.add(classObj); - } else { - typedefs.add(classObj); - } - } - break; - - case FUNCTION: - FunctionSymbol functionSymbol = - (FunctionSymbol) symbol; - - if (functionSymbol.qualifiers().contains(Qualifier.PUBLIC)) { - FunctionData functionData = new FunctionDataBuilder() - .semanticModel(semanticModel) - .moduleInfo(moduleInfo) - .functionSymbol(functionSymbol) - .build(); - - JsonObject functionObj = functionDataToJson(functionData, org, packageName); - functions.add(functionObj); - } - break; - - case TYPE_DEFINITION: - TypeDefinitionSymbol typeDefSymbol = (TypeDefinitionSymbol) symbol; - - if (typeDefSymbol.qualifiers().contains(Qualifier.PUBLIC)) { - TypeDefData typeDefData = buildTypeDefDataFromSymbol(typeDefSymbol); - JsonObject typeDefObj = typeDefDataToJson(typeDefData, org, packageName); - typedefs.add(typeDefObj); - } - break; - - case ENUM: - EnumSymbol enumSymbol = (EnumSymbol) symbol; - - if (enumSymbol.qualifiers().contains(Qualifier.PUBLIC)) { - TypeDefData enumData = buildEnumTypeDefData(enumSymbol); - JsonObject enumObj = typeDefDataToJson(enumData, org, packageName); - typedefs.add(enumObj); - } - break; - - case CONSTANT: - ConstantSymbol constantSymbol = (ConstantSymbol) symbol; - - if (constantSymbol.qualifiers().contains(Qualifier.PUBLIC)) { - TypeDefData constantData = buildConstantTypeDefData(constantSymbol); - JsonObject constantObj = typeDefDataToJson(constantData, org, packageName); - - // Add varType using ConstantValue - JsonObject varTypeObj = new JsonObject(); - String varTypeName = ""; - Object constValue = constantSymbol.constValue(); - if (constValue instanceof ConstantValue constantValue) { - varTypeName = constantValue.valueType().typeKind().getName(); - } - - // Fallback to type descriptor if constValue is null or not ConstantValue - if (varTypeName.isEmpty()) { - TypeSymbol typeSymbol = constantSymbol.typeDescriptor(); - if (typeSymbol != null && !typeSymbol.signature().isEmpty()) { - varTypeName = typeSymbol.signature(); - } - } - - varTypeObj.addProperty("name", varTypeName); - constantObj.add("varType", varTypeObj); - - typedefs.add(constantObj); - } - break; - - default: - // Skip other symbol types - break; - } - } - - // Add collected data to package object - packageObj.add("clients", clients); - packageObj.add("functions", functions); - packageObj.add("typeDefs", typedefs); - packageObj.add("services", services); - - result.add(packageObj); - } - - return result; - } - - /** - * Gets the JDBC database path for the search-index.sqlite file. - * - * @return the JDBC database path - * @throws IOException if database file cannot be accessed - */ - private String getDatabasePath() throws IOException { - - String indexFileName = "search-index.sqlite"; - java.net.URL dbUrl = getClass().getClassLoader().getResource(indexFileName); - - if (dbUrl == null) { - throw new IOException("Database resource not found: " + indexFileName); - } - - // Copy database to temp directory - java.nio.file.Path tempDir = java.nio.file.Files.createTempDirectory("search-index"); - java.nio.file.Path tempFile = tempDir.resolve(indexFileName); - - try (InputStream inputStream = dbUrl.openStream()) { - java.nio.file.Files.copy(inputStream, tempFile); - } - - return "jdbc:sqlite:" + tempFile; - } - - /** - * Retrieves the package description from the database for a given org and package name. - * - * @param org the organization name - * @param packageName the package name - * @return the package description, or empty string if not found - */ - private String getPackageDescriptionFromDatabase(String org, String packageName) { - String description = ""; - - try { - String dbPath = getDatabasePath(); - String sql = """ - SELECT description - FROM Package - WHERE org = ? AND package_name = ? - ORDER BY id DESC - LIMIT 1; - """; - - try (Connection conn = DriverManager.getConnection(dbPath); - PreparedStatement stmt = conn.prepareStatement(sql)) { - - stmt.setString(1, org); - stmt.setString(2, packageName); - - try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - String desc = rs.getString("description"); - description = desc != null ? desc : ""; - } - } - } - } catch (IOException | SQLException e) { - throw new RuntimeException("Error retrieving package description for " + org + "/" + packageName + ": " + - e.getMessage()); - } - - return description; - } - - /** - * Converts FunctionData to JsonObject manually to avoid Gson reflection issues. - * - * @param functionData the function data to convert - * @param currentOrg the current package organization - * @param currentPackage the current package name - * @return JsonObject representation - */ - private JsonObject functionDataToJson(FunctionData functionData, String currentOrg, String currentPackage) { - JsonObject obj = new JsonObject(); - - // For resource functions, don't add "name" field, add "accessor" and "paths" instead - boolean isResourceFunction = functionData.kind() == FunctionData.Kind.RESOURCE; - - if (!isResourceFunction) { - obj.addProperty("name", functionData.name()); - } - - // Map function kind to human-readable type - String functionType = getFunctionTypeString(functionData.kind()); - obj.addProperty("type", functionType); - obj.addProperty("description", functionData.description()); - - // Add resource-specific fields for resource functions - if (isResourceFunction) { - // Extract accessor and paths from resourcePath - String resourcePath = functionData.resourcePath(); - if (resourcePath != null && !resourcePath.isEmpty()) { - JsonArray pathsArray = new JsonArray(); - - if (REST_RESOURCE_PATH.equals(resourcePath)) { - // For rest resource path, add a special path parameter - obj.addProperty("accessor", functionData.name()); - JsonObject pathParam = new JsonObject(); - pathParam.addProperty("name", "path"); - pathParam.addProperty("type", "..."); - pathsArray.add(pathParam); - } else { - // Parse normal resource paths - String[] pathParts = parseResourcePath(resourcePath); - obj.addProperty("accessor", pathParts[0]); - - // Parse paths array - String[] paths = pathParts[1].split("/"); - for (String path : paths) { - if (path.isEmpty()) { - continue; - } - // Check if it's a path parameter (starts with []) - if (path.startsWith("[") && path.endsWith("]")) { - // Path parameter: extract name and type - String paramContent = path.substring(1, path.length() - 1); - String[] paramParts = paramContent.split(":"); - JsonObject pathParam = new JsonObject(); - pathParam.addProperty("name", paramParts[0].trim()); - if (paramParts.length > 1) { - pathParam.addProperty("type", paramParts[1].trim()); - } else { - pathParam.addProperty("type", "string"); - } - pathsArray.add(pathParam); - } else { - // Regular path segment - pathsArray.add(path); - } - } - } - obj.add("paths", pathsArray); - } - } - - // Add parameters array if present - if (functionData.parameters() != null) { - JsonArray parametersArray = new JsonArray(); - for (Map.Entry entry : - functionData.parameters().entrySet()) { - JsonObject paramObj = parameterDataToJsonForArray(entry.getValue(), currentOrg, currentPackage); - parametersArray.add(paramObj); - } - obj.add("parameters", parametersArray); - } - - // Add return object with type - JsonObject returnObj = new JsonObject(); - - // Use ReturnTypeData if available, otherwise fall back to returnType string - if (functionData.returnTypeData() != null) { - ReturnTypeData returnTypeData = functionData.returnTypeData(); - - JsonObject returnTypeObj = new JsonObject(); - returnTypeObj.addProperty("name", returnTypeData.name()); - - returnObj.add("type", returnTypeObj); - } else if (functionData.returnType() != null) { - // Fallback to old format - JsonObject returnTypeObj = new JsonObject(); - returnTypeObj.addProperty("name", functionData.returnType()); - returnObj.add("type", returnTypeObj); - } - - obj.add("return", returnObj); - - return obj; - } - - /** - * Parses a resource path string to extract accessor and path. - * - * @param resourcePath the resource path string - * @return array with [accessor, path] - */ - private String[] parseResourcePath(String resourcePath) { - String trimmed = resourcePath.trim(); - int firstSlash = trimmed.indexOf('/'); - - if (firstSlash > 0) { - // Format: "accessor /path" - return new String[]{ - trimmed.substring(0, firstSlash).trim(), - trimmed.substring(firstSlash) - }; - } else if (firstSlash == 0) { - // Format: "/path" (default to "get") - return new String[]{"get", trimmed}; - } else { - // No path, just accessor - return new String[]{trimmed, ""}; - } - } - - /** - * Converts FunctionData.Kind to human-readable function type string. - * - * @param kind the function kind - * @return string representation for JSON - */ - private String getFunctionTypeString(FunctionData.Kind kind) { - if (kind == null) { - return "Normal Function"; - } - return switch (kind) { - case CLASS_INIT, CONNECTOR, LISTENER_INIT -> "Constructor"; - case REMOTE -> "Remote Function"; - case RESOURCE -> "Resource Function"; - default -> "Normal Function"; - }; - } - - /** - * Converts ParameterData to JsonObject for use in array format (with type links). - * - * @param paramData the parameter data to convert - * @param currentOrg the current package organization - * @param currentPackage the current package name - * @return JsonObject representation - */ - private JsonObject parameterDataToJsonForArray(ParameterData paramData, - String currentOrg, String currentPackage) { - JsonObject obj = new JsonObject(); - obj.addProperty("name", paramData.name()); - obj.addProperty("description", paramData.description()); - obj.addProperty("optional", paramData.optional()); - obj.addProperty("default", paramData.defaultValue()); - - // Add type object - if (paramData.type() != null) { - JsonObject typeObj = new JsonObject(); - String typeName = paramData.type(); - - if (!typeName.isEmpty()) { - typeObj.addProperty("name", typeName); - - // Add type links from import statements if available - if (paramData.importStatements() != null && !paramData.importStatements().isEmpty()) { - String recordName = extractRecordNameFromTypeSymbol(paramData.typeSymbol()); - JsonArray links = extractTypeLinks( - paramData.importStatements(), recordName, currentOrg, currentPackage); - - if (!links.isEmpty()) { - typeObj.add("links", links); - } - } - } - obj.add("type", typeObj); - } - return obj; - } - - /** - * Converts TypeDefData to JsonObject manually with org and package info for link generation. - */ - private JsonObject typeDefDataToJson(TypeDefData typeDefData, String currentOrg, String currentPackage) { - JsonObject obj = new JsonObject(); - obj.addProperty("name", typeDefData.name()); - obj.addProperty("description", typeDefData.description()); - obj.addProperty("type", typeDefData.type() != null ? typeDefData.type().getValue() : null); - - TypeDefData.TypeCategory category = typeDefData.type(); - - // Only add value for CONSTANT category - if (category == TypeDefData.TypeCategory.CONSTANT) { - obj.addProperty("value", typeDefData.baseType()); - } - - // Only add fields/members for specific categories (not for CONSTANT, ERROR, or OTHER) - if (typeDefData.fields() != null && - category != TypeDefData.TypeCategory.CONSTANT && - category != TypeDefData.TypeCategory.ERROR && - category != TypeDefData.TypeCategory.OTHER) { - - JsonArray fieldsArray = new JsonArray(); - - for (FieldData field : typeDefData.fields()) { - if (category == TypeDefData.TypeCategory.UNION) { - fieldsArray.add(field.name()); - } else { - JsonObject fieldObj; - - if (category == TypeDefData.TypeCategory.ENUM) { - // Enum members: only name and description - fieldObj = new JsonObject(); - fieldObj.addProperty("name", field.name()); - fieldObj.addProperty("description", field.description()); - } else { - fieldObj = fieldDataToJson(field, currentOrg, currentPackage); - } - - fieldsArray.add(fieldObj); - } - } - - // Use "members" for ENUM and UNION, "fields" for others - String arrayName = (category == TypeDefData.TypeCategory.ENUM || - category == TypeDefData.TypeCategory.UNION) ? "members" : "fields"; - obj.add(arrayName, fieldsArray); - } - - return obj; - } - - /** - * Converts FieldData to JsonObject manually with org and package info for link generation. - * Used for RECORD fields and other non-enum, non-constant field types. - */ - private JsonObject fieldDataToJson(FieldData fieldData, String currentOrg, String currentPackage) { - JsonObject obj = new JsonObject(); - obj.addProperty("name", fieldData.name()); - obj.addProperty("description", fieldData.description()); - obj.addProperty("optional", fieldData.optional()); - - // Add type object - if (fieldData.type() != null) { - JsonObject typeObj = new JsonObject(); - String typeName = fieldData.type().name(); - - // Get the formatted record name from TypeSymbol - String recordName = extractRecordNameFromTypeSymbol(fieldData.type().typeSymbol()); - - typeObj.addProperty("name", typeName); - - // Extract type links if we have the TypeSymbol and org/package info - if (currentOrg != null && currentPackage != null && fieldData.type().typeSymbol() != null) { - TypeSymbol typeSymbol = fieldData.type().typeSymbol(); - String importStatements = extractImportStatementsFromTypeSymbol(typeSymbol); - - if (importStatements != null && !importStatements.isEmpty()) { - JsonArray links = extractTypeLinks(importStatements, recordName, currentOrg, currentPackage); - if (!links.isEmpty()) { - typeObj.add("links", links); - } - } - } - - obj.add("type", typeObj); - } - - return obj; - } - - /** - * Extracts the record name from TypeSymbol handling Union, TypeReference, Array, and basic types. - */ - private String extractRecordNameFromTypeSymbol(TypeSymbol typeSymbol) { - if (typeSymbol == null) { - return ""; - } - - switch (typeSymbol.typeKind()) { - case UNION: - // Handle union types - UnionTypeSymbol unionType = (UnionTypeSymbol) typeSymbol; - List memberTypes = new java.util.ArrayList<>(); - for (TypeSymbol member : unionType.memberTypeDescriptors()) { - memberTypes.add(extractRecordNameFromTypeSymbol(member)); - } - return String.join("|", memberTypes); - - case TYPE_REFERENCE: - // Handle type references - get the definition name from the referenced type - TypeReferenceTypeSymbol typeRef = (TypeReferenceTypeSymbol) typeSymbol; - return typeRef.definition().getName() - .or(() -> typeRef.typeDescriptor().getName()) - .orElse(typeSymbol.typeKind().getName()); - - case ARRAY: - // Handle array types - recursively get the member type name - ArrayTypeSymbol arrayType = (ArrayTypeSymbol) typeSymbol; - return extractRecordNameFromTypeSymbol(arrayType.memberTypeDescriptor()) + "[]"; - - default: - // For other types, use getName() directly - return typeSymbol.getName().orElse(typeSymbol.signature()); - } - } - - /** - * Builds TypeDefData from a TypeDefinitionSymbol using FunctionDataBuilder's allMembers function. - */ - private TypeDefData buildTypeDefDataFromSymbol(TypeDefinitionSymbol typeDefSymbol) { - String typeName = typeDefSymbol.getName().orElse(""); - String typeDescription = typeDefSymbol.documentation() - .flatMap(Documentation::description) - .orElse(""); - - TypeSymbol typeDescriptor = typeDefSymbol.typeDescriptor(); - TypeSymbol rawType = getRawType(typeDescriptor); - TypeDescKind typeKind = rawType.typeKind(); - - // Use FunctionDataBuilder.allMembers to extract all type information - Map typeMap = new java.util.LinkedHashMap<>(); - FunctionDataBuilder.allMembers(typeMap, typeDescriptor); - - // Determine type category - TypeDefData.TypeCategory typeCategory = switch (typeKind) { - case RECORD -> TypeDefData.TypeCategory.RECORD; - case UNION -> TypeDefData.TypeCategory.UNION; - case OBJECT -> TypeDefData.TypeCategory.CLASS; - case ERROR -> TypeDefData.TypeCategory.ERROR; - default -> TypeDefData.TypeCategory.OTHER; - }; - - // Extract fields based on type - List fields = new java.util.ArrayList<>(); - String baseType = null; - - baseType = switch (typeKind) { - case RECORD -> { - extractRecordFields((RecordTypeSymbol) rawType, fields); - yield typeDescriptor.signature(); - } - case UNION -> { - extractUnionMembers((UnionTypeSymbol) rawType, fields); - yield typeDescriptor.signature(); - } - case MAP -> extractMapFields((MapTypeSymbol) rawType, fields); - case TABLE -> { - extractTableFields((TableTypeSymbol) rawType, fields); - yield typeDescriptor.signature(); - } - case STREAM -> { - extractStreamFields((StreamTypeSymbol) rawType, fields); - yield typeDescriptor.signature(); - } - default -> typeDescriptor.signature(); - }; - - return new TypeDefData(typeName, typeDescription, typeCategory, fields, baseType); - } - - private void extractRecordFields(RecordTypeSymbol recordType, List fields) { - recordType.fieldDescriptors().forEach((key, fieldSymbol) -> { - String fieldName = fieldSymbol.getName().orElse(key); - String fieldDescription = fieldSymbol.documentation() - .flatMap(Documentation::description) - .orElse(""); - TypeSymbol fieldTypeSymbol = fieldSymbol.typeDescriptor(); - boolean optional = fieldSymbol.isOptional() || fieldSymbol.hasDefaultValue(); - - FieldData.FieldType fieldType = new FieldData.FieldType(fieldTypeSymbol.signature(), fieldTypeSymbol); - fields.add(new FieldData(fieldName, fieldDescription, fieldType, optional)); - }); - - // Handle rest field if present - recordType.restTypeDescriptor().ifPresent(restType -> { - FieldData.FieldType fieldType = new FieldData.FieldType(restType.signature(), restType); - fields.add(new FieldData("", "Rest field", fieldType, false)); - }); - } - - private void extractUnionMembers(UnionTypeSymbol unionType, List fields) { - unionType.memberTypeDescriptors().forEach(memberType -> { - String memberTypeName = memberType.signature(); - FieldData.FieldType fieldType = new FieldData.FieldType(memberTypeName, memberType); - fields.add(new FieldData(memberTypeName, "Union member", fieldType, false)); - }); - } - - private String extractMapFields(MapTypeSymbol mapType, List fields) { - TypeSymbol constraintType = mapType.typeParam(); - String constraintTypeName = constraintType.signature(); - FieldData.FieldType fieldType = new FieldData.FieldType(constraintTypeName, constraintType); - fields.add(new FieldData("constraint", "Map constraint type", fieldType, false)); - return "map<" + constraintTypeName + ">"; - } - - private void extractTableFields(TableTypeSymbol tableType, List fields) { - TypeSymbol rowType = tableType.rowTypeParameter(); - FieldData.FieldType rowFieldType = new FieldData.FieldType(rowType.signature(), rowType); - fields.add(new FieldData("rowType", "Table row type", rowFieldType, false)); - - // Extract key constraint if present - tableType.keyConstraintTypeParameter().ifPresent(keyType -> { - FieldData.FieldType keyFieldType = new FieldData.FieldType(keyType.signature(), keyType); - fields.add(new FieldData("keyConstraint", "Table key constraint", keyFieldType, false)); - }); - } - - private void extractStreamFields(StreamTypeSymbol streamType, List fields) { - TypeSymbol streamTypeParam = streamType.typeParameter(); - FieldData.FieldType streamFieldType = new FieldData.FieldType(streamTypeParam.signature(), streamTypeParam); - fields.add(new FieldData("valueType", "Stream value type", streamFieldType, false)); - - TypeSymbol completionType = streamType.completionValueTypeParameter(); - FieldData.FieldType completionFieldType = new FieldData.FieldType(completionType.signature(), completionType); - fields.add(new FieldData("completionType", "Stream completion type", completionFieldType, false)); - } - - /** - * Builds TypeDefData for an Enum symbol. - */ - private TypeDefData buildEnumTypeDefData(EnumSymbol enumSymbol) { - String typeName = enumSymbol.getName().orElse(""); - String typeDescription = enumSymbol.documentation() - .flatMap(Documentation::description) - .orElse(""); - - List enumMembers = new java.util.ArrayList<>(); - - // Extract enum members - for (ConstantSymbol member : enumSymbol.members()) { - String memberName = member.getName().orElse(""); - String memberDescription = member.documentation() - .flatMap(Documentation::description) - .orElse(""); - - // Get the constant value if available - String memberValue = memberName; - Object constValueObj = member.constValue(); - if (constValueObj instanceof ConstantValue constantValue) { - Object value = constantValue.value(); - if (value != null) { - memberValue = value.toString(); - } - } - - FieldData.FieldType fieldType = new FieldData.FieldType(memberValue); - enumMembers.add(new FieldData(memberName, memberDescription, fieldType, false)); - } - - return new TypeDefData(typeName, typeDescription, TypeDefData.TypeCategory.ENUM, enumMembers, null); - } - - /** - * Builds TypeDefData for a Constant symbol. - */ - private TypeDefData buildConstantTypeDefData(ConstantSymbol constantSymbol) { - String typeName = constantSymbol.getName().orElse(""); - String typeDescription = constantSymbol.documentation() - .flatMap(Documentation::description) - .orElse(""); - - // Get the constant's type and value - TypeSymbol typeSymbol = constantSymbol.typeDescriptor(); - - // Get the constant value if available - String constantValue = typeSymbol.signature(); - Object constValueObj = constantSymbol.constValue(); - if (constValueObj instanceof ConstantValue constantVal) { - Object value = constantVal.value(); - if (value != null) { - constantValue = value.toString(); - } - } - - return new TypeDefData(typeName, typeDescription, TypeDefData.TypeCategory.CONSTANT, - new java.util.ArrayList<>(), constantValue); - } - - /** - * Extracts import statements from a TypeSymbol by analyzing its module information. - * Returns a comma-separated string of package paths (e.g., "org/package, org2/package2"). - */ - private String extractImportStatementsFromTypeSymbol(TypeSymbol typeSymbol) { - if (typeSymbol == null) { - return null; - } - - // Get the module information from the type symbol - Optional moduleOpt = typeSymbol.getModule(); - if (moduleOpt.isEmpty()) { - return null; - } - - ModuleSymbol moduleSymbol = moduleOpt.get(); - - // Get org and module name - String org = moduleSymbol.id().orgName(); - String moduleName = moduleSymbol.id().moduleName(); - - // Return the package path - return org + "/" + moduleName; - } - - /** - * Extracts type links from import statements string combined with type symbol. - */ - private JsonArray extractTypeLinks(String importStatements, - String recordName, - String currentOrg, - String currentPackage) { - JsonArray links = new JsonArray(); - - if (importStatements == null || importStatements.trim().isEmpty()) { - return links; - } - - // Split by semicolon to get individual import statements - String[] imports = importStatements.split(","); - for (String importStmt : imports) { - String packagePath = importStmt.trim(); - - if (packagePath.isEmpty()) { - continue; - } - - // Handle "as alias" part if present - int asIndex = packagePath.indexOf(" as "); - if (asIndex > 0) { - packagePath = packagePath.substring(0, asIndex).trim(); - } - - String[] parts = packagePath.split("/"); - if (parts.length >= 2) { - String org = parts[0]; - String pkgName = parts[1]; - - // Skip predefined lang libs - if (isPredefinedLangLib(org, pkgName)) { - continue; - } - - // Determine if it's internal or external - boolean isInternal = org.equals(currentOrg) && pkgName.equals(currentPackage); - - // Create the link object - JsonObject link = new JsonObject(); - link.addProperty("category", isInternal ? "internal" : "external"); - link.addProperty("recordName", recordName); - - if (!isInternal) { - // Add library name for external types - link.addProperty("libraryName", org + "/" + pkgName); - } - - links.add(link); - } - } - - return links; - } - - /** - * Checks if a module is a predefined language library. - */ - private boolean isPredefinedLangLib(String orgName, String packageName) { - return "ballerina".equals(orgName) && - packageName.startsWith("lang.") && - !packageName.equals("lang.annotations"); - } - - /** - * Loads services from inbuilt-triggers JSON files. - * These JSON files contain service definitions with listener and function information. - * - * @param libraryName the library name (e.g., "ballerinax/kafka") - * @return JsonArray containing services, or empty array if not found - */ - private JsonArray loadServicesFromInbuiltTriggers(String libraryName) { - JsonArray services = new JsonArray(); - - // Map library names to inbuilt-triggers file names - // This is specifically for known trigger libraries like kafka, asb, GitHub, etc. - String triggerFileName = getInbuiltTriggerFileName(libraryName); - if (triggerFileName == null) { - return services; // No inbuilt trigger for this library - } - - try (InputStream inputStream = CopilotLibraryService.class.getResourceAsStream("/inbuilt-triggers/" + - triggerFileName)) { - if (inputStream == null) { - return services; // File not found - } - - try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { - - JsonObject triggerData = JsonParser.parseReader(reader).getAsJsonObject(); - - // Extract listener information - JsonObject listener = triggerData.getAsJsonObject("listener"); - if (listener == null) { - return services; - } - - // Extract service types - JsonArray serviceTypes = triggerData.getAsJsonArray("serviceTypes"); - if (serviceTypes == null || serviceTypes.isEmpty()) { - return services; - } - - // For each service type, create a service object - for (JsonElement serviceTypeElement : serviceTypes) { - JsonObject serviceType = serviceTypeElement.getAsJsonObject(); - - JsonObject serviceObj = new JsonObject(); - - // Service type: "fixed" for specific listeners - serviceObj.addProperty("type", "fixed"); - - // Build listener object - JsonObject listenerObj = buildListenerFromTriggerData(listener); - serviceObj.add("listener", listenerObj); - - // Extract functions from service type - JsonArray functionsFromService = serviceType.getAsJsonArray("functions"); - if (functionsFromService != null && !functionsFromService.isEmpty()) { - JsonArray transformedFunctions = new JsonArray(); - for (JsonElement funcElement : functionsFromService) { - JsonObject func = funcElement.getAsJsonObject(); - JsonObject transformedFunc = transformServiceFunction(func); - transformedFunctions.add(transformedFunc); - } - serviceObj.add("functions", transformedFunctions); - } - - services.add(serviceObj); - } - - } - } catch (IOException e) { - // If file doesn't exist or cannot be read, return empty array - return services; - } - - return services; - } - - /** - * Loads generic services for a specific library from the generic-services.json file. - * Returns services defined for libraries like ballerina/http, ballerina/graphql, etc. - * - * @param libraryName the library name (e.g., "ballerina/http") - * @return JsonArray containing services for this library, or empty array if not found - */ - private JsonArray loadGenericServicesForLibrary(String libraryName) { - JsonArray matchingServices = new JsonArray(); - - try (InputStream inputStream = CopilotLibraryService.class.getResourceAsStream(GENERIC_SERVICES_JSON_PATH)) { - if (inputStream == null) { - return matchingServices; // File not found, return empty array - } - - try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { - JsonObject genericServicesData = JsonParser.parseReader(reader).getAsJsonObject(); - - // Get the services array - JsonArray allServices = genericServicesData.getAsJsonArray("services"); - if (allServices == null || allServices.isEmpty()) { - return matchingServices; - } - - // Filter services by library name - for (JsonElement serviceElement : allServices) { - JsonObject service = serviceElement.getAsJsonObject(); - - // Check if this service belongs to the requested library - if (service.has("libraryName") && - service.get("libraryName").getAsString().equals(libraryName)) { - - // Create a copy of the service without the libraryName field - JsonObject serviceObj = new JsonObject(); - serviceObj.addProperty("type", service.get("type").getAsString()); - serviceObj.addProperty("instructions", service.get("instructions").getAsString()); - - // Copy listener object - if (service.has("listener")) { - serviceObj.add("listener", service.get("listener")); - } - - // Copy functions array if present - if (service.has("functions")) { - serviceObj.add("functions", service.get("functions")); - } - - matchingServices.add(serviceObj); - } - } - } - } catch (IOException e) { - // If file doesn't exist or cannot be read, return empty array - return matchingServices; - } - - return matchingServices; - } - - /** - * Maps library names to inbuilt-trigger file names. - * - * @param libraryName the library name (e.g., "ballerinax/kafka") - * @return the trigger file name (e.g., "kafka.json") or null if not a trigger library - */ - private String getInbuiltTriggerFileName(String libraryName) { - // Remove org prefix if present - String packageName = libraryName.contains("/") ? - libraryName.substring(libraryName.indexOf("/") + 1) : libraryName; - - // Map known trigger libraries to their JSON file names - return switch (packageName) { - case "kafka" -> "kafka.json"; - case "asb" -> "asb.json"; - case "jms" -> "jms.json"; - case "rabbitmq" -> "rabbitmq.json"; - case "nats" -> "nats.json"; - case "ftp" -> "ftp.json"; - case "mqtt" -> "mqtt.json"; - case "salesforce" -> "salesforce.json"; - case "trigger.github", "github" -> "github.json"; - default -> null; - }; - } - - /** - * Builds a listener object from inbuilt-triggers listener data. - * - * @param listenerData the listener JSON object from triggers file - * @return JsonObject representing the listener - */ - private JsonObject buildListenerFromTriggerData(JsonObject listenerData) { - JsonObject listenerObj = new JsonObject(); - - // Get listener name from valueTypeConstraint - String listenerName = listenerData.has("valueTypeConstraint") ? - listenerData.get("valueTypeConstraint").getAsString() : "Listener"; - listenerObj.addProperty("name", listenerName); - - // Extract parameters from listener properties - JsonArray parametersArray = new JsonArray(); - if (listenerData.has("properties")) { - JsonObject properties = listenerData.getAsJsonObject("properties"); - for (String propKey : properties.keySet()) { - JsonObject prop = properties.getAsJsonObject(propKey); - JsonObject paramObj = buildParameterFromProperty(propKey, prop); - parametersArray.add(paramObj); - } - } - - listenerObj.add("parameters", parametersArray); - return listenerObj; - } - - /** - * Builds a parameter object from a listener property. - * - * @param propertyName the property name - * @param property the property JSON object - * @return JsonObject representing the parameter, or null if invalid - */ - private JsonObject buildParameterFromProperty(String propertyName, JsonObject property) { - JsonObject paramObj = new JsonObject(); - - // Parameter name - paramObj.addProperty("name", propertyName); - - // Parameter description from metadata - String description = ""; - if (property.has("metadata")) { - JsonObject metadata = property.getAsJsonObject("metadata"); - if (metadata.has("description")) { - description = metadata.get("description").getAsString(); - } - } - paramObj.addProperty("description", description); - - // Parameter type - JsonObject typeObj = new JsonObject(); - String typeName = property.has("valueTypeConstraint") ? - property.get("valueTypeConstraint").getAsString() : "string"; - typeObj.addProperty("name", typeName); - paramObj.add("type", typeObj); - - // Default value if present - if (property.has("placeholder") && !property.get("placeholder").isJsonNull()) { - paramObj.addProperty("default", property.get("placeholder").getAsString()); - } - - return paramObj; - } - - /** - * Transforms a service function from trigger data to the required format. - * - * @param functionData the function JSON object from triggers file - * @return JsonObject representing the transformed function - */ - private JsonObject transformServiceFunction(JsonObject functionData) { - JsonObject func = new JsonObject(); - - // Function name - if (functionData.has("name")) { - func.addProperty("name", functionData.get("name").getAsString()); - } - - String functionType = "Remote Function"; - if (functionData.has("qualifiers")) { - JsonArray qualifiers = functionData.getAsJsonArray("qualifiers"); - if (qualifiers != null && !qualifiers.isEmpty()) { - String qualifier = qualifiers.get(0).getAsString(); - functionType = qualifier.equals("remote") ? "Remote Function" : "Normal Function"; - } - } - func.addProperty("type", functionType); - - // Function documentation - if (functionData.has("documentation")) { - func.addProperty("description", functionData.get("documentation").getAsString()); - } - - // Parameters - if (functionData.has("parameters")) { - JsonArray parameters = functionData.getAsJsonArray("parameters"); - JsonArray transformedParams = new JsonArray(); - for (JsonElement paramElement : parameters) { - JsonObject param = paramElement.getAsJsonObject(); - JsonObject transformedParam = new JsonObject(); - - // Parameter name - if (param.has("name")) { - transformedParam.addProperty("name", param.get("name").getAsString()); - } - - // Parameter description - if (param.has("documentation")) { - transformedParam.addProperty("description", param.get("documentation").getAsString()); - } - - // Parameter type - JsonObject typeObj = new JsonObject(); - if (param.has("type")) { - JsonElement typeElement = param.get("type"); - if (typeElement.isJsonArray()) { - // If type is an array, get the first element (or default type) - JsonArray typeArray = typeElement.getAsJsonArray(); - if (!typeArray.isEmpty()) { - typeObj.addProperty("name", typeArray.get(0).getAsString()); - } - } else { - typeObj.addProperty("name", typeElement.getAsString()); - } - } else if (param.has("typeName")) { - typeObj.addProperty("name", param.get("typeName").getAsString()); - } - transformedParam.add("type", typeObj); - - // Optional flag - if (param.has("optional")) { - transformedParam.addProperty("optional", param.get("optional").getAsBoolean()); - } - - transformedParams.add(transformedParam); - } - func.add("parameters", transformedParams); - } - - // Return type - if (functionData.has("returnType")) { - JsonObject returnTypeData = functionData.getAsJsonObject("returnType"); - JsonObject returnObj = new JsonObject(); - JsonObject returnTypeObj = new JsonObject(); - - if (returnTypeData.has("typeName")) { - returnTypeObj.addProperty("name", returnTypeData.get("typeName").getAsString()); - } else if (returnTypeData.has("type")) { - JsonElement typeElement = returnTypeData.get("type"); - if (typeElement.isJsonArray()) { - JsonArray typeArray = typeElement.getAsJsonArray(); - if (!typeArray.isEmpty()) { - returnTypeObj.addProperty("name", typeArray.get(0).getAsString()); - } - } else { - returnTypeObj.addProperty("name", typeElement.getAsString()); - } - } - returnObj.add("type", returnTypeObj); - func.add("return", returnObj); - } - - return func; - } }