org.ballerinalang.langserver.extensions.ballerina.document.BallerinaDocumentServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.ballerinalang.langserver.extensions.ballerina.document.BallerinaDocumentServiceImpl.java

Source

/*
 * Copyright (c) 2018, WSO2 Inc. (http://wso2.com) All Rights Reserved.
 *
 * Licensed 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 org.ballerinalang.langserver.extensions.ballerina.document;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.ballerinalang.ballerina.swagger.convertor.service.SwaggerConverterUtils;
import org.ballerinalang.compiler.CompilerPhase;
import org.ballerinalang.langserver.BallerinaLanguageServer;
import org.ballerinalang.langserver.LSGlobalContext;
import org.ballerinalang.langserver.LSGlobalContextKeys;
import org.ballerinalang.langserver.common.utils.CommonUtil;
import org.ballerinalang.langserver.compiler.DocumentServiceKeys;
import org.ballerinalang.langserver.compiler.LSCompiler;
import org.ballerinalang.langserver.compiler.LSCompilerException;
import org.ballerinalang.langserver.compiler.LSContext;
import org.ballerinalang.langserver.compiler.LSServiceOperationContext;
import org.ballerinalang.langserver.compiler.common.LSCustomErrorStrategy;
import org.ballerinalang.langserver.compiler.common.LSDocument;
import org.ballerinalang.langserver.compiler.common.modal.BallerinaFile;
import org.ballerinalang.langserver.compiler.common.modal.SymbolMetaInfo;
import org.ballerinalang.langserver.compiler.format.JSONGenerationException;
import org.ballerinalang.langserver.compiler.format.TextDocumentFormatUtil;
import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentException;
import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentManager;
import org.ballerinalang.langserver.extensions.OASGenerationException;
import org.ballerinalang.langserver.formatting.FormattingSourceGen;
import org.ballerinalang.model.tree.ServiceNode;
import org.ballerinalang.model.tree.TopLevelNode;
import org.ballerinalang.swagger.CodeGenerator;
import org.ballerinalang.swagger.model.GenSrcFile;
import org.ballerinalang.swagger.utils.GeneratorConstants;
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit;
import org.wso2.ballerinalang.compiler.tree.BLangNode;
import org.wso2.ballerinalang.compiler.tree.BLangPackage;
import org.wso2.ballerinalang.compiler.util.CompilerContext;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

import static org.ballerinalang.langserver.compiler.LSCompilerUtil.getProjectDir;
import static org.ballerinalang.langserver.compiler.LSCompilerUtil.getUntitledFilePath;

/**
 * Implementation of Ballerina Document extension for Language Server.
 *
 * @since 0.981.2
 */
public class BallerinaDocumentServiceImpl implements BallerinaDocumentService {

    private static final Logger logger = LoggerFactory.getLogger(BallerinaDocumentService.class);

    private final BallerinaLanguageServer ballerinaLanguageServer;
    private final WorkspaceDocumentManager documentManager;
    private final LSCompiler lsCompiler;

    public BallerinaDocumentServiceImpl(LSGlobalContext globalContext) {
        this.ballerinaLanguageServer = globalContext.get(LSGlobalContextKeys.LANGUAGE_SERVER_KEY);
        this.documentManager = globalContext.get(LSGlobalContextKeys.DOCUMENT_MANAGER_KEY);
        this.lsCompiler = new LSCompiler(documentManager);
    }

    @Override
    public CompletableFuture<BallerinaOASResponse> swaggerDef(BallerinaOASRequest request) {
        String fileUri = request.getBallerinaDocument().getUri();
        Path formattingFilePath = new LSDocument(fileUri).getPath();
        Path compilationPath = getUntitledFilePath(formattingFilePath.toString()).orElse(formattingFilePath);
        Optional<Lock> lock = documentManager.lockFile(compilationPath);

        BallerinaOASResponse reply = new BallerinaOASResponse();

        try {
            String fileContent = documentManager.getFileContent(compilationPath);
            String swaggerDefinition = SwaggerConverterUtils.generateOAS3Definitions(fileContent,
                    request.getBallerinaService());
            reply.setBallerinaOASJson(convertToJson(swaggerDefinition));
        } catch (Exception e) {
            reply.isIsError(true);
            logger.error("error: while processing service definition at converter service: " + e.getMessage(), e);
        } finally {
            lock.ifPresent(Lock::unlock);
        }

        return CompletableFuture.supplyAsync(() -> reply);
    }

