Java tutorial
/* * 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.compiler.format; import com.google.common.base.CaseFormat; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.ballerinalang.langserver.compiler.LSCompiler; import org.ballerinalang.langserver.compiler.LSCompilerException; import org.ballerinalang.langserver.compiler.LSCompilerUtil; import org.ballerinalang.langserver.compiler.LSContext; import org.ballerinalang.langserver.compiler.common.LSCustomErrorStrategy; import org.ballerinalang.langserver.compiler.common.modal.SymbolMetaInfo; import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentManager; import org.ballerinalang.model.Whitespace; import org.ballerinalang.model.elements.AttachPoint; import org.ballerinalang.model.elements.Flag; import org.ballerinalang.model.tree.Node; import org.ballerinalang.model.tree.NodeKind; import org.ballerinalang.model.tree.OperatorKind; import org.ballerinalang.util.diagnostic.Diagnostic; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wso2.ballerinalang.compiler.semantics.model.types.BType; import org.wso2.ballerinalang.compiler.tree.BLangAnnotation; import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit; import org.wso2.ballerinalang.compiler.tree.BLangFunction; import org.wso2.ballerinalang.compiler.tree.BLangNode; import org.wso2.ballerinalang.compiler.tree.BLangPackage; import org.wso2.ballerinalang.compiler.tree.BLangRecordVariable; import org.wso2.ballerinalang.compiler.tree.expressions.BLangInvocation; import org.wso2.ballerinalang.compiler.tree.expressions.BLangRecordVarRef; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * Utilities for text document format. */ public class TextDocumentFormatUtil { private static final Logger logger = LoggerFactory.getLogger(TextDocumentFormatUtil.class); private static final String SYMBOL_TYPE = "symbolType"; private static final String INVOCATION_TYPE = "invocationType"; private static final String UNESCAPED_VALUE = "unescapedValue"; /** * Get the AST for the current text document's content. * * @param file File path as a URI * @param lsCompiler Language server compiler * @param documentManager Workspace document manager instance * @param context Document formatting context * @return {@link JsonObject} AST as a Json Object * @throws JSONGenerationException when AST build fails * @throws LSCompilerException when compilation fails */ public static JsonObject getAST(Path file, LSCompiler lsCompiler, WorkspaceDocumentManager documentManager, LSContext context) throws JSONGenerationException, LSCompilerException { String path = file.toAbsolutePath().toString(); String sourceRoot = LSCompilerUtil.getSourceRoot(file); String packageName = LSCompilerUtil.getPackageNameForGivenFile(sourceRoot, path); String[] breakFromPackage = path.split(Pattern.quote(packageName + File.separator)); String relativePath = breakFromPackage[breakFromPackage.length - 1]; final BLangPackage bLangPackage = lsCompiler.getBLangPackage(context, documentManager, true, LSCustomErrorStrategy.class, false); final List<Diagnostic> diagnostics = new ArrayList<>(); JsonArray errors = new JsonArray(); JsonObject result = new JsonObject(); result.add("errors", errors); Gson gson = new Gson(); JsonElement diagnosticsJson = gson.toJsonTree(diagnostics); result.add("diagnostics", diagnosticsJson); BLangCompilationUnit compilationUnit; // If package is testable package process as tests // else process normally if (isTestablePackage(relativePath)) { compilationUnit = bLangPackage.getTestablePkg().getCompilationUnits().stream() .filter(compUnit -> (relativePath).equals(compUnit.getName())).findFirst().orElse(null); } else { compilationUnit = bLangPackage.getCompilationUnits().stream() .filter(compUnit -> relativePath.equals(compUnit.getName())).findFirst().orElse(null); } JsonElement modelElement = generateJSON(compilationUnit, new HashMap<>(), new HashMap<>()); result.add("model", modelElement); return result; } private static boolean isTestablePackage(String relativeFilePath) { return relativeFilePath.startsWith("tests" + File.separator); } /** * Generate json representation for the given node. * * @param node Node to get the json representation * @param anonStructs Map of anonymous structs * @param symbolMetaInfoMap symbol meta information map * @return {@link JsonElement} Json Representation of the node * @throws JSONGenerationException when Json error occurs */ public static JsonElement generateJSON(Node node, Map<String, Node> anonStructs, Map<BLangNode, List<SymbolMetaInfo>> symbolMetaInfoMap) throws JSONGenerationException { if (node == null) { return JsonNull.INSTANCE; } Set<Method> methods = ClassUtils.getAllInterfaces(node.getClass()).stream() .flatMap(aClass -> Arrays.stream(aClass.getMethods())).collect(Collectors.toSet()); JsonObject nodeJson = new JsonObject(); JsonArray wsJsonArray = new JsonArray(); Set<Whitespace> ws = node.getWS(); if (ws != null && !ws.isEmpty()) { for (Whitespace whitespace : ws) { JsonObject wsJson = new JsonObject(); wsJson.addProperty("ws", whitespace.getWs()); wsJson.addProperty("i", whitespace.getIndex()); wsJson.addProperty("text", whitespace.getPrevious()); wsJson.addProperty("static", whitespace.isStatic()); wsJsonArray.add(wsJson); } nodeJson.add("ws", wsJsonArray); } Diagnostic.DiagnosticPosition position = node.getPosition(); if (position != null) { JsonObject positionJson = new JsonObject(); positionJson.addProperty("startColumn", position.getStartColumn()); positionJson.addProperty("startLine", position.getStartLine()); positionJson.addProperty("endColumn", position.getEndColumn()); positionJson.addProperty("endLine", position.getEndLine()); nodeJson.add("position", positionJson); } /* Virtual props */ // Add UUID for each node. nodeJson.addProperty("id", UUID.randomUUID().toString()); // Add the visible endpoints for a given node if (symbolMetaInfoMap.containsKey(node)) { List<SymbolMetaInfo> endpointMetaList = symbolMetaInfoMap.get(node); JsonArray endpoints = new JsonArray(); endpointMetaList.forEach(symbolMetaInfo -> endpoints.add(symbolMetaInfo.getJson())); nodeJson.add("VisibleEndpoints", endpoints); } JsonArray type = getType(node); if (type != null) { nodeJson.add(SYMBOL_TYPE, type); } if (node.getKind() == NodeKind.INVOCATION) { assert node instanceof BLangInvocation : node.getClass(); BLangInvocation invocation = (BLangInvocation) node; if (invocation.symbol != null && invocation.symbol.kind != null) { nodeJson.addProperty(INVOCATION_TYPE, invocation.symbol.kind.toString()); } } for (Method m : methods) { String name = m.getName(); if (name.equals("getWS") || name.equals("getPosition")) { continue; } String jsonName; if (name.startsWith("get")) { jsonName = toJsonName(name, 3); } else if (name.startsWith("is")) { jsonName = toJsonName(name, 2); } else { continue; } Object prop = null; try { prop = m.invoke(node); } catch (IllegalAccessException | InvocationTargetException e) { throw new JSONGenerationException("Error occurred while generating JSON", e); } /* Literal class - This class is escaped in backend to address cases like "ss\"" and 8.0 and null */ if ((node.getKind() == NodeKind.LITERAL || node.getKind() == NodeKind.NUMERIC_LITERAL) && "value".equals(jsonName)) { if (prop instanceof String) { nodeJson.addProperty(jsonName, '"' + StringEscapeUtils.escapeJava((String) prop) + '"'); nodeJson.addProperty(UNESCAPED_VALUE, String.valueOf(prop)); } else { nodeJson.addProperty(jsonName, String.valueOf(prop)); } continue; } if (node.getKind() == NodeKind.ANNOTATION && node instanceof BLangAnnotation) { JsonArray attachmentPoints = new JsonArray(); ((BLangAnnotation) node).getAttachPoints().stream().map(AttachPoint::getValue) .map(JsonPrimitive::new).forEach(attachmentPoints::add); nodeJson.add("attachmentPoints", attachmentPoints); } if (prop instanceof List && jsonName.equals("types")) { // Currently we don't need any Symbols for the UI. So skipping for now. continue; } /* Node classes */ if (prop instanceof Node) { nodeJson.add(jsonName, generateJSON((Node) prop, anonStructs, symbolMetaInfoMap)); } else if (prop instanceof List) { List listProp = (List) prop; JsonArray listPropJson = new JsonArray(); nodeJson.add(jsonName, listPropJson); for (Object listPropItem : listProp) { if (listPropItem instanceof Node) { /* Remove top level anon func and struct */ if (node.getKind() == NodeKind.COMPILATION_UNIT) { if (listPropItem instanceof BLangFunction && (((BLangFunction) listPropItem)).name.value.startsWith("$lambda$")) { continue; } } listPropJson.add(generateJSON((Node) listPropItem, anonStructs, symbolMetaInfoMap)); } else if (listPropItem instanceof BLangRecordVarRef.BLangRecordVarRefKeyValue) { listPropJson.add(generateJSON( ((BLangRecordVarRef.BLangRecordVarRefKeyValue) listPropItem).getVariableName(), anonStructs, symbolMetaInfoMap)); listPropJson.add(generateJSON( ((BLangRecordVarRef.BLangRecordVarRefKeyValue) listPropItem).getBindingPattern(), anonStructs, symbolMetaInfoMap)); } else if (listPropItem instanceof BLangRecordVariable.BLangRecordVariableKeyValue) { listPropJson.add(generateJSON( ((BLangRecordVariable.BLangRecordVariableKeyValue) listPropItem).getKey(), anonStructs, symbolMetaInfoMap)); listPropJson.add(generateJSON( ((BLangRecordVariable.BLangRecordVariableKeyValue) listPropItem).getValue(), anonStructs, symbolMetaInfoMap)); } else if (listPropItem instanceof String) { listPropJson.add((String) listPropItem); } else { logger.debug("Can't serialize " + jsonName + ", has a an array of " + listPropItem); } } /* Runtime model classes */ } else if (prop instanceof Set && jsonName.equals("flags")) { Set flags = (Set) prop; for (Flag flag : Flag.values()) { nodeJson.addProperty(StringUtils.lowerCase(flag.toString()), flags.contains(flag)); } } else if (prop instanceof Set) { // TODO : limit this else if to getInputs getOutputs of transform. Set vars = (Set) prop; JsonArray listVarJson = new JsonArray(); nodeJson.add(jsonName, listVarJson); for (Object obj : vars) { listVarJson.add(obj.toString()); } } else if (prop instanceof NodeKind) { String kindName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, prop.toString()); nodeJson.addProperty(jsonName, kindName); } else if (prop instanceof OperatorKind) { nodeJson.addProperty(jsonName, prop.toString()); /* Generic classes */ } else if (prop instanceof String) { nodeJson.addProperty(jsonName, (String) prop); } else if (prop instanceof Number) { nodeJson.addProperty(jsonName, (Number) prop); } else if (prop instanceof Boolean) { nodeJson.addProperty(jsonName, (Boolean) prop); } else if (prop instanceof Enum) { nodeJson.addProperty(jsonName, StringUtils.lowerCase(((Enum) prop).name())); } else if (prop instanceof int[]) { int[] intArray = ((int[]) prop); JsonArray intArrayPropJson = new JsonArray(); nodeJson.add(jsonName, intArrayPropJson); for (int intProp : intArray) { intArrayPropJson.add(intProp); } } else if (prop != null) { nodeJson.addProperty(jsonName, prop.toString()); } } return nodeJson; } /** * Convert given name to the Json object name. * * @param name Name to be converted * @param prefixLen Length of prefix * @return {@link String} Converted value */ public static String toJsonName(String name, int prefixLen) { return Character.toLowerCase(name.charAt(prefixLen)) + name.substring(prefixLen + 1); } /** * Get Type of the node as an Json Array. * * @param node Node to get the types * @return {@link JsonArray} Converted array value */ public static JsonArray getType(Node node) { if (!(node instanceof BLangNode)) { return null; } BType type = ((BLangNode) node).type; if (node instanceof BLangInvocation) { return new JsonArray(); } else if (type != null) { JsonArray jsonElements = new JsonArray(); jsonElements.add(type.getKind().typeName()); return jsonElements; } return null; } }