Java tutorial
/* * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. 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 org.ballerinalang.composer.service.workspace.rest.datamodel; 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 org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.ballerinalang.compiler.CompilerPhase; import org.ballerinalang.composer.service.workspace.langconstruct.ModelPackage; import org.ballerinalang.composer.service.workspace.util.WorkspaceUtils; import org.ballerinalang.model.Whitespace; import org.ballerinalang.model.elements.Flag; import org.ballerinalang.model.tree.IdentifierNode; 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.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.BLangStruct; import org.wso2.ballerinalang.compiler.tree.expressions.BLangInvocation; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.ws.rs.Consumes; import javax.ws.rs.OPTIONS; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; /** * Basic classes which exposes ballerina language object model over REST service. */ @Path("/ballerina") public class BLangFileRestService { private static final Logger logger = LoggerFactory.getLogger(BLangFileRestService.class); private static final String SYMBOL_TYPE = "symbolType"; private static final String INVOCATION_TYPE = "invocationType"; public static final String UNESCAPED_VALUE = "unescapedValue"; public static final String PACKAGE_REGEX = "package\\s+([a-zA_Z_][\\.\\w]*);"; @POST @Path("/file/validate-and-parse") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response validateAndParseBFile(BFile bFileRequest) throws IOException, InvocationTargetException, IllegalAccessException { return Response.status(Response.Status.OK).entity(validateAndParse(bFileRequest)) .header("Access-Control-Allow-Origin", '*').type(MediaType.APPLICATION_JSON).build(); } @OPTIONS @Path("/file/validate-and-parse") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response validateAndParseOptions() { return Response.ok().header("Access-Control-Max-Age", "600 ").header("Access-Control-Allow-Origin", "*") .header("Access-Control-Allow-Credentials", "true") .header("Access-Control-Allow-Methods", "POST, GET, PUT, UPDATE, DELETE, OPTIONS, HEAD") .header("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With").build(); } @POST @Path("/model/parse-fragment") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response getBallerinaJsonDataModelGivenFragment(BLangSourceFragment sourceFragment) throws IOException { String response = BLangFragmentParser.parseFragment(sourceFragment); return Response.ok(response, MediaType.APPLICATION_JSON).header("Access-Control-Allow-Origin", '*').build(); } @OPTIONS @Path("/model/parse-fragment") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response optionsParseFragment() { return Response.ok().header("Access-Control-Max-Age", "600 ").header("Access-Control-Allow-Origin", "*") .header("Access-Control-Allow-Credentials", "true") .header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") .header("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With").build(); } public static JsonElement generateJSON(Node node, Map<String, Node> anonStructs) throws InvocationTargetException, IllegalAccessException { 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 */ 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 = m.invoke(node); /* Literal class - This class is escaped in backend to address cases like "ss\"" and 8.0 and null */ if (node.getKind() == NodeKind.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.USER_DEFINED_TYPE && jsonName.equals("typeName")) { IdentifierNode typeNode = (IdentifierNode) prop; Node structNode; if (typeNode.getValue().startsWith("$anonStruct$") && (structNode = anonStructs.remove(typeNode.getValue())) != null) { JsonObject anonStruct = generateJSON(structNode, anonStructs).getAsJsonObject(); anonStruct.addProperty("anonStruct", true); nodeJson.add("anonStruct", anonStruct); continue; } } 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)); } 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 BLangStruct && ((BLangStruct) listPropItem).isAnonymous) { anonStructs.put(((BLangStruct) listPropItem).getName().getValue(), ((BLangStruct) listPropItem)); continue; } if (listPropItem instanceof BLangFunction && (((BLangFunction) listPropItem)).name.value.startsWith("$lambda$")) { continue; } } listPropJson.add(generateJSON((Node) listPropItem, anonStructs)); } 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 != null) { nodeJson.addProperty(jsonName, prop.toString()); String message = "Node " + node.getClass().getSimpleName() + " contains unknown type prop: " + jsonName + " of type " + prop.getClass(); logger.error(message); } } return nodeJson; } private static JsonArray getType(Node node) { BType type = ((BLangNode) node).type; if (node instanceof BLangInvocation) { JsonArray jsonElements = new JsonArray(); for (BType returnType : ((BLangInvocation) node).types) { jsonElements.add(returnType.getKind().typeName()); } return jsonElements; } else if (type != null) { JsonArray jsonElements = new JsonArray(); jsonElements.add(type.getKind().typeName()); return jsonElements; } return null; } private static String toJsonName(String name, int prefixLen) { return Character.toLowerCase(name.charAt(prefixLen)) + name.substring(prefixLen + 1); } /** * Validates a given ballerina input. * * @param bFileRequest - Object which holds data about Ballerina content. * @return List of errors if any */ private JsonObject validateAndParse(BFile bFileRequest) throws InvocationTargetException, IllegalAccessException { final String filePath = bFileRequest.getFilePath(); final String fileName = bFileRequest.getFileName(); final String content = bFileRequest.getContent(); Pattern pkgPattern = Pattern.compile(PACKAGE_REGEX); Matcher pkgMatcher = pkgPattern.matcher(content); String programDir = null; String unitToCompile = fileName; if (pkgMatcher.find()) { final String packageName = pkgMatcher.group(1); if (bFileRequest.needProgramDir() && packageName != null) { List<String> pathParts = Arrays.asList(filePath.split(Pattern.quote(File.separator))); List<String> pkgParts = Arrays.asList(packageName.split(Pattern.quote("."))); Collections.reverse(pkgParts); boolean foundProgramDir = true; for (int i = 1; i <= pkgParts.size(); i++) { if (!pathParts.get(pathParts.size() - i).equals(pkgParts.get(i - 1))) { foundProgramDir = false; break; } } if (foundProgramDir) { List<String> programDirParts = pathParts.subList(0, pathParts.size() - pkgParts.size()); programDir = String.join(File.separator, programDirParts); unitToCompile = packageName; } } } // compile package in disk to resolve constructs in complete package (including constructs from other files) final BLangPackage packageFromDisk = Files.exists(Paths.get(filePath, fileName)) ? WorkspaceUtils .getBallerinaFile(programDir != null ? programDir : filePath, unitToCompile).getBLangPackage() : null; // always use dirty content from editor to generate model // TODO: Remove this once in-memory file resolver with dirty content for compiler is implemented final BallerinaFile balFileFromDirtyContent = WorkspaceUtils.getBallerinaFileForContent(fileName, content, CompilerPhase.CODE_ANALYZE); // always get compilation unit and diagnostics from dirty content final BLangPackage model = balFileFromDirtyContent.getBLangPackage(); final List<Diagnostic> diagnostics = balFileFromDirtyContent.getDiagnostics(); ErrorCategory errorCategory = ErrorCategory.NONE; if (!diagnostics.isEmpty()) { if (model == null) { errorCategory = ErrorCategory.SYNTAX; } else { errorCategory = ErrorCategory.SEMANTIC; } } JsonArray errors = new JsonArray(); final String errorCategoryName = errorCategory.name(); diagnostics.forEach(diagnostic -> { JsonObject error = new JsonObject(); Diagnostic.DiagnosticPosition position = diagnostic.getPosition(); if (position != null) { if (!diagnostic.getSource().getCompilationUnitName().equals(fileName)) { return; } error.addProperty("row", position.getStartLine()); error.addProperty("column", position.getStartColumn()); error.addProperty("type", "error"); error.addProperty("category", errorCategoryName); } else { // position == null means it's a bug in core side. error.addProperty("category", ErrorCategory.RUNTIME.name()); } error.addProperty("text", diagnostic.getMessage()); errors.add(error); }); JsonObject result = new JsonObject(); result.add("errors", errors); Gson gson = new Gson(); JsonElement diagnosticsJson = gson.toJsonTree(diagnostics); result.add("diagnostics", diagnosticsJson); if (model != null && bFileRequest.needTree()) { BLangCompilationUnit compilationUnit = model.getCompilationUnits().stream() .filter(compUnit -> fileName.equals(compUnit.getName())).findFirst().get(); JsonElement modelElement = generateJSON(compilationUnit, new HashMap<>()); result.add("model", modelElement); } // adding current package info whenever we have a parsed model final Map<String, ModelPackage> modelPackage = new HashMap<>(); // file is in a package, load constructs from package in disk if (packageFromDisk != null) { WorkspaceUtils.loadPackageMap("Current Package", packageFromDisk, modelPackage); // remove constructs from current file and later add them from dirty content WorkspaceUtils.removeConstructsOfFile("Current Package", fileName, modelPackage); } // add constructs in current file's dirty content to package map WorkspaceUtils.loadPackageMap("Current Package", balFileFromDirtyContent.getBLangPackage(), modelPackage); // Add 'packageInfo' only if there are any packages. Optional<ModelPackage> packageInfoJson = modelPackage.values().stream().findFirst(); if (packageInfoJson.isPresent() && bFileRequest.needPackageInfo()) { JsonElement packageInfo = gson.toJsonTree(packageInfoJson.get()); result.add("packageInfo", packageInfo); } if (programDir != null) { result.addProperty("programDirPath", programDir); } return result; } /** * Enum for Error Category. */ public enum ErrorCategory { SYNTAX, SEMANTIC, RUNTIME, NONE; } }