    private static String convertToJson(String yamlString) throws IOException {
        ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
        Object obj = yamlReader.readValue(yamlString, Object.class);

        ObjectMapper jsonWriter = new ObjectMapper();
        return jsonWriter.writeValueAsString(obj);
    }

    @Override
    public void apiDesignDidChange(ApiDesignDidChangeParams params) {
        String fileUri = params.getDocumentIdentifier().getUri();
        Path sourceFilePath = new LSDocument(fileUri).getPath();
        Optional<Lock> lock = documentManager.lockFile(sourceFilePath);

        try {
            //Generate compilation unit for provided Open Api Sep JSON
            File tempOasJsonFile = getSwaggerFile(params.getOASDefinition());
            CodeGenerator generator = new CodeGenerator();
            List<GenSrcFile> oasSources = generator.generate(GeneratorConstants.GenType.MOCK,
                    tempOasJsonFile.getPath());

            Optional<GenSrcFile> oasServiceFile = oasSources.stream()
                    .filter(genSrcFile -> genSrcFile.getType().equals(GenSrcFile.GenFileType.GEN_SRC)).findAny();

            if (!oasServiceFile.isPresent()) {
                throw new OASGenerationException("OAS Service file is empty.");
            }

            //Generate ballerina file to get services
            BallerinaFile oasServiceBal = LSCompiler.compileContent(oasServiceFile.get().getContent(),
                    CompilerPhase.CODE_ANALYZE);

            Optional<BLangPackage> oasFilePackage = oasServiceBal.getBLangPackage();

            String fileContent = documentManager.getFileContent(sourceFilePath);
            String[] contentComponents = fileContent.split("\\n|\\r\\n|\\r");
            int lastNewLineCharIndex = Math.max(fileContent.lastIndexOf("\n"), fileContent.lastIndexOf("\r"));
            int lastCharCol = fileContent.substring(lastNewLineCharIndex + 1).length();
            int totalLines = contentComponents.length;
            Range range = new Range(new Position(0, 0), new Position(totalLines, lastCharCol));

            BallerinaFile ballerinaFile = LSCompiler.compileContent(fileContent, CompilerPhase.CODE_ANALYZE);
            Optional<BLangPackage> bLangPackage = ballerinaFile.getBLangPackage();

            if (bLangPackage.isPresent() && bLangPackage.get().symbol != null && oasFilePackage.isPresent()) {
                Optional<BLangCompilationUnit> compilationUnit = bLangPackage.get().getCompilationUnits().stream()
                        .findFirst();
                Optional<BLangCompilationUnit> oasCompilationUnit = oasFilePackage.get().getCompilationUnits()
                        .stream().findFirst();

                if (!oasCompilationUnit.isPresent() || !compilationUnit.isPresent()) {
                    return;
                }

                JsonObject targetAST = TextDocumentFormatUtil
                        .generateJSON(compilationUnit.get(), new HashMap<>(), new HashMap<>()).getAsJsonObject();
                FormattingSourceGen.build(targetAST, "CompilationUnit");
                JsonObject generatedAST = TextDocumentFormatUtil
                        .generateJSON(oasCompilationUnit.get(), new HashMap<>(), new HashMap<>()).getAsJsonObject();
                FormattingSourceGen.build(generatedAST, "CompilationUnit");
                mergeAst(targetAST, generatedAST);

                // generate source for the new ast.
                String textEditContent = FormattingSourceGen.getSourceOf(targetAST);

                // create text edit
                TextEdit textEdit = new TextEdit(range, textEditContent);
                ApplyWorkspaceEditParams applyWorkspaceEditParams = new ApplyWorkspaceEditParams();
                TextDocumentEdit textDocumentEdit = new TextDocumentEdit(params.getDocumentIdentifier(),
                        Collections.singletonList(textEdit));
                WorkspaceEdit workspaceEdit = new WorkspaceEdit(
                        Collections.singletonList(Either.forLeft(textDocumentEdit)));
                applyWorkspaceEditParams.setEdit(workspaceEdit);

                ballerinaLanguageServer.getClient().applyEdit(applyWorkspaceEditParams);
            }
        } catch (Exception ex) {
            logger.error("error: while processing service definition at converter service: " + ex.getMessage(), ex);
        } finally {
            lock.ifPresent(Lock::unlock);
        }

    }

