Repository: ballerina-platform/openapi-tools
Issue: OpenAPI build extension generate duplicate OAS for multiple service node in same ballerina file 

**Description:**
<!-- Give a brief description of the bug -->
```ballerina file 
import ballerina/http;

listener http:Listener backendEP01 = check new(8080);
listener http:Listener backendEP02 = check new(8081);
service /v1 on backendEP01 {
    resource function get mock(@http:Payload json payload) returns string {
        return "Mock2 resource was invoked.";
    }
}
service /v1 on backendEP02 {
    resource function get .() returns string {
        return "Mock1 resource was invoked.";
    }
}
```
This ballerina file build option generate duplicate yaml file 
**Steps to reproduce:**
> bal build --export-openapi
**Affected Versions:**
Beta6 RC1
**OS, DB, other environment details and versions:**

**Related Issues (optional):**
<!-- Any related issues such as sub tasks, issues reported in other repositories (e.g component repositories), similar problems, etc. -->

**Suggested Labels (optional):**
<!-- Optional comma separated list of suggested labels. Non committers can’t assign labels to issues, so this will help issue creators who are not a committer to suggest possible labels-->

**Suggested Assignees (optional):**
<!--Optional comma separated list of suggested team members who should attend the issue. Non committers can’t assign issues to assignees, so this will help issue creators who are not a committer to suggest possible assignees-->


Changed files:
openapi-build-extension/src/main/java/io/ballerina/openapi/build/HttpServiceAnalysisTask.java
openapi-build-extension/src/main/java/io/ballerina/openapi/build/PluginConstants.java
openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java
openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ServiceToOpenAPIConverterUtils.java
openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/CodegenUtils.java

diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/CodegenUtils.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/CodegenUtils.java
index 66f6e3d9..7589282e 100644
--- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/CodegenUtils.java
+++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/CodegenUtils.java
@@ -34,6 +34,7 @@ import static io.ballerina.openapi.converter.Constants.YAML_EXTENSION;
  * Utilities used by ballerina openapi code generator.
  */
 public final class CodegenUtils {
+    private static final String LINE_SEPARATOR = System.lineSeparator();
     /**
      * Resolves path to write generated implementation source files.
      *
diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java
index 4fbe5503..4c278622 100644
--- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java
+++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java
@@ -45,6 +45,8 @@ import io.ballerina.openapi.converter.diagnostic.DiagnosticMessages;
 import io.ballerina.openapi.converter.diagnostic.ExceptionDiagnostic;
 import io.ballerina.openapi.converter.diagnostic.OpenAPIConverterDiagnostic;
 import io.ballerina.openapi.converter.model.OASResult;
+import io.ballerina.tools.diagnostics.Diagnostic;
+import io.ballerina.tools.diagnostics.DiagnosticSeverity;
 import io.ballerina.tools.diagnostics.Location;
 import io.ballerina.tools.text.LinePosition;
 import io.ballerina.tools.text.LineRange;
@@ -387,4 +389,9 @@ public class ConverterCommonUtils {
         return moduleNameOpt.isPresent() && Constants.HTTP.equals(moduleNameOpt.get())
                 && Constants.BALLERINA.equals(moduleSymbol.id().orgName());
     }
+
+    public static boolean containErrors(List<Diagnostic> diagnostics) {
+        return diagnostics != null && diagnostics.stream().anyMatch(diagnostic ->
+                diagnostic.diagnosticInfo().severity() == DiagnosticSeverity.ERROR);
+    }
 }
diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ServiceToOpenAPIConverterUtils.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ServiceToOpenAPIConverterUtils.java
index efbe5b25..f8fd3e76 100644
--- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ServiceToOpenAPIConverterUtils.java
+++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ServiceToOpenAPIConverterUtils.java
@@ -44,8 +44,6 @@ import io.ballerina.openapi.converter.model.OASResult;
 import io.ballerina.openapi.converter.model.OpenAPIInfo;
 import io.ballerina.openapi.converter.service.OpenAPIEndpointMapper;
 import io.ballerina.openapi.converter.service.OpenAPIServiceMapper;
-import io.ballerina.tools.diagnostics.Diagnostic;
-import io.ballerina.tools.diagnostics.DiagnosticSeverity;
 import io.ballerina.tools.diagnostics.Location;
 import io.swagger.v3.core.util.Json;
 import io.swagger.v3.core.util.Yaml;
@@ -71,6 +69,7 @@ import static io.ballerina.openapi.converter.Constants.SLASH;
 import static io.ballerina.openapi.converter.Constants.SPECIAL_CHAR_REGEX;
 import static io.ballerina.openapi.converter.Constants.TITLE;
 import static io.ballerina.openapi.converter.Constants.VERSION;
+import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.containErrors;
 import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.getOpenApiFileName;
 import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.isHttpService;
 
@@ -131,11 +130,6 @@ public class ServiceToOpenAPIConverterUtils {
         return outputs;
     }
 
-    private static boolean containErrors(List<Diagnostic> diagnostics) {
-        return diagnostics != null && diagnostics.stream().anyMatch(diagnostic ->
-                diagnostic.diagnosticInfo().severity() == DiagnosticSeverity.ERROR);
-    }
-
     /**
      * Filter all the end points and service nodes.
      */
diff --git a/openapi-build-extension/src/main/java/io/ballerina/openapi/build/HttpServiceAnalysisTask.java b/openapi-build-extension/src/main/java/io/ballerina/openapi/build/HttpServiceAnalysisTask.java
index 054479fa..727e5df9 100644
--- a/openapi-build-extension/src/main/java/io/ballerina/openapi/build/HttpServiceAnalysisTask.java
+++ b/openapi-build-extension/src/main/java/io/ballerina/openapi/build/HttpServiceAnalysisTask.java
@@ -17,14 +17,19 @@
 package io.ballerina.openapi.build;
 
 import io.ballerina.compiler.api.SemanticModel;
+import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol;
+import io.ballerina.compiler.api.symbols.Symbol;
+import io.ballerina.compiler.syntax.tree.ListenerDeclarationNode;
 import io.ballerina.compiler.syntax.tree.ModulePartNode;
 import io.ballerina.compiler.syntax.tree.Node;
+import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode;
 import io.ballerina.compiler.syntax.tree.SyntaxKind;
 import io.ballerina.compiler.syntax.tree.SyntaxTree;
 import io.ballerina.openapi.converter.diagnostic.DiagnosticMessages;
 import io.ballerina.openapi.converter.diagnostic.ExceptionDiagnostic;
 import io.ballerina.openapi.converter.diagnostic.OpenAPIConverterDiagnostic;
 import io.ballerina.openapi.converter.model.OASResult;
+import io.ballerina.openapi.converter.service.OpenAPIEndpointMapper;
 import io.ballerina.openapi.converter.utils.ServiceToOpenAPIConverterUtils;
 import io.ballerina.projects.BuildOptions;
 import io.ballerina.projects.Package;
@@ -38,13 +43,20 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 import static io.ballerina.openapi.build.PluginConstants.OAS_PATH_SEPARATOR;
 import static io.ballerina.openapi.build.PluginConstants.OPENAPI;
+import static io.ballerina.openapi.build.PluginConstants.UNDERSCORE;
+import static io.ballerina.openapi.converter.Constants.HYPHEN;
+import static io.ballerina.openapi.converter.Constants.SLASH;
 import static io.ballerina.openapi.converter.utils.CodegenUtils.resolveContractFileName;
 import static io.ballerina.openapi.converter.utils.CodegenUtils.writeFile;
+import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.containErrors;
+import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.isHttpService;
 
 /**
  * SyntaxNodeAnalyzer for getting all service node.
@@ -52,6 +64,7 @@ import static io.ballerina.openapi.converter.utils.CodegenUtils.writeFile;
  * @since 2.0.0
  */
 public class HttpServiceAnalysisTask implements AnalysisTask<SyntaxNodeAnalysisContext> {
+
     @Override
     public void perform(SyntaxNodeAnalysisContext context) {
         SemanticModel semanticModel = context.semanticModel();
@@ -63,24 +76,26 @@ public class HttpServiceAnalysisTask implements AnalysisTask<SyntaxNodeAnalysisC
         if (!buildOptions.exportOpenAPI()) {
             return;
         }
-        // Take output path to target directory location in package.
         Path outPath = project.targetDir();
         Optional<Path> path = currentPackage.project().documentPath(context.documentId());
         Path inputPath = path.orElse(null);
-        // Traverse the service declaration nodes
-        ModulePartNode modulePartNode = syntaxTree.rootNode();
-        List<OASResult> openAPIDefinitions = new ArrayList<>();
-        for (Node node : modulePartNode.members()) {
-            SyntaxKind syntaxKind = node.kind();
-            // Load a service declarations
-            if (syntaxKind == SyntaxKind.SERVICE_DECLARATION) {
-                openAPIDefinitions.addAll(ServiceToOpenAPIConverterUtils.generateOAS3Definition(syntaxTree,
-                        semanticModel, null, false, inputPath));
-            }
-        }
+        ServiceDeclarationNode serviceNode = (ServiceDeclarationNode) context.node();
+        List<ListenerDeclarationNode> endpoints = new ArrayList<>();
+        Map<Integer, String> services = new HashMap<>();
         List<Diagnostic> diagnostics = new ArrayList<>();
-        if (!openAPIDefinitions.isEmpty()) {
-            extractOpenAPIYamlFromOutputs(outPath, openAPIDefinitions, diagnostics);
+
+        // Spec generation won't proceed, If diagnostic includes error type.
+        if (containErrors(semanticModel.diagnostics())) {
+            diagnostics.addAll(semanticModel.diagnostics());
+        } else if (isHttpService(serviceNode, semanticModel)) {
+            Optional<Symbol> serviceSymbol = semanticModel.symbol(serviceNode);
+            if (serviceSymbol.isPresent() && serviceSymbol.get() instanceof ServiceDeclarationSymbol) {
+                extractListenersAndServiceNodes(syntaxTree.rootNode(), endpoints, services, semanticModel);
+                OASResult oasResult = ServiceToOpenAPIConverterUtils.generateOAS(serviceNode, endpoints,
+                        semanticModel, services.get(serviceSymbol.get().hashCode()), inputPath);
+                oasResult.setServiceName(constructFileName(syntaxTree, services, serviceSymbol.get()));
+                writeOpenAPIYaml(outPath, oasResult, diagnostics);
+            }
         }
         if (!diagnostics.isEmpty()) {
             for (Diagnostic diagnostic : diagnostics) {
@@ -89,26 +104,79 @@ public class HttpServiceAnalysisTask implements AnalysisTask<SyntaxNodeAnalysisC
         }
     }
 
-    private void extractOpenAPIYamlFromOutputs(Path outPath, List<OASResult> openAPIDefinitions,
-                                               List<Diagnostic> diagnostics) {
-        for (OASResult oasResult: openAPIDefinitions) {
-            if (oasResult.getYaml().isPresent()) {
-                try {
-                    // Create openapi directory if not exists in the path. If exists do not throw an error
-                    Files.createDirectories(Paths.get(outPath + OAS_PATH_SEPARATOR + OPENAPI));
-                    String fileName = resolveContractFileName(outPath.resolve(OPENAPI), oasResult.getServiceName(),
-                            false);
-                    writeFile(outPath.resolve(OPENAPI + OAS_PATH_SEPARATOR + fileName), oasResult.getYaml().get());
-                } catch (IOException e) {
-                    DiagnosticMessages error = DiagnosticMessages.OAS_CONVERTOR_108;
-                    ExceptionDiagnostic diagnostic = new ExceptionDiagnostic(error.getCode(),
-                            error.getDescription(), null, e.toString());
-                    diagnostics.add(BuildExtensionUtil.getDiagnostics(diagnostic));
-                }
+    /**
+     * This util function is to construct the generated file name.
+     *
+     * @param syntaxTree syntax tree for check the multiple services
+     * @param services   service map for maintain the file name with updated name
+     * @param serviceSymbol symbol for taking the hash code of services
+     */
+    private String constructFileName(SyntaxTree syntaxTree, Map<Integer, String> services, Symbol serviceSymbol) {
+        String fileName = services.get(serviceSymbol.hashCode());
+        if (fileName.equals(SLASH)) {
+            return syntaxTree.filePath().split("\\\\.")[0];
+        } else if (fileName.contains(HYPHEN) && fileName.split(HYPHEN)[0].equals(SLASH)) {
+            return syntaxTree.filePath().split("\\\\.")[0] + UNDERSCORE +
+                     services.get(serviceSymbol.hashCode()).split(HYPHEN)[1];
+        } else {
+            return services.get(serviceSymbol.hashCode());
+        }
+    }
+
+    private void writeOpenAPIYaml(Path outPath, OASResult oasResult, List<Diagnostic> diagnostics) {
+        if (oasResult.getYaml().isPresent()) {
+            try {
+                // Create openapi directory if not exists in the path. If exists do not throw an error
+                Files.createDirectories(Paths.get(outPath + OAS_PATH_SEPARATOR + OPENAPI));
+                String fileName = resolveContractFileName(outPath.resolve(OPENAPI), oasResult.getServiceName(),
+                        false);
+                writeFile(outPath.resolve(OPENAPI + OAS_PATH_SEPARATOR + fileName), oasResult.getYaml().get());
+            } catch (IOException e) {
+                DiagnosticMessages error = DiagnosticMessages.OAS_CONVERTOR_108;
+                ExceptionDiagnostic diagnostic = new ExceptionDiagnostic(error.getCode(),
+                        error.getDescription(), null, e.toString());
+                diagnostics.add(BuildExtensionUtil.getDiagnostics(diagnostic));
+            }
+        }
+        if (!oasResult.getDiagnostics().isEmpty()) {
+            for (OpenAPIConverterDiagnostic diagnostic : oasResult.getDiagnostics()) {
+                diagnostics.add(BuildExtensionUtil.getDiagnostics(diagnostic));
+            }
+        }
+    }
+
+    /**
+     * Filter all the end points and service nodes for avoiding the generated file name conflicts.
+     */
+    private static void extractListenersAndServiceNodes(ModulePartNode modulePartNode,
+                                                        List<ListenerDeclarationNode> endpoints,
+                                                        Map<Integer, String> services,
+                                                        SemanticModel semanticModel) {
+        List<String> allServices = new ArrayList<>();
+        for (Node node : modulePartNode.members()) {
+            SyntaxKind syntaxKind = node.kind();
+            // Load a listen_declaration for the server part in the yaml spec
+            if (syntaxKind.equals(SyntaxKind.LISTENER_DECLARATION)) {
+                ListenerDeclarationNode listener = (ListenerDeclarationNode) node;
+                endpoints.add(listener);
             }
-            if (!oasResult.getDiagnostics().isEmpty()) {
-                for (OpenAPIConverterDiagnostic diagnostic: oasResult.getDiagnostics()) {
-                    diagnostics.add(BuildExtensionUtil.getDiagnostics(diagnostic));
+            if (syntaxKind.equals(SyntaxKind.SERVICE_DECLARATION)) {
+                ServiceDeclarationNode serviceNode = (ServiceDeclarationNode) node;
+                if (isHttpService(serviceNode, semanticModel)) {
+                    // Here check the service is related to the http
+                    // module by checking listener type that attached to service endpoints.
+                    Optional<Symbol> serviceSymbol = semanticModel.symbol(serviceNode);
+                    if (serviceSymbol.isPresent() && serviceSymbol.get() instanceof ServiceDeclarationSymbol) {
+                        String service = OpenAPIEndpointMapper.ENDPOINT_MAPPER.getServiceBasePath(serviceNode);
+                        String updateServiceName = service;
+                        if (allServices.contains(service)) {
+                            updateServiceName = service + HYPHEN + serviceSymbol.get().hashCode();
+                        } else {
+                            // To generate for all services
+                            allServices.add(service);
+                        }
+                        services.put(serviceSymbol.get().hashCode(), updateServiceName);
+                    }
                 }
             }
         }
diff --git a/openapi-build-extension/src/main/java/io/ballerina/openapi/build/PluginConstants.java b/openapi-build-extension/src/main/java/io/ballerina/openapi/build/PluginConstants.java
index 9a42f704..2376bc9b 100644
--- a/openapi-build-extension/src/main/java/io/ballerina/openapi/build/PluginConstants.java
+++ b/openapi-build-extension/src/main/java/io/ballerina/openapi/build/PluginConstants.java
@@ -23,4 +23,5 @@ package io.ballerina.openapi.build;
 public class PluginConstants {
     public static final String OPENAPI = "openapi";
     public static final String OAS_PATH_SEPARATOR = "/";
+    public static final String UNDERSCORE = "_";
 }