    @Override
    public CompletableFuture<BallerinaServiceListResponse> serviceList(BallerinaServiceListRequest request) {
        BallerinaServiceListResponse reply = new BallerinaServiceListResponse();
        String fileUri = request.getDocumentIdentifier().getUri();
        Path formattingFilePath = new LSDocument(fileUri).getPath();
        Path compilationPath = getUntitledFilePath(formattingFilePath.toString()).orElse(formattingFilePath);
        Optional<Lock> lock = documentManager.lockFile(compilationPath);

        try {
            String fileContent = documentManager.getFileContent(compilationPath);
            BallerinaFile ballerinaFile = LSCompiler.compileContent(fileContent, CompilerPhase.CODE_ANALYZE);
            Optional<BLangPackage> bLangPackage = ballerinaFile.getBLangPackage();
            ArrayList<String> services = new ArrayList<>();

            if (bLangPackage.isPresent() && bLangPackage.get().symbol != null) {
                BLangCompilationUnit compilationUnit = bLangPackage.get().getCompilationUnits().stream().findFirst()
                        .orElse(null);

                List<TopLevelNode> servicePkgs = new ArrayList<>();
                servicePkgs.addAll(compilationUnit.getTopLevelNodes().stream()
                        .filter(topLevelNode -> topLevelNode instanceof ServiceNode).collect(Collectors.toList()));

                servicePkgs.forEach(servicepkg -> {
                    if (servicepkg instanceof ServiceNode) {
                        ServiceNode pkg = ((ServiceNode) servicepkg);
                        services.add(pkg.getName().getValue());
                    }
                });
            }
            reply.setServices(services.toArray(new String[0]));
        } catch (LSCompilerException | WorkspaceDocumentException e) {
            logger.error("error: while processing service definition at converter service: " + e.getMessage());
        } finally {
            lock.ifPresent(Lock::unlock);
        }

        return CompletableFuture.supplyAsync(() -> reply);
    }

    @Override
    public CompletableFuture<BallerinaASTResponse> ast(BallerinaASTRequest request) {
        BallerinaASTResponse reply = new BallerinaASTResponse();
        String fileUri = request.getDocumentIdentifier().getUri();
        Path formattingFilePath = new LSDocument(fileUri).getPath();
        Path compilationPath = getUntitledFilePath(formattingFilePath.toString()).orElse(formattingFilePath);
        Optional<Lock> lock = documentManager.lockFile(compilationPath);
        try {
            LSContext astContext = new LSServiceOperationContext();
            astContext.put(DocumentServiceKeys.FILE_URI_KEY, fileUri);
            BLangPackage bLangPackage = lsCompiler.getBLangPackage(astContext, this.documentManager, true,
                    LSCustomErrorStrategy.class, false);
            astContext.put(DocumentServiceKeys.CURRENT_BLANG_PACKAGE_CONTEXT_KEY, bLangPackage);
            reply.setAst(getTreeForContent(astContext));
            reply.setParseSuccess(true);
        } catch (LSCompilerException | JSONGenerationException e) {
            reply.setParseSuccess(false);
        } finally {
            lock.ifPresent(Lock::unlock);
        }
        return CompletableFuture.supplyAsync(() -> reply);
    }

    @Override
    public CompletableFuture<BallerinaASTDidChangeResponse> astDidChange(BallerinaASTDidChange notification) {
        BallerinaASTDidChangeResponse reply = new BallerinaASTDidChangeResponse();
        String fileUri = notification.getTextDocumentIdentifier().getUri();
        Path formattingFilePath = new LSDocument(fileUri).getPath();
        Path compilationPath = getUntitledFilePath(formattingFilePath.toString()).orElse(formattingFilePath);
        Optional<Lock> lock = documentManager.lockFile(compilationPath);
        try {
            // calculate range to replace
            String fileContent = documentManager.getFileContent(compilationPath);
            String[] contentComponents = fileContent.split("\\n|\\r\\n|\\r");
            int lastNewLineCharIndex = Math.max(fileContent.lastIndexOf("\n"), fileContent.lastIndexOf("\r"));
            int lastCharCol = fileContent.substring(lastNewLineCharIndex + 1).length();
            int totalLines = contentComponents.length;
            Range range = new Range(new Position(0, 0), new Position(totalLines, lastCharCol));

            // generate source for the new ast.
            JsonObject ast = notification.getAst();
            FormattingSourceGen.build(ast, "CompilationUnit");
            // we are reformatting entire document upon each astChange
            // until partial formatting is supported
            // FormattingVisitorEntry formattingUtil = new FormattingVisitorEntry();
            // formattingUtil.accept(ast);
            String textEditContent = FormattingSourceGen.getSourceOf(ast);

            // create text edit
            TextEdit textEdit = new TextEdit(range, textEditContent);
            ApplyWorkspaceEditParams applyWorkspaceEditParams = new ApplyWorkspaceEditParams();
            TextDocumentEdit txtDocumentEdit = new TextDocumentEdit(notification.getTextDocumentIdentifier(),
                    Collections.singletonList(textEdit));

            WorkspaceEdit workspaceEdit = new WorkspaceEdit(
                    Collections.singletonList(Either.forLeft(txtDocumentEdit)));
            applyWorkspaceEditParams.setEdit(workspaceEdit);

            // update the document
            ballerinaLanguageServer.getClient().applyEdit(applyWorkspaceEditParams);
            reply.setContent(textEditContent);
        } catch (Exception e) {
            if (CommonUtil.LS_DEBUG_ENABLED) {
                String msg = e.getMessage();
                logger.error("Error while tree modification source gen" + ((msg != null) ? ": " + msg : ""), e);
            }
        } finally {
            lock.ifPresent(Lock::unlock);
        }
        return CompletableFuture.supplyAsync(() -> reply);
    }

    @Override
    public CompletableFuture<BallerinaProject> project(BallerinaProjectParams params) {
        return CompletableFuture.supplyAsync(() -> {
            Path sourceFilePath = new LSDocument(params.getDocumentIdentifier().getUri()).getPath();
            BallerinaProject project = new BallerinaProject();
            project.setPath(getProjectDir(sourceFilePath));
            return project;
        });
    }

    private JsonElement getTreeForContent(LSContext context) throws LSCompilerException, JSONGenerationException {
        BLangPackage bLangPackage = context.get(DocumentServiceKeys.CURRENT_BLANG_PACKAGE_CONTEXT_KEY);
        CompilerContext compilerContext = context.get(DocumentServiceKeys.COMPILER_CONTEXT_KEY);
        SymbolFindVisitor symbolFindVisitor = new SymbolFindVisitor(compilerContext);

        if (bLangPackage.symbol != null) {
            symbolFindVisitor.visit(bLangPackage);
            Map<BLangNode, List<SymbolMetaInfo>> symbolMetaInfoMap = symbolFindVisitor.getVisibleSymbolsMap();
            String relativeFilePath = context.get(DocumentServiceKeys.RELATIVE_FILE_PATH_KEY);
            BLangCompilationUnit compilationUnit = bLangPackage.getCompilationUnits().stream()
                    .filter(cUnit -> cUnit.getPosition().getSource().cUnitName
                            .replace("/", CommonUtil.FILE_SEPARATOR).equals(relativeFilePath))
                    .findFirst().orElse(null);
            JsonElement jsonAST = TextDocumentFormatUtil.generateJSON(compilationUnit, new HashMap<>(),
                    symbolMetaInfoMap);
            FormattingSourceGen.build(jsonAST.getAsJsonObject(), "CompilationUnit");
            return jsonAST;
        }
        return null;
    }

    /**
     * A Util method to create a temporary swagger JSON file to be used to convert into ballerina definition.
     *
     * @param oasDefinition Swagger JSON string for file creation
     * @return Temporary file created with provided string
     * @throws IOException will throw IO Exception if file error
     */
    private File getSwaggerFile(String oasDefinition) throws IOException {
        File oasTempFile = File.createTempFile("oasTempFile", ".json");
        try (BufferedWriter bw = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(oasTempFile), StandardCharsets.UTF_8))) {
            bw.write(oasDefinition);
        }
        return oasTempFile;
    }

    /**
     * Util method to merge updated compilation unit to the current compilation unit.
     *
     * @param targetCompUnit    target compilation unit
     * @param generatedCompUnit generated compilation unit which needs to be merged
     */
    private void mergeAst(JsonObject targetCompUnit, JsonObject generatedCompUnit) {
        generatedCompUnit.getAsJsonArray("topLevelNodes").forEach(item -> {
            JsonObject topLevelNode = item.getAsJsonObject();
            if (topLevelNode.get("kind").getAsString().equals("Import")) {
                if (!hasImport(targetCompUnit, topLevelNode)) {
                    int startPosition = FormattingSourceGen.getStartPosition(targetCompUnit, "imports", -1);
                    FormattingSourceGen.reconcileWS(topLevelNode, targetCompUnit.getAsJsonArray("topLevelNodes"),
                            targetCompUnit, startPosition);
                    targetCompUnit.getAsJsonArray("topLevelNodes").add(topLevelNode);
                }
            }

            if (topLevelNode.get("kind").getAsString().equals("Service")) {
                for (JsonElement astNode : targetCompUnit.getAsJsonArray("topLevelNodes")) {
                    JsonObject targetNode = astNode.getAsJsonObject();
                    if (targetNode.get("kind").getAsString().equals("Service")) {
                        if (targetNode.get("name").getAsJsonObject().get("value")
                                .equals(topLevelNode.get("name").getAsJsonObject().get("value"))) {
                            mergeServices(targetNode, topLevelNode, targetCompUnit);
                        }
                    }
                }
            }
        });
    }

    /**
     * Util method to merge given two service nodes.
     *
     * @param originService Origin service
     * @param targetService Target service which will get merged to origin service
     */
    private void mergeServices(JsonObject originService, JsonObject targetService, JsonObject tree) {
        mergeAnnotations(originService, targetService, tree);
        List<JsonObject> targetServices = new ArrayList<>();

        for (JsonElement targetItem : targetService.getAsJsonArray("resources")) {
            JsonObject targetResource = targetItem.getAsJsonObject();
            boolean matched = false;
            for (JsonElement originItem : originService.getAsJsonArray("resources")) {
                JsonObject originResource = originItem.getAsJsonObject();
                if (matchResource(originResource, targetResource)) {
                    matched = true;
                    mergeAnnotations(originResource, targetResource, tree);
                }
            }

            if (!matched) {
                targetResource.getAsJsonObject("body").add("statements", new JsonArray());
                targetServices.add(targetResource);
            }
        }

        targetServices.forEach(resource -> {
            int startIndex = FormattingSourceGen.getStartPosition(originService, "resources", -1);
            FormattingSourceGen.reconcileWS(resource, originService.getAsJsonArray("resources"), tree, startIndex);
            originService.getAsJsonArray("resources").add(resource);
        });
    }

    /**
     * Util method to merge annotation attachments.
     *
     * @param targetNode target node
     * @param sourceNode source node which will get merged to target node
     */
    private void mergeAnnotations(JsonObject targetNode, JsonObject sourceNode, JsonObject tree) {
        JsonArray annotationAttachments = sourceNode.has("annotationAttachments")
                ? sourceNode.getAsJsonArray("annotationAttachments")
                : sourceNode.getAsJsonArray("annAttachments");
        for (JsonElement item : annotationAttachments) {
            JsonObject sourceNodeAttachment = item.getAsJsonObject();

            JsonObject matchedTargetNode = findAttachmentNode(targetNode, sourceNodeAttachment);

            if (matchedTargetNode != null) {
                if (sourceNodeAttachment.getAsJsonObject("expression").get("kind").getAsString()
                        .equals("RecordLiteralExpr")
                        && matchedTargetNode.getAsJsonObject("expression").get("kind").getAsString()
                                .equals("RecordLiteralExpr")) {

                    JsonObject sourceRecord = sourceNodeAttachment.getAsJsonObject("expression");
                    JsonObject matchedTargetRecord = matchedTargetNode.getAsJsonObject("expression");

                    if (sourceNodeAttachment.getAsJsonObject("annotationName").get("value").getAsString()
                            .equals("MultiResourceInfo")) {
                        JsonArray sourceResourceInformations = sourceRecord.getAsJsonArray("keyValuePairs").get(0)
                                .getAsJsonObject().getAsJsonObject("value").getAsJsonArray("keyValuePairs");
                        JsonArray targetResourceInformations = matchedTargetRecord.getAsJsonArray("keyValuePairs")
                                .get(0).getAsJsonObject().getAsJsonObject("value").getAsJsonArray("keyValuePairs");

                        // Get map values of the resourceInformation map in MultiResourceInfo annotation.
                        for (JsonElement sourceResourceInfoItem : sourceResourceInformations) {
                            JsonObject sourceResourceInfo = sourceResourceInfoItem.getAsJsonObject();
                            JsonObject matchedTargetResourceInfo = null;
                            for (JsonElement targetResourceInfoItem : targetResourceInformations) {
                                JsonObject targetResourceInfo = targetResourceInfoItem.getAsJsonObject();
                                if (targetResourceInfo.has("key") && targetResourceInfo.getAsJsonObject("key")
                                        .get("kind").getAsString().equals("Literal")) {
                                    JsonObject targetResourceInfoKey = targetResourceInfo.getAsJsonObject("key");
                                    JsonObject sourceResourceInfoKey = sourceResourceInfo.getAsJsonObject("key");

                                    if (sourceResourceInfoKey.get("value").getAsString()
                                            .equals(targetResourceInfoKey.get("value").getAsString())) {
                                        matchedTargetResourceInfo = targetResourceInfo;
                                    }
                                }
                            }

                            if (matchedTargetResourceInfo != null) {
                                JsonArray sourceResourceInfoOperation = sourceResourceInfo.getAsJsonObject("value")
                                        .getAsJsonArray("keyValuePairs");
                                JsonArray targetResourceInfoOperation = matchedTargetResourceInfo
                                        .getAsJsonObject("value").getAsJsonArray("keyValuePairs");

                                for (JsonElement keyValueItem : sourceResourceInfoOperation) {
                                    JsonObject sourceKeyValue = keyValueItem.getAsJsonObject();
                                    int matchedKeyValuePairIndex = 0;
                                    JsonObject matchedObj = null;
                                    for (JsonElement matchedKeyValueItem : targetResourceInfoOperation) {
                                        JsonObject matchedKeyValue = matchedKeyValueItem.getAsJsonObject();
                                        if ((matchedKeyValue.has("key") && matchedKeyValue.getAsJsonObject("key")
                                                .get("kind").getAsString().equals("SimpleVariableRef"))) {
                                            JsonObject matchedKey = matchedKeyValue.getAsJsonObject("key");
                                            JsonObject sourceKey = sourceKeyValue.getAsJsonObject("key");
                                            if (matchedKey.getAsJsonObject("variableName").get("value")
                                                    .getAsString().equals(sourceKey.getAsJsonObject("variableName")
                                                            .get("value").getAsString())) {
                                                matchedObj = matchedKeyValue;
                                                break;
                                            }
                                        }
                                        matchedKeyValuePairIndex++;
                                    }

                                    if (matchedObj != null) {
                                        List<JsonObject> matchedObjWS = FormattingSourceGen.extractWS(matchedObj);
                                        int firstTokenIndex = matchedObjWS.get(0).get("i").getAsInt();
                                        targetResourceInfoOperation.remove(matchedKeyValuePairIndex);
                                        FormattingSourceGen.reconcileWS(sourceKeyValue, targetResourceInfoOperation,
                                                tree, firstTokenIndex);
                                        targetResourceInfoOperation.add(sourceKeyValue);
                                    } else {
                                        // Add new key value pair to the annotation record.
                                        FormattingSourceGen.reconcileWS(sourceKeyValue, targetResourceInfoOperation,
                                                tree, -1);
                                        targetResourceInfoOperation.add(sourceKeyValue);

                                        if (targetResourceInfoOperation.size() > 1) {
                                            // Add a new comma to separate the new key value pair.
                                            int startIndex = FormattingSourceGen.extractWS(sourceKeyValue).get(0)
                                                    .getAsJsonObject().get("i").getAsInt();
                                            FormattingSourceGen.addNewWS(
                                                    matchedTargetResourceInfo.getAsJsonObject("value"), tree, "",
                                                    ",", true, startIndex);
                                        }
                                    }
                                }

                            } else {
                                FormattingSourceGen.reconcileWS(sourceResourceInfo, targetResourceInformations,
                                        tree, -1);
                                targetResourceInformations.add(sourceResourceInfo);
                            }
                        }

                    } else {
                        for (JsonElement keyValueItem : sourceRecord.getAsJsonArray("keyValuePairs")) {
                            JsonObject sourceKeyValue = keyValueItem.getAsJsonObject();
                            int matchedKeyValuePairIndex = 0;
                            JsonObject matchedObj = null;

                            for (JsonElement matchedKeyValueItem : matchedTargetRecord
                                    .getAsJsonArray("keyValuePairs")) {
                                JsonObject matchedKeyValue = matchedKeyValueItem.getAsJsonObject();
                                if ((matchedKeyValue.has("key") && matchedKeyValue.getAsJsonObject("key")
                                        .get("kind").getAsString().equals("SimpleVariableRef"))) {
                                    JsonObject matchedKey = matchedKeyValue.getAsJsonObject("key");
                                    JsonObject sourceKey = sourceKeyValue.getAsJsonObject("key");
                                    if (matchedKey.getAsJsonObject("variableName").get("value").getAsString()
                                            .equals(sourceKey.getAsJsonObject("variableName").get("value")
                                                    .getAsString())) {
                                        matchedObj = matchedKeyValue;
                                        break;
                                    }
                                }
                                matchedKeyValuePairIndex++;
                            }

                            if (matchedObj != null) {
                                List<JsonObject> matchedObjWS = FormattingSourceGen.extractWS(matchedObj);
                                int firstTokenIndex = matchedObjWS.get(0).get("i").getAsInt();
                                matchedTargetRecord.getAsJsonArray("keyValuePairs")
                                        .remove(matchedKeyValuePairIndex);
                                FormattingSourceGen.reconcileWS(sourceKeyValue,
                                        matchedTargetRecord.getAsJsonArray("keyValuePairs"), tree, firstTokenIndex);
                                matchedTargetRecord.getAsJsonArray("keyValuePairs").add(sourceKeyValue);
                            } else {
                                // Add the new record key value pair.
                                FormattingSourceGen.reconcileWS(sourceKeyValue,
                                        matchedTargetRecord.getAsJsonArray("keyValuePairs"), tree, -1);
                                matchedTargetRecord.getAsJsonArray("keyValuePairs").add(sourceKeyValue);

                                if (matchedTargetRecord.getAsJsonArray("keyValuePairs").size() > 1) {
                                    // Add a new comma to separate the new key value pair.
                                    int startIndex = FormattingSourceGen.extractWS(sourceKeyValue).get(0)
                                            .getAsJsonObject().get("i").getAsInt();
                                    FormattingSourceGen.addNewWS(matchedTargetRecord, tree, "", ",", true,
                                            startIndex);
                                }
                            }
                        }
                    }
                }
            } else {
                int startIndex = FormattingSourceGen.getStartPosition(targetNode, "annAttachments", -1);
                JsonArray targetAnnAttachments = targetNode.has("annotationAttachments")
                        ? targetNode.getAsJsonArray("annotationAttachments")
                        : targetNode.getAsJsonArray("annAttachments");
                FormattingSourceGen.reconcileWS(sourceNodeAttachment, targetAnnAttachments, tree, startIndex);
                targetAnnAttachments.add(sourceNodeAttachment);
            }

        }
    }

    private JsonObject findAttachmentNode(JsonObject targetNode, JsonObject sourceNodeAttachment) {
        JsonObject matchedNode = null;
        JsonArray annotationAttachments = targetNode.has("annotationAttachments")
                ? targetNode.getAsJsonArray("annotationAttachments")
                : targetNode.getAsJsonArray("annAttachments");
        for (JsonElement item : annotationAttachments) {
            JsonObject attachmentNode = item.getAsJsonObject();
            if (sourceNodeAttachment.getAsJsonObject("annotationName").get("value").getAsString()
                    .equals(attachmentNode.getAsJsonObject("annotationName").get("value").getAsString())
                    && sourceNodeAttachment.getAsJsonObject("packageAlias").get("value").getAsString()
                            .equals(attachmentNode.getAsJsonObject("packageAlias").get("value").getAsString())) {
                matchedNode = attachmentNode;
                break;
            }
        }
        return matchedNode;
    }

    /**
     * Util method to match given resource in a service node.
     *
     * @param astResource     service node
     * @param swaggerResource resource which needs to be checked
     * @return true if matched else false
     */
    private boolean matchResource(JsonObject astResource, JsonObject swaggerResource) {
        return astResource.getAsJsonObject("name").get("value").getAsString()
                .equals(swaggerResource.getAsJsonObject("name").get("value").getAsString());
    }

    /**
     * Util method to check if given node is an existing import in current AST model.
     *
     * @param originAst    - current AST model
     * @param mergePackage - Import Node
     * @return - boolean status
     */
    private boolean hasImport(JsonObject originAst, JsonObject mergePackage) {
        boolean importFound = false;

        for (JsonElement node : originAst.getAsJsonArray("topLevelNodes")) {
            JsonObject originNode = node.getAsJsonObject();
            if (importFound) {
                break;
            } else if (originNode.get("kind").getAsString().equals("Import")
                    && originNode.get("orgName").getAsJsonObject().get("value").getAsString()
                            .equals(mergePackage.get("orgName").getAsJsonObject().get("value").getAsString())
                    && originNode.getAsJsonArray("packageName").size() == mergePackage.getAsJsonArray("packageName")
                            .size()) {
                JsonArray packageName = originNode.getAsJsonArray("packageName");
                for (int i = 0; i < packageName.size(); i++) {
                    JsonArray mergePackageName = mergePackage.getAsJsonArray("packageName");
                    if (mergePackageName.get(i).getAsJsonObject().get("value").getAsString()
                            .equals(packageName.get(i).getAsJsonObject().get("value").getAsString())) {
                        importFound = true;
                    } else {
                        importFound = false;
                        break;
                    }
                }
            }
        }

        return importFound;
    }

}