+ * + *
+ "ProtocolMessage": { // implemented by {@link Message} + "type": "object", + "description": "Base class of requests, responses, and events.", + "properties": { + "seq": { // implemented by (depending on type, with conversion to/from String): + // {@link METPRequestMessage#getId()}, or + // {@link METPNotificationMessage#getId()} or + // {@link METPResponseMessage#getResponseId()} + "type": "integer", + "description": "Sequence number." + }, + "type": { // implicit in type of subclass of {@link Message} + "type": "string", + "description": "Message type.", + "_enum": [ "request", "response", "event" ] + } + }, + "required": [ "seq", "type" ] + }, + "Request": { // implemented by {@link METPRequestMessage} + "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { + "type": "object", + "description": "A client or server-initiated request.", + "properties": { + "type": { // implicit by being of type {@link METPRequestMessage} + "type": "string", + "enum": [ "request" ] + }, + "command": { // implemented by {@link METPRequestMessage#getMethod()} + "type": "string", + "description": "The command to execute." + }, + "arguments": { // implemented by {@link METPRequestMessage#getParams()} + "type": [ "array", "boolean", "integer", "null", "number" , "object", "string" ], + "description": "Object containing arguments for the command." + } + }, + "required": [ "type", "command" ] + }] + }, + "Event": { // implemented by {@link METPNotificationMessage} + "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { + "type": "object", + "description": "Server-initiated event.", + "properties": { + "type": { // implicit by being of type {@link METPNotificationMessage} + "type": "string", + "enum": [ "event" ] + }, + "event": { // implemented by {@link METPNotificationMessage#getMethod()} + "type": "string", + "description": "Type of event." + }, + "body": { // implemented by {@link METPNotificationMessage#getParams()} + "type": [ "array", "boolean", "integer", "null", "number" , "object", "string" ], + "description": "Event-specific information." + } + }, + "required": [ "type", "event" ] + }] + }, + "Response": { // implemented by {@link METPResponseMessage} + "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { + "type": "object", + "description": "Response to a request.", + "properties": { + "type": { // implicit by being of type {@link METPResponseMessage} + "type": "string", + "enum": [ "response" ] + }, + "request_seq": { // implemented by {@link METPResponseMessage#getId()} + "type": "integer", + "description": "Sequence number of the corresponding request." + }, + "success": { // implemented by {@link METPResponseMessage#getError()} == null + "type": "boolean", + "description": "Outcome of the request." + }, + "command": { // implemented by {@link METPResponseMessage#getMethod()} + "type": "string", + "description": "The command requested." + }, + "message": { // implemented by {@link ResponseError#getMessage()} + "type": "string", + "description": "Contains error message if success == false." + }, + "body": { // implemented by {@link METPResponseMessage#getResult()} for success and {@link ResponseError#getData()} for error + "type": [ "array", "boolean", "integer", "null", "number" , "object", "string" ], + "description": "Contains request result if success is true and optional error details if success is false." + } + }, + "required": [ "type", "request_seq", "success", "command" ] + }] + }, + *+ * + */ +public class EaopMessageTypeAdapter extends MessageTypeAdapter { + + public static class Factory implements TypeAdapterFactory { + + private final MessageJsonHandler handler; + + public Factory(MessageJsonHandler handler) { + this.handler = handler; + } + + @Override + @SuppressWarnings("unchecked") + public
(.*)<\/code>/g, "'$1'");
+ numIndents++;
+ description = description.replace(/\n/g, '\n' + indent());
+ numIndents--;
+ if (description.indexOf('\n') >= 0) {
+ return line(`/** ${description}\n${indent()}*/`);
+ } else {
+ return line(`/** ${description} */`);
+ }
+ }
+ return '';
+}
+
+function openBlock(str: string, openChar?: string, indent?: boolean): string {
+ indent = typeof indent === 'boolean' ? indent : true;
+ openChar = openChar || ' {';
+ let s = line(`${str}${openChar}`, true, indent);
+ numIndents++;
+ return s;
+}
+
+function closeBlock(closeChar?: string, newline?: boolean): string {
+ newline = typeof newline === 'boolean' ? newline : true;
+ closeChar = closeChar || '}';
+ numIndents--;
+ return line(closeChar, newline);
+}
+
+function propertyType(prop: any): string {
+ if (prop.$ref) {
+ return getRef(prop.$ref);
+ }
+ switch (prop.type) {
+ case 'array':
+ const s = propertyType(prop.items);
+ if (s.indexOf(' ') >= 0) {
+ return `(${s})[]`;
+ }
+ return `${s}[]`;
+ case 'object':
+ return objectType(prop);
+ case 'string':
+ if (prop.enum) {
+ return enumAsOrType(prop.enum);
+ }
+ return `String`;
+ case 'integer':
+ return 'Integer';
+ }
+ if (Array.isArray(prop.type)) {
+ if (prop.type.length === 7 && prop.type.sort().join() === 'array,boolean,integer,null,number,object,string') { // silly way to detect all possible json schema types
+ return 'Object';
+ } else {
+ return prop.type.map(v => v === 'integer' ? 'Integer' : v).join(' | ');
+ }
+ }
+ return prop.type;
+}
+
+function objectType(prop: any): string {
+ if (prop.properties) {
+ let s = openBlock('', '{', false);
+
+ for (let propName in prop.properties) {
+ const required = prop.required ? prop.required.indexOf(propName) >= 0 : false;
+ s += property(propName, !required, prop.properties[propName]);
+ }
+
+ s += closeBlock('}', false);
+ return s;
+ }
+ if (prop.additionalProperties) {
+ return `{ [key: string]: ${orType(prop.additionalProperties.type)}; }`;
+ }
+ return '{}';
+}
+
+function orType(enm: string | string[]): string {
+ if (typeof enm === 'string') {
+ return enm;
+ }
+ return enm.join(' | ');
+}
+
+function requestArg(name: string, prop: P.PropertyType): string {
+ let s = '';
+ s += comment(prop);
+ const type = propertyType(prop);
+ const propertyDef = `${type} ${name}`;
+ if (type[0] === '\'' && type[type.length-1] === '\'' && type.indexOf('|') < 0) {
+ s += line(`// ${propertyDef};`);
+ } else {
+ s += line(`${propertyDef};`);
+ }
+ return s;
+}
+
+function property(name: string, optional: boolean, prop: P.PropertyType): string {
+ let s = '';
+ s += comment(prop);
+ const type = propertyType(prop);
+ const propertyDef = `${name}${optional ? '?' : ''}: ${type}`;
+ if (type[0] === '\'' && type[type.length-1] === '\'' && type.indexOf('|') < 0) {
+ s += line(`// ${propertyDef};`);
+ } else {
+ s += line(`${propertyDef};`);
+ }
+ return s;
+}
+
+function getRef(ref: string): string {
+ const REXP = /#\/(.+)\/(.+)/;
+ const matches = REXP.exec(ref);
+ if (matches && matches.length === 3) {
+ return matches[2];
+ }
+ console.log('error: ref');
+ return ref;
+}
+
+function indent(): string {
+ return '\t'.repeat(numIndents);
+}
+
+function line(str?: string, newline?: boolean, indnt?: boolean): string {
+ newline = typeof newline === 'boolean' ? newline : true;
+ indnt = typeof indnt === 'boolean' ? indnt : true;
+ let s = '';
+ if (str) {
+ if (indnt) {
+ s += indent();
+ }
+ s += str;
+ }
+ if (newline) {
+ s += '\n';
+ }
+ return s;
+}
+
+
+/// Main
+
+/*
+const debugProtocolSchema = JSON.parse(fs.readFileSync('./ModelExecutionTraceProtocol.json').toString());
+
+const emitStr = TSGeneratorModule('ModelExecutionTraceProtocol', debugProtocolSchema);
+
+fs.writeFileSync(`../metp_protocol/src/ModelExecutionTraceProtocol.ts`, emitStr, { encoding: 'utf-8'});
+*/
diff --git a/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/JavaServerGenerator.ts b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/JavaServerGenerator.ts
new file mode 100644
index 000000000..db51d3c86
--- /dev/null
+++ b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/JavaServerGenerator.ts
@@ -0,0 +1,331 @@
+/*---------------------------------------------------------
+ * Copyright (c) 2020 Inria and others.. All rights reserved.
+ *--------------------------------------------------------*/
+
+'use strict';
+
+//import * as fs from 'fs';
+import {IProtocol, Protocol as P} from './json_schema';
+import {ResponseHelper, getJavaSafeName, capitalizeFirstLetter, isJavaKeyWord} from './json_schemaHelpers'
+
+let numIndents = 0;
+
+export function JavaServerGeneratorModule(moduleName: string, basePackageName: string, schema: IProtocol): string {
+
+ let s = '';
+ s += line("/*---------------------------------------------------------------------------------------------");
+ s += line(" * Copyright (c) 2020 Inria and others.");
+ s += line(" * All rights reserved. This program and the accompanying materials");
+ s += line(" * are made available under the terms of the Eclipse Public License v1.0");
+ s += line(" * which accompanies this distribution, and is available at");
+ s += line(" * http://www.eclipse.org/legal/epl-v10.html");
+ s += line(" *--------------------------------------------------------------------------------------------*/");
+ s += line("/* GENERATED FILE, DO NOT MODIFY MANUALLY */");
+ s += line();
+ s += line("package "+basePackageName+".services;");
+ s += line();
+
+ s += line("import com.google.gson.annotations.SerializedName;");
+ s += line("import java.util.concurrent.CompletableFuture;");
+ s += line("import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;");
+
+ s += line("import "+basePackageName+".data.*;");
+ // TODO add the other imports individually
+
+ //s += comment(schema.description);
+ s += comment({ description : 'Server interface for the model execution trace protocol.\nAuto-generated from json schema. Do not edit manually.'});
+
+ s += openBlock(`public interface ${moduleName}`);
+
+ // for (let typeName in schema.definitions) {
+ // console.log(typeName);
+ // }
+
+ //console.table(schema.definitions);
+
+ for (let typeName in schema.definitions) {
+
+ const d2 = schema.definitions[typeName];
+
+ let supertype: string = null;
+ if ((d2).allOf) {
+ const array = (d2).allOf;
+ for (let d of array) {
+ if ((d).$ref) {
+ supertype = getRef((d).$ref);
+ } else {
+ if(supertype == 'Request') {
+ let requestName = typeName.replace(/(.*)Request$/, "$1"); // TODO find the real name of the command in the "command"" property rather than infer from typeName
+ let responseDefTypeName = typeName.replace(/(.*)Request$/, "$1Response"); // TODO do we have a better way to find the response from the request in the json schema ?
+ let responseDefType = schema.definitions[responseDefTypeName];
+ var responseDef : P.Definition
+ for (let respDef of (responseDefType).allOf) {
+ if ((respDef).$ref) {
+ } else {
+ responseDef = respDef;
+ }
+ }
+ const response : ResponseHelper = new ResponseHelper(responseDefTypeName, responseDef);
+
+ s += RequestInterface(requestName, d, response);
+ }
+ }
+ }
+ }
+ }
+
+ s += closeBlock();
+ s += line();
+
+ return s;
+}
+
+//function RequestInterface(interfaceName: string, definition: P.Definition, responsedefinition: P.Definition): string {
+function RequestInterface(interfaceName: string, definition: P.Definition, responseHelper: ResponseHelper): string {
+ let desc = definition.description;
+ let methodName = "";
+ if (definition.properties && definition.properties.event && definition.properties.event['enum']) {
+ const eventName = `${definition.properties.event['enum'][0]}`;
+ methodName = eventName;
+ if (eventName) {
+ desc = `Event message for '${eventName}' event type.\n${desc}`;
+ }
+ } else if (definition.properties && definition.properties.command && definition.properties.command['enum']) {
+ const requestName = `${definition.properties.command['enum'][0]}`;
+ methodName = requestName;
+ if (requestName) {
+ const RequestName = requestName[0].toUpperCase() + requestName.substr(1);
+ desc = `${RequestName} request; value of command field is '${requestName}'.\n${desc}`;
+ }
+ }
+ let s = line();
+ s += comment({ description : desc });
+
+ s += line("@JsonRequest");
+ // find response type
+ var returnType = 'CompletableFuture';
+
+ if(responseHelper.responseDef && responseHelper.responseDef.properties && responseHelper.responseDef.properties.body){
+ /*console.log(interfaceName+': responsedefinition = '+responsedefinition.properties.body);
+ console.log(responsedefinition.properties.body);
+ console.table(responsedefinition.properties.body);
+ console.table(argumentType(responsedefinition.properties.body));*/
+
+ if(responseHelper.responseDef.properties.body.type === 'object'){
+ returnType = "CompletableFuture<"+responseHelper.responseDefTypeName+">";
+ } else {
+ returnType = "CompletableFuture<"+propertyType(responseHelper.responseDef.properties.body)+">";
+ }
+ }
+ var argsString : string[] = [];
+ for (let propName in definition.properties) {
+
+ const type = propertyType(definition.properties[propName]);
+ const propertyDef = `${type} ${propName}`;
+ if (type[0] === '\'' && type[type.length-1] === '\'' && type.indexOf('|') < 0) {
+ //s += line(`// ${propertyDef};`);
+ } else {
+ argsString.push(`${propertyDef}`);
+ }
+ }
+ if(isJavaKeyWord(methodName)){
+ s += line(`@SerializedName("${methodName}")`);
+ }
+ s += openBlock(`default ${returnType} ${getJavaSafeName(methodName)}(`+argsString.join(', ')+`)`);
+ s += line(`throw new UnsupportedOperationException();`)
+ s += closeBlock();
+ return s;
+}
+
+
+function Enum(typeName: string, definition: P.StringType): string {
+ let s = line();
+ s += comment(definition);
+ const x = enumAsOrType(definition.enum);
+ s += line(`export type ${typeName} = ${x};`);
+ return s;
+}
+
+function enumAsOrType(enm: string[]) {
+ return enm.map(v => `'${v}'`).join(' | ');
+}
+
+function comment(c: P.Commentable): string {
+
+ let description = c.description || '';
+
+ if ((c).items) { // array
+ c = (c).items;
+ }
+
+ // a 'closed' enum with individual descriptions
+ if (c.enum && c.enumDescriptions) {
+ for (let i = 0; i < c.enum.length; i++) {
+ description += `\n'${c.enum[i]}': ${c.enumDescriptions[i]}`;
+ }
+ }
+
+ // an 'open' enum
+ if (c._enum) {
+ description += '\nValues: ';
+ if (c.enumDescriptions) {
+ for (let i = 0; i < c._enum.length; i++) {
+ description += `\n'${c._enum[i]}': ${c.enumDescriptions[i]}`;
+ }
+ description += '\netc.';
+ } else {
+ description += `${c._enum.map(v => `'${v}'`).join(', ')}, etc.`;
+ }
+ }
+
+ if (description) {
+ description = description.replace(/(.*)<\/code>/g, "'$1'");
+ numIndents++;
+ description = description.replace(/\n/g, '\n' + indent());
+ numIndents--;
+ if (description.indexOf('\n') >= 0) {
+ return line(`/** ${description}\n${indent()}*/`);
+ } else {
+ return line(`/** ${description} */`);
+ }
+ }
+ return '';
+}
+
+function openBlock(str: string, openChar?: string, indent?: boolean): string {
+ indent = typeof indent === 'boolean' ? indent : true;
+ openChar = openChar || ' {';
+ let s = line(`${str}${openChar}`, true, indent);
+ numIndents++;
+ return s;
+}
+
+function closeBlock(closeChar?: string, newline?: boolean): string {
+ newline = typeof newline === 'boolean' ? newline : true;
+ closeChar = closeChar || '}';
+ numIndents--;
+ return line(closeChar, newline);
+}
+
+function propertyType(prop: any): string {
+ if (prop.$ref) {
+ return getRef(prop.$ref);
+ }
+ switch (prop.type) {
+ case 'array':
+ const s = propertyType(prop.items);
+ if (s.indexOf(' ') >= 0) {
+ return `(${s})[]`;
+ }
+ return `${s}[]`;
+ case 'object':
+ return objectType(prop);
+ case 'string':
+ if (prop.enum) {
+ return enumAsOrType(prop.enum);
+ }
+ return `String`;
+ case 'integer':
+ return 'Integer';
+ }
+ if (Array.isArray(prop.type)) {
+ if (prop.type.length === 7 && prop.type.sort().join() === 'array,boolean,integer,null,number,object,string') { // silly way to detect all possible json schema types
+ return 'Object';
+ } else {
+ return prop.type.map(v => v === 'integer' ? 'Integer' : v).join(' | ');
+ }
+ }
+ return prop.type;
+}
+
+function objectType(prop: any): string {
+ if (prop.properties) {
+ let s = openBlock('', '{', false);
+
+ for (let propName in prop.properties) {
+ const required = prop.required ? prop.required.indexOf(propName) >= 0 : false;
+ s += property(propName, !required, prop.properties[propName]);
+ }
+
+ s += closeBlock('}', false);
+ return s;
+ }
+ if (prop.additionalProperties) {
+ return `{ [key: string]: ${orType(prop.additionalProperties.type)}; }`;
+ }
+ return '{}';
+}
+
+function orType(enm: string | string[]): string {
+ if (typeof enm === 'string') {
+ return enm;
+ }
+ return enm.join(' | ');
+}
+
+function requestArg(name: string, prop: P.PropertyType): string {
+ let s = '';
+ s += comment(prop);
+ const type = propertyType(prop);
+ const propertyDef = `${type} ${name}`;
+ if (type[0] === '\'' && type[type.length-1] === '\'' && type.indexOf('|') < 0) {
+ s += line(`// ${propertyDef};`);
+ } else {
+ s += line(`${propertyDef};`);
+ }
+ return s;
+}
+
+function property(name: string, optional: boolean, prop: P.PropertyType): string {
+ let s = '';
+ s += comment(prop);
+ const type = propertyType(prop);
+ const propertyDef = `${name}${optional ? '?' : ''}: ${type}`;
+ if (type[0] === '\'' && type[type.length-1] === '\'' && type.indexOf('|') < 0) {
+ s += line(`// ${propertyDef};`);
+ } else {
+ s += line(`${propertyDef};`);
+ }
+ return s;
+}
+
+function getRef(ref: string): string {
+ const REXP = /#\/(.+)\/(.+)/;
+ const matches = REXP.exec(ref);
+ if (matches && matches.length === 3) {
+ return matches[2];
+ }
+ console.log('error: ref');
+ return ref;
+}
+
+function indent(): string {
+ return '\t'.repeat(numIndents);
+}
+
+function line(str?: string, newline?: boolean, indnt?: boolean): string {
+ newline = typeof newline === 'boolean' ? newline : true;
+ indnt = typeof indnt === 'boolean' ? indnt : true;
+ let s = '';
+ if (str) {
+ if (indnt) {
+ s += indent();
+ }
+ s += str;
+ }
+ if (newline) {
+ s += '\n';
+ }
+ return s;
+}
+
+
+/// Main
+
+/*
+const debugProtocolSchema = JSON.parse(fs.readFileSync('./ModelExecutionTraceProtocol.json').toString());
+
+const emitStr = TSGeneratorModule('ModelExecutionTraceProtocol', debugProtocolSchema);
+
+fs.writeFileSync(`../metp_protocol/src/ModelExecutionTraceProtocol.ts`, emitStr, { encoding: 'utf-8'});
+*/
diff --git a/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/TsAPIGenerator.ts b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/TsAPIGenerator.ts
new file mode 100644
index 000000000..8ac6f298b
--- /dev/null
+++ b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/TsAPIGenerator.ts
@@ -0,0 +1,282 @@
+/*---------------------------------------------------------
+ * Copyright (c) 2020 Inria and others.. All rights reserved.
+ *--------------------------------------------------------*/
+
+
+// inspired from https://github.com/microsoft/vscode-debugadapter-node/blob/main/src/generator.ts
+
+'use strict';
+
+//import * as fs from 'fs';
+import {IProtocol, Protocol as P} from './json_schema';
+
+let numIndents = 0;
+
+export function TSGeneratorModule(moduleName: string, schema: IProtocol): string {
+
+ let s = '';
+ s += line("/*---------------------------------------------------------------------------------------------");
+ s += line(" * Copyright (c) 2020 Inria and others.");
+ s += line(" * All rights reserved. This program and the accompanying materials");
+ s += line(" * are made available under the terms of the Eclipse Public License v1.0");
+ s += line(" * which accompanies this distribution, and is available at");
+ s += line(" * http://www.eclipse.org/legal/epl-v10.html");
+ s += line(" *--------------------------------------------------------------------------------------------*/");
+ s += line("/* GENERATED FILE, DO NOT MODIFY MANUALLY */");
+ s += line();
+ s += line("'use strict';");
+ s += line();
+
+ //s += comment(schema.description);
+ s += comment({ description : 'Declaration module describing the model execution trace protocol.\nAuto-generated from json schema. Do not edit manually.'});
+
+ s += openBlock(`export module ${moduleName}`);
+
+ for (let typeName in schema.definitions) {
+
+ const d2 = schema.definitions[typeName];
+
+ let supertype: string = null;
+ if ((d2).allOf) {
+ const array = (d2).allOf;
+ for (let d of array) {
+ if ((d).$ref) {
+ supertype = getRef((d).$ref);
+ } else {
+ s += Interface(typeName, d, supertype);
+ }
+ }
+ } else {
+ if ((d2).enum) {
+ s += Enum(typeName, d2);
+ } else {
+ s += Interface(typeName, d2);
+ }
+ }
+ }
+
+ s += closeBlock();
+ s += line();
+
+ return s;
+}
+
+function Interface(interfaceName: string, definition: P.Definition, superType?: string): string {
+
+ let desc = definition.description;
+
+ if (definition.properties && definition.properties.event && definition.properties.event['enum']) {
+ const eventName = `${definition.properties.event['enum'][0]}`;
+ if (eventName) {
+ desc = `Event message for '${eventName}' event type.\n${desc}`;
+ }
+ } else if (definition.properties && definition.properties.command && definition.properties.command['enum']) {
+ const requestName = `${definition.properties.command['enum'][0]}`;
+ if (requestName) {
+ const RequestName = requestName[0].toUpperCase() + requestName.substr(1);
+ desc = `${RequestName} request; value of command field is '${requestName}'.\n${desc}`;
+ }
+ }
+
+ let s = line();
+
+ s += comment({ description : desc });
+
+ let x = `export interface ${interfaceName}`;
+ if (superType) {
+ x += ` extends ${superType}`;
+ }
+ s += openBlock(x);
+
+ for (let propName in definition.properties) {
+ const required = definition.required ? definition.required.indexOf(propName) >= 0 : false;
+ s += property(propName, !required, definition.properties[propName]);
+ }
+
+ s += closeBlock();
+
+ return s;
+}
+
+function Enum(typeName: string, definition: P.StringType): string {
+ let s = line();
+ s += comment(definition);
+ const x = enumAsOrType(definition.enum);
+ s += line(`export type ${typeName} = ${x};`);
+ return s;
+}
+
+function enumAsOrType(enm: string[]) {
+ return enm.map(v => `'${v}'`).join(' | ');
+}
+
+function comment(c: P.Commentable): string {
+
+ let description = c.description || '';
+
+ if ((c).items) { // array
+ c = (c).items;
+ }
+
+ // a 'closed' enum with individual descriptions
+ if (c.enum && c.enumDescriptions) {
+ for (let i = 0; i < c.enum.length; i++) {
+ description += `\n'${c.enum[i]}': ${c.enumDescriptions[i]}`;
+ }
+ }
+
+ // an 'open' enum
+ if (c._enum) {
+ description += '\nValues: ';
+ if (c.enumDescriptions) {
+ for (let i = 0; i < c._enum.length; i++) {
+ description += `\n'${c._enum[i]}': ${c.enumDescriptions[i]}`;
+ }
+ description += '\netc.';
+ } else {
+ description += `${c._enum.map(v => `'${v}'`).join(', ')}, etc.`;
+ }
+ }
+
+ if (description) {
+ description = description.replace(/(.*)<\/code>/g, "'$1'");
+ numIndents++;
+ description = description.replace(/\n/g, '\n' + indent());
+ numIndents--;
+ if (description.indexOf('\n') >= 0) {
+ return line(`/** ${description}\n${indent()}*/`);
+ } else {
+ return line(`/** ${description} */`);
+ }
+ }
+ return '';
+}
+
+function openBlock(str: string, openChar?: string, indent?: boolean): string {
+ indent = typeof indent === 'boolean' ? indent : true;
+ openChar = openChar || ' {';
+ let s = line(`${str}${openChar}`, true, indent);
+ numIndents++;
+ return s;
+}
+
+function closeBlock(closeChar?: string, newline?: boolean): string {
+ newline = typeof newline === 'boolean' ? newline : true;
+ closeChar = closeChar || '}';
+ numIndents--;
+ return line(closeChar, newline);
+}
+
+function propertyType(prop: any): string {
+ if (prop.$ref) {
+ return getRef(prop.$ref);
+ }
+ if (Array.isArray(prop.oneOf)) {
+ return (prop.oneOf as any[]).map(t => propertyType(t)).join(' | ')
+ }
+ switch (prop.type) {
+ case 'array':
+ const s = propertyType(prop.items);
+ if (s.indexOf(' ') >= 0) {
+ return `(${s})[]`;
+ }
+ return `${s}[]`;
+ case 'object':
+ return objectType(prop);
+ case 'string':
+ if (prop.enum) {
+ return enumAsOrType(prop.enum);
+ }
+ return `string`;
+ case 'integer':
+ return 'number';
+ }
+ if (Array.isArray(prop.type)) {
+ if (prop.type.length === 7 && prop.type.sort().join() === 'array,boolean,integer,null,number,object,string') { // silly way to detect all possible json schema types
+ return 'any';
+ } else {
+ return prop.type.map(v => v === 'integer' ? 'number' : v).join(' | ');
+ }
+ }
+ return prop.type;
+}
+
+function objectType(prop: any): string {
+ if (prop.properties) {
+ let s = openBlock('', '{', false);
+
+ for (let propName in prop.properties) {
+ const required = prop.required ? prop.required.indexOf(propName) >= 0 : false;
+ s += property(propName, !required, prop.properties[propName]);
+ }
+
+ s += closeBlock('}', false);
+ return s;
+ }
+ if (prop.additionalProperties) {
+ return `{ [key: string]: ${orType(prop.additionalProperties.type)}; }`;
+ }
+ return '{}';
+}
+
+function orType(enm: string | string[]): string {
+ if (typeof enm === 'string') {
+ return enm;
+ }
+ return enm.join(' | ');
+}
+
+function property(name: string, optional: boolean, prop: P.PropertyType): string {
+ let s = '';
+ s += comment(prop);
+ const type = propertyType(prop);
+ //console.log('property() name='+name+' prop='+prop+' type='+type);
+ const propertyDef = `${name}${optional ? '?' : ''}: ${type}`;
+ if (type[0] === '\'' && type[type.length-1] === '\'' && type.indexOf('|') < 0) {
+ s += line(`// ${propertyDef};`);
+ } else {
+ s += line(`${propertyDef};`);
+ }
+ return s;
+}
+
+function getRef(ref: string): string {
+ const REXP = /#\/(.+)\/(.+)/;
+ const matches = REXP.exec(ref);
+ if (matches && matches.length === 3) {
+ return matches[2];
+ }
+ console.log('error: ref');
+ return ref;
+}
+
+function indent(): string {
+ return '\t'.repeat(numIndents);
+}
+
+function line(str?: string, newline?: boolean, indnt?: boolean): string {
+ newline = typeof newline === 'boolean' ? newline : true;
+ indnt = typeof indnt === 'boolean' ? indnt : true;
+ let s = '';
+ if (str) {
+ if (indnt) {
+ s += indent();
+ }
+ s += str;
+ }
+ if (newline) {
+ s += '\n';
+ }
+ return s;
+}
+
+
+/// Main
+
+/*
+const debugProtocolSchema = JSON.parse(fs.readFileSync('./ModelExecutionTraceProtocol.json').toString());
+
+const emitStr = TSGeneratorModule('ModelExecutionTraceProtocol', debugProtocolSchema);
+
+fs.writeFileSync(`../metp_protocol/src/ModelExecutionTraceProtocol.ts`, emitStr, { encoding: 'utf-8'});
+*/
diff --git a/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/XtendArgumentsGenerator.ts b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/XtendArgumentsGenerator.ts
new file mode 100644
index 000000000..84843e860
--- /dev/null
+++ b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/XtendArgumentsGenerator.ts
@@ -0,0 +1,396 @@
+
+/*---------------------------------------------------------
+ * Copyright (c) 2020 Inria and others.. All rights reserved.
+ *--------------------------------------------------------*/
+
+'use strict';
+
+import {IProtocol, Protocol as P } from './json_schema';
+import {PropertyHelper, getJavaSafeName, capitalizeFirstLetter, isJavaKeyWord} from './json_schemaHelpers'
+
+let numIndents = 0;
+
+export function XtendArgumentsGeneratorModule(moduleName: string, basePackageName: string, schema: IProtocol): string {
+
+ let s = '';
+ s += line("/*---------------------------------------------------------------------------------------------");
+ s += line(" * Copyright (c) 2020 Inria and others.");
+ s += line(" * All rights reserved. This program and the accompanying materials");
+ s += line(" * are made available under the terms of the Eclipse Public License v1.0");
+ s += line(" * which accompanies this distribution, and is available at");
+ s += line(" * http://www.eclipse.org/legal/epl-v10.html");
+ s += line(" *--------------------------------------------------------------------------------------------*/");
+ s += line("/* GENERATED FILE, DO NOT MODIFY MANUALLY */");
+ s += line();
+ s += line("package "+basePackageName+".data;");
+ s += line();
+
+ s += line("import com.google.gson.annotations.SerializedName");
+ s += line("import java.util.Map");
+ s += line("import org.eclipse.lsp4j.generator.JsonRpcData");
+ s += line("import org.eclipse.lsp4j.jsonrpc.messages.Either");
+ s += line("import org.eclipse.lsp4j.jsonrpc.validation.NonNull");
+ s += line();
+ s += comment({ description : `Declaration of data classes and enum for the ${moduleName}.\nAuto-generated from json schema. Do not edit manually.`});
+
+ for (let typeName in schema.definitions) {
+
+ const d2 = schema.definitions[typeName];
+
+ let supertype: string = null;
+ if ((d2).allOf) {
+ // all definition with inheritances
+ const array = (d2).allOf;
+ for (let d of array) {
+ if ((d).$ref) {
+ supertype = getRef((d).$ref);
+ } else {
+
+ // ignore generic Json RPC data
+ // Request, Event, Response, ErrorResponse
+
+ if( typeName != 'Request' && typeName != 'Event' && typeName != 'Response' && typeName != 'ErrorResponse') {
+ if(supertype == 'Response') {
+ // let's create the data'
+ //console.log(`create interface for Response ${typeName}`);
+ if(( d).properties && ( d).properties['body']){
+ s += MessageInterface(typeName, ( d).properties['body']);
+ } else {
+ s += MessageInterface(typeName, d);
+ }
+ } else if(supertype == 'Event') {
+ // let's create the data'
+ //console.log(`create interface for Event ${typeName}`);
+ //console.log(( d).properties['body']);
+ if(( d).properties['body']){
+ s += MessageInterface(typeName+"Arguments", ( d).properties['body']);
+ }
+ //s += MessageInterface(typeName, d);
+ } else if(supertype !== 'Request'){
+ s += MessageInterface(typeName, d);
+ } else {
+ console.log("Ignore1 "+typeName+ " with supertype="+supertype);
+ }
+ } else {
+ console.log("Ignore2 "+typeName);
+ }
+ }
+ }
+ } else {
+ // arguments
+ //console.log('not allOf ' + typeName);
+ //console.log(d2);
+ //s += MessageInterface(typeName, d);
+ if ((d2).enum) {
+ s += Enum(typeName, d2);
+ } else {
+ if(typeName != 'ProtocolMessage') {
+ s += MessageInterface(typeName, d2);
+ } else {
+ console.log("Ignore3 "+typeName);
+ }
+ }
+ }
+ }
+
+// s += closeBlock();
+ s += line();
+
+ return s;
+}
+
+function MessageInterface(interfaceName: string, definition: P.Definition): string {
+
+ let desc = definition.description;
+ if (definition.properties && definition.properties.event && definition.properties.event['enum']) {
+ const eventName = `${definition.properties.event['enum'][0]}`;
+ if (eventName) {
+ desc = `Event message for '${eventName}' event type.\n${desc}`;
+ }
+ } else if (definition.properties && definition.properties.command && definition.properties.command['enum']) {
+ const requestName = `${definition.properties.command['enum'][0]}`;
+ if (requestName) {
+ const RequestName = requestName[0].toUpperCase() + requestName.substr(1);
+ desc = `${RequestName} request; value of command field is '${requestName}'.\n${desc}`;
+ }
+ }
+ let s = line();
+ s += comment({ description : desc });
+
+ s += line("@JsonRpcData");
+
+ s += openBlock(`class ${interfaceName} `);
+/* s += line(`throw new UnsupportedOperationException();`)*/
+ for (let propName in definition.properties) {
+ const required = definition.required ? definition.required.indexOf(propName) >= 0 : false;
+ const propertyHelper: PropertyHelper = new PropertyHelper(interfaceName, propName, definition.properties[propName]);
+ s += property(!required, propertyHelper);
+ }
+ s += closeBlock();
+
+ // TODO property Enum Special classes
+
+ for (let propName in definition.properties) {
+ const required = definition.required ? definition.required.indexOf(propName) >= 0 : false;
+ const propertyHelper: PropertyHelper = new PropertyHelper(interfaceName, propName, definition.properties[propName]);
+ s += propertySpecificTypeDef(propertyHelper);
+ }
+ return s;
+}
+
+
+
+function Enum(typeName: string, definition: P.StringType): string {
+ let s = line();
+ let commentableProp = definition;
+ s += comment(definition);
+ //const x = enumAsOrType(definition.enum);
+ s += line(`enum ${typeName} {`);
+ numIndents++;
+ s += line(definition.enum.map((v, index) => {
+ let comment = '';
+ if (commentableProp.enumDescriptions && commentableProp.enumDescriptions.length > index) {
+ comment = formatDescription(commentableProp.enumDescriptions[index]);
+ }
+ return `${comment}${indent()}@SerializedName("${v}")\n${indent()}${EnumLiteralStringToJavaEnumLiteralString(v)}`;
+ }).join(`,\n`));
+
+
+ numIndents--;
+ s += line('}')
+
+
+ return s;
+}
+
+// function enumAsOrType(enm: string[]) {
+// return enm.map(v => `'${v}'`).join(' | ');
+// }
+
+// function enumAsDedicatedType(hostDefinitionName: string, propertyName: string) {
+// return hostDefinitionName+capitalizeFirstLetter(propertyName);
+// }
+
+function comment(c: P.Commentable): string {
+
+ let description = c.description || '';
+
+ if ((c).items) { // array
+ c = (c).items;
+ }
+
+ // a 'closed' enum with individual descriptions
+ if (c.enum && c.enumDescriptions) {
+ for (let i = 0; i < c.enum.length; i++) {
+ description += `\n'${c.enum[i]}': ${c.enumDescriptions[i]}`;
+ }
+ }
+
+ // an 'open' enum
+ if (c._enum) {
+ description += '\nValues: ';
+ if (c.enumDescriptions) {
+ for (let i = 0; i < c._enum.length; i++) {
+ description += `\n'${c._enum[i]}': ${c.enumDescriptions[i]}`;
+ }
+ description += '\netc.';
+ } else {
+ description += `${c._enum.map(v => `'${v}'`).join(', ')}, etc.`;
+ }
+ }
+
+ if (description) {
+ description = description.replace(/(.*)<\/code>/g, "'$1'");
+ numIndents++;
+ description = description.replace(/\n/g, '\n' + indent());
+ numIndents--;
+ if (description.indexOf('\n') >= 0) {
+ return line(`/** ${description}\n${indent()}*/`);
+ } else {
+ return line(`/** ${description} */`);
+ }
+ }
+ return '';
+}
+
+function formatDescription(description: string) {
+ description = description.replace(/(.*)<\/code>/g, "'$1'");
+ numIndents++;
+ description = description.replace(/\n/g, '\n' + indent());
+ numIndents--;
+ if (description.indexOf('\n') >= 0) {
+ return line(`/** ${description}\n${indent()} */`);
+ } else {
+ return line(`/** ${description} */`);
+ }
+}
+
+function openBlock(str: string, openChar?: string, indent?: boolean): string {
+ indent = typeof indent === 'boolean' ? indent : true;
+ openChar = openChar || ' {';
+ let s = line(`${str}${openChar}`, true, indent);
+ numIndents++;
+ return s;
+}
+
+function closeBlock(closeChar?: string, newline?: boolean): string {
+ newline = typeof newline === 'boolean' ? newline : true;
+ closeChar = closeChar || '}';
+ numIndents--;
+ return line(closeChar, newline);
+}
+
+function property(optional: boolean, propertyHelper: PropertyHelper) {
+ let s = '';
+ s += comment(propertyHelper.propertyType as P.PropertyType);
+ const typeName = propertyHelper.propertyTypeOrRefJavaName()
+ //const type = propertyTypeOrRef(hostDefinitionName, propertyName, prop);
+
+ const propertyDef = `${typeName} ${getJavaSafeName(propertyHelper.propertyName)}`;
+ //console.table(prop)
+ if (typeName[0] === '\'' && typeName[typeName.length-1] === '\'' && typeName.indexOf('|') < 0) {
+ s += line(`// ${propertyDef};`);
+ } else {
+ if(!optional) {
+ s += line('// @NonNull');
+ }
+ if(isJavaKeyWord(propertyHelper.propertyName)){
+ s += line(`@SerializedName("${propertyHelper.propertyName}")`);
+ }
+ s += line(`${propertyDef};`);
+ }
+ return s;
+}
+
+// Class or Enum definition coming from property that declares a new Type (enum or class with static Strings)
+function propertySpecificTypeDef(propertyHelper: PropertyHelper): string {
+
+ let s = '';
+ const prop = propertyHelper.propertyType as P.PropertyType
+ if (Array.isArray(prop.type)) {
+ if (prop.type.length === 7 && prop.type.sort().join() === 'array,boolean,integer,null,number,object,string') { // silly way to detect all possible json schema types
+ s+= '// Object';
+ } else {
+ s+= '// '+ prop.type.map(v => v === 'integer' ? 'Integer' : v).join(' | ');
+ }
+ } else {
+ switch (prop.type) {
+ case 'array':
+ // const s2 = propertyTypeOrRef(hostDefinitionName, propertyName, prop.items);
+ // if (s2.indexOf(' ') >= 0) {
+ // s+= '// '+ `(${s2})[]`;
+ // } else {
+ // s += '// '+ `${s2}[]`;
+ // }
+ s += `// TODO deal with array propertytype for ${propertyHelper.propertyName}`;
+ break;
+ case 'object':
+ //s += '// '+ objectType(hostDefinitionName, prop);
+
+ s += `// TODO deal with object propertytype for ${propertyHelper.propertyName}`;
+ break;
+ case 'string':
+ let commentableProp = prop;
+ let description = prop.description || '';
+ if (commentableProp.enum) {
+ // a 'closed' enum
+ s += line(`/** ${description}\n${indent()}*/`);
+ s += line('enum ' + propertyHelper.getDedicatedTypeJavaName() + ' {')
+ s += line();
+ numIndents++;
+ s += line(prop.enum.map((v, index) => {
+ let comment = '';
+ if (commentableProp.enumDescriptions && commentableProp.enumDescriptions.length > index) {
+ // ${commentableProp.enumDescriptions[i]}`
+ comment = formatDescription(commentableProp.enumDescriptions[index]);
+ // comment = "/** "+commentableProp.enumDescriptions[index]+' */\n'+indent();
+ // comment = comment.replace(/\n/g, '\n' + indent());
+ }
+ return `${comment}${indent()}@SerializedName("${v}")\n${indent()}${EnumLiteralStringToJavaEnumLiteralString(v)}`;
+ }).join(`,\n`));
+ numIndents--;
+ s += line('}')
+ } else if (commentableProp._enum) {
+ // an 'open' enum
+ s += line(`/** ${description}\n${indent()}*/`);
+ s += line('interface ' + propertyHelper.getDedicatedTypeJavaName() + ' {');
+ numIndents++;
+ s += line(prop._enum.map(v => `public static final String ${EnumLiteralStringToJavaEnumLiteralString(v)} = "${v}";`)
+ .join(`\n${indent()}`));
+ numIndents--;
+ s += line('}')
+ } else {
+ s += '// '+ `String`;
+ }
+ break;
+ case 'integer':
+ s += '// '+ 'Integer';
+ break;
+ default:
+ s += '// '+ prop.type;
+ }
+ }
+ s += line();
+
+ return s;
+}
+
+
+/**
+ *
+ * @param string Convert the string of the enum literal into Java Enume literal
+ * Ie. remplace whitespace by '_'
+ * uppercase
+ * replace camel case by snake case
+ * @returns
+ */
+function EnumLiteralStringToJavaEnumLiteralString(s: string) {
+ s = s.replace(" ", "_");
+ s = s.split(/(?=[A-Z])/).join('_');
+ return s.toUpperCase() ;
+}
+
+// function capitalizeFirstLetter(s: string) {
+// return s.charAt(0).toUpperCase() + s.slice(1);
+// }
+
+function getRef(ref: string): string {
+ const REXP = /#\/(.+)\/(.+)/;
+ const matches = REXP.exec(ref);
+ if (matches && matches.length === 3) {
+ return matches[2];
+ }
+ console.log('error: ref');
+ return ref;
+}
+
+function indent(): string {
+ return '\t'.repeat(numIndents);
+}
+
+function line(str?: string, newline?: boolean, indnt?: boolean): string {
+ newline = typeof newline === 'boolean' ? newline : true;
+ indnt = typeof indnt === 'boolean' ? indnt : true;
+ let s = '';
+ if (str) {
+ if (indnt) {
+ s += indent();
+ }
+ s += str;
+ }
+ if (newline) {
+ s += '\n';
+ }
+ return s;
+}
+
+
+/// Main
+
+/*
+const debugProtocolSchema = JSON.parse(fs.readFileSync('./ModelExecutionTraceProtocol.json').toString());
+
+const emitStr = TSGeneratorModule('ModelExecutionTraceProtocol', debugProtocolSchema);
+
+fs.writeFileSync(`../metp_protocol/src/ModelExecutionTraceProtocol.ts`, emitStr, { encoding: 'utf-8'});
+*/
diff --git a/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/generatorConfig.d.ts b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/generatorConfig.d.ts
new file mode 100644
index 000000000..710dd3782
--- /dev/null
+++ b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/generatorConfig.d.ts
@@ -0,0 +1,26 @@
+
+
+// data structure to configure the generator inputs and outputs for several
+export interface ProtocolGenConfigs {
+ protocolGenConfigs: ProtocolGenConfig[];
+}
+
+export interface ProtocolGenConfig {
+ protocolName: string;
+ protocolShortName: string;
+ protocolJSONSchemaPath: string;
+ tsAPI: FileGen;
+ javaServer: PackageGen;
+ javaClient: PackageGen;
+ javaAPI: PackageGen;
+ plantuml: FileGen;
+}
+
+export interface PackageGen {
+ packageName: string;
+ destFileName: string;
+}
+
+export interface FileGen {
+ destFileName: string;
+}
\ No newline at end of file
diff --git a/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/json_schema.d.ts b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/json_schema.d.ts
new file mode 100644
index 000000000..b2f6de195
--- /dev/null
+++ b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/json_schema.d.ts
@@ -0,0 +1,80 @@
+/**
+ * TypeScript definitions for a subset of the json schema.
+ */
+
+export interface IProtocol {
+
+ $schema: string,
+ title: string,
+ description: string,
+ type: "object",
+
+ definitions: { [key: string]: Protocol.Definition2 }
+}
+
+export module Protocol {
+
+ export interface Definition extends ObjectType {
+ }
+
+ export interface AllOf {
+ allOf: (Definition | RefType ) []
+ }
+
+ export type Definition2 = Definition | AllOf | StringType;
+ type PropertyType = PrimitiveType | StringType | ObjectType | ArrayType;
+
+ export interface PrimitiveType extends BaseType {
+ type: "number" | "integer" | "boolean"
+ }
+
+ export interface Commentable {
+ /** Description of the type */
+ description?: string
+ /** Possible values of a string. */
+ enum?: string[]
+ /** Possible descriptions for the values of a string. */
+ enumDescriptions?: string[]
+ /** Possible values of a string. */
+ _enum?: string[]
+ }
+
+ export interface StringType extends BaseType, Commentable {
+ type: "string"
+ /** Possible values of a string. */
+ enum?: string[]
+ /** Possible descriptions for the values of a string. */
+ enumDescriptions?: string[]
+ /** Possible values of a string. */
+ _enum?: string[]
+ }
+
+ export interface ObjectType extends BaseType {
+ type: "object"
+ /** Properties of the type. Maps to a typed object */
+ properties?: { [key: string]: PropertyType; }
+ /** Names of required properties */
+ required?: string[],
+ /** Are additional properties allowed? */
+ additionalProperties?: boolean
+ }
+
+ export interface ArrayType extends BaseType {
+ type: "array"
+ /** Maps to a typed array e.g string[] */
+ items: RefType | PrimitiveType | StringType | ObjectType
+ /** Cardinality of length of array type */
+ //minItems?: number
+ //maxItems?: number
+ }
+
+ export interface RefType {
+ /** Reference to a domain defined type */
+ $ref: string
+ }
+
+ export interface BaseType {
+ /** Description of the type */
+ description?: string
+ }
+}
\ No newline at end of file
diff --git a/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/json_schemaHelpers.ts b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/json_schemaHelpers.ts
new file mode 100644
index 000000000..f8cefdcf6
--- /dev/null
+++ b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/json_schemaHelpers.ts
@@ -0,0 +1,174 @@
+/*---------------------------------------------------------
+ * Copyright (c) 2020 Inria and others.. All rights reserved.
+ *--------------------------------------------------------*/
+
+'use strict';
+
+import {IProtocol, Protocol as P } from './json_schema';
+
+/**
+ * Helper class to compute information from a property definition in json_schema
+ */
+export class PropertyHelper {
+ containerDefinitionName: string;
+ propertyName: string;
+ propertyType : (P.PropertyType | P.RefType );
+ constructor(containerDefinitionName: string, propertyName: string, propertyType : (P.PropertyType | P.RefType )) {
+ this.containerDefinitionName = containerDefinitionName;
+ this.propertyName = propertyName
+ this.propertyType = propertyType
+ }
+
+ propertyTypeOrRefJavaName(): string {
+ return this.internalPropertyTypeOrRefJavaName(this.propertyType);
+ }
+
+ protected internalPropertyTypeOrRefJavaName(prop: (P.PropertyType | P.RefType )): string {
+ if (((prop as P.RefType)?.$ref)) {
+ return getRef((prop as P.RefType)?.$ref);
+ } else {
+ return this.internalPropertyTypeJavaName( prop as P.PropertyType);
+ }
+ }
+
+ protected internalPropertyTypeJavaName(prop: P.PropertyType ): string {
+ switch (prop.type) {
+ case 'array':
+ const s = this.internalPropertyTypeOrRefJavaName( prop.items);
+ //console.table(prop.items);
+ //console.table(this);
+ if (s.indexOf(' ') >= 0) {
+ return `(${s})[]`;
+ }
+ return `${s}[]`;
+ case 'object':
+
+ return this.objectTypeJavaName(prop);
+ case 'string':
+ if (prop.enum) {
+ return this.getDedicatedTypeJavaName();
+ }
+ return `String`;
+ case 'integer':
+ return 'Integer';
+ case 'number':
+ return 'Float';
+ }
+ if (Array.isArray(prop.type)) {
+
+ if(prop.type.length === 2 && prop.type['null']) {
+ // this is actually a nullable
+ let a = prop.type.filter(e => e !== 'null')
+ switch (a[0]){
+ case 'string':
+ return 'String';
+ case 'integer':
+ return 'Integer';
+ case 'number':
+ return 'Float';
+ }
+ return 'Object';
+ } else {
+ return 'Object';
+ }
+
+ // if (prop.type.length === 7 && prop.type.sort().join() === 'array,boolean,integer,null,number,object,string') { // silly way to detect all possible json schema types
+ // return 'Object';
+ // } else {
+ // return prop.type.map(v => v === 'integer' ? 'Integer' : v).join(' | ');
+ // }
+ }
+ return prop.type;
+ }
+
+ getDedicatedTypeJavaName() {
+ return this.containerDefinitionName+capitalizeFirstLetter(this.propertyName);
+ }
+
+ objectTypeJavaName( prop: P.ObjectType): string {
+ // if (prop.properties) {
+ // let s = openBlock('', '{', false);
+
+ // for (let propName in prop.properties) {
+ // const required = prop.required ? prop.required.indexOf(propName) >= 0 : false;
+ // s += property(hostDefinitionName, propName, !required, prop.properties[propName]);
+ // }
+
+ // s += closeBlock('}', false);
+ // return s;
+ // }
+ // if (prop.additionalProperties) {
+ // return `Map`;
+ // //return `{ [key: string]: ${orType(prop.additionalProperties.type)}; }`;
+ // }
+ // return '{}';
+ if (prop.properties) {
+ if(prop.properties['body']) {
+ return this.internalPropertyTypeOrRefJavaName(prop.properties['body']);
+ } else {
+ return `\/* TODO objectTypeJavaName() properties ? ${prop} ${this.containerDefinitionName+capitalizeFirstLetter(this.propertyName)} *\/`
+ }
+
+ // for (let propName in prop.properties) {
+ // const required = prop.required ? prop.required.indexOf(propName) >= 0 : false;
+ // return property(hostDefinitionName, propName, !required, prop.properties[propName]);
+ // }
+ } else if (prop.additionalProperties) {
+ // TODO check version of json_schema.d.ts and use of it ...
+ return `Map`;
+ } else {
+ return `\/* TODO objectTypeJavaName() ? ${prop} ${this.containerDefinitionName+capitalizeFirstLetter(this.propertyName)} *\/`
+ }
+ }
+
+}
+
+export class ResponseHelper {
+ responseDefTypeName: string;
+ responseDef: P.Definition;
+ constructor(responseDefTypeName: string, responseDef: P.Definition){
+ this.responseDef=responseDef;
+ this.responseDefTypeName=responseDefTypeName;
+ }
+
+}
+
+function orType(enm: string | string[]): string {
+ if (typeof enm === 'string') {
+ return 'String';
+ }
+ return enm.join(' | ');
+}
+
+function getRef(ref: string): string {
+ const REXP = /#\/(.+)\/(.+)/;
+ const matches = REXP.exec(ref);
+ if (matches && matches.length === 3) {
+ return matches[2];
+ }
+ console.log('error: ref');
+ return ref;
+}
+
+// function toEnumLiteral(string: string) {
+// // TODO transform camelcase into uppercase and _
+// return string.toUpperCase() ;
+// }
+
+/**
+ * replace java keywords
+ * @param s
+ */
+export function isJavaKeyWord(s: string): boolean {
+ return s === 'continue' ||s === 'default' || s === 'goto' || s === 'switch' ;
+}
+
+export function getJavaSafeName(s: string): string {
+ if(isJavaKeyWord(s)){
+ return '_'+s;
+ } else return s;
+}
+
+export function capitalizeFirstLetter(s: string) {
+ return s.charAt(0).toUpperCase() + s.slice(1);
+}
\ No newline at end of file
diff --git a/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/plantumlGenerator.ts b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/plantumlGenerator.ts
new file mode 100644
index 000000000..982116b7c
--- /dev/null
+++ b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/src/modules/plantumlGenerator.ts
@@ -0,0 +1,579 @@
+/*---------------------------------------------------------
+ * Copyright (c) 2020 Inria and others.. All rights reserved.
+ *--------------------------------------------------------*/
+
+'use strict';
+
+//import * as fs from 'fs';
+import {IProtocol, Protocol as P} from './json_schema';
+import {PropertyHelper, ResponseHelper} from './json_schemaHelpers'
+
+let numIndents = 0;
+
+export function PlantumlGeneratorModule(protocolName: string, schema: IProtocol): string {
+
+ let s = '';
+ s += line("@startuml");
+ s += line("' GENERATED FILE, DO NOT MODIFY MANUALLY");
+ s+= line("left to right direction");
+ s += line();
+ s += line("package "+protocolName+"_API {");
+
+ let links = '';
+ let messagesClasses = '';
+ let messagesClassesList :string[] = [];
+ let dtoClasses = '';
+
+
+ s += line("package data {");
+ for (let typeName in schema.definitions) {
+
+ const d2 = schema.definitions[typeName];
+
+ let supertype: string = null;
+ if ((d2).allOf) {
+ // responses and messages
+ const array = (d2).allOf;
+ for (let d of array) {
+ if ((d).$ref) {
+ supertype = getRef((d).$ref);
+ } else {
+
+ // ignore generic Json RPC data
+ // Request, Event, Response, ErrorResponse
+
+ if( typeName != 'Request' && typeName != 'Event' && typeName != 'Response' && typeName != 'ErrorResponse') {
+ if(supertype == 'Response') {
+ // let's create the data'
+ //console.log(`create interface for Response ${typeName}`);
+
+ if(( d).properties && ( d).properties['body']){
+ messagesClasses += MessageInterface(typeName, ( d).properties['body'], true);
+ messagesClassesList.push(typeName);
+ links += MessageInterfaceLinks(typeName, ( d).properties['body']);
+ } else {
+ messagesClasses += MessageInterface(typeName, d, true);
+ messagesClassesList.push(typeName);
+ links += MessageInterfaceLinks(typeName, d2);
+ }
+ } else if(supertype == 'Event') {
+ // let's create the data'
+ //console.log(`create interface for Event ${typeName}`);
+ //console.log(( d).properties['body']);
+ if(( d).properties['body']){
+ messagesClasses += MessageInterface(typeName+"Arguments", ( d).properties['body'], true);
+ messagesClassesList.push(typeName+"Arguments");
+ links += MessageInterfaceLinks(typeName+"Arguments", ( d).properties['body']);
+ } else {
+ console.log("plantuml Ignore1 "+typeName);
+ }
+ //s += MessageInterface(typeName, d);
+ }
+ } else {
+ console.log("plantuml Ignore2 "+typeName);
+ }
+ }
+ }
+ } else {
+ // arguments
+ //console.log('not allOf ' + typeName);
+ //console.log(d2);
+ //s += MessageInterface(typeName, d);
+ if ((d2).enum) {
+ s += Enum(typeName, d2);
+ } else {
+ if(typeName != 'ProtocolMessage') {
+ //console.log(`create interface for DTO ${typeName}`);
+ if(typeName.toLowerCase().endsWith('dto')){
+ dtoClasses += MessageInterface(typeName, d2, true);
+ } else {
+ messagesClasses += MessageInterface(typeName, d2, true);
+ messagesClassesList.push(typeName);
+ }
+ links += MessageInterfaceLinks(typeName, d2);
+ }
+ }
+ }
+ }
+ s += line("package messageClasses {");
+ s += messagesClasses;
+ s += line("}");
+ s += line("package dtoClasses {");
+ s += dtoClasses;
+ s += line("}");
+ s += line("}");
+
+ s += links;
+
+ messagesClassesList.forEach(function (messagesClass) {
+ s += line(""+messagesClass+" -[hidden]- dtoClasses" );
+ });
+
+ s += line("package services {");
+
+ s += openBlock("class "+protocolName+'Server');
+ for (let typeName in schema.definitions) {
+
+ const d2 = schema.definitions[typeName];
+
+ let supertype: string = null;
+ if ((d2).allOf) {
+ const array = (d2).allOf;
+ for (let d of array) {
+ if ((d).$ref) {
+ supertype = getRef((d).$ref);
+ } else {
+ if(supertype == 'Request') {
+ let requestName = typeName.replace(/(.*)Request$/, "$1"); // TODO find the real name of the command in the "command"" property rather than infer from typeName
+ let responseDefTypeName = typeName.replace(/(.*)Request$/, "$1Response"); // TODO do we have a better way to find the response from the request in the json schema ?
+ let responseDefType = schema.definitions[responseDefTypeName];
+ var responseDef : P.Definition
+ for (let respDef of (responseDefType).allOf) {
+ if ((respDef).$ref) {
+ } else {
+ responseDef = respDef;
+ }
+ }
+ const response : ResponseHelper = new ResponseHelper(responseDefTypeName, responseDef);
+ s += RequestInterface(requestName, d, response);
+ }
+ }
+ }
+ }
+ }
+ s += closeBlock();
+
+ s += openBlock("class "+protocolName+'Client');
+ for (let typeName in schema.definitions) {
+
+ const d2 = schema.definitions[typeName];
+
+ let supertype: string = null;
+ if ((d2).allOf) {
+ const array = (d2).allOf;
+ for (let d of array) {
+ if ((d).$ref) {
+ supertype = getRef((d).$ref);
+ } else {
+ if(supertype == 'Event') {
+ //let eventName = typeName.replace(/(.*)Event$/, "$1"); // TODO find the real name of the command in the "command"" property rather than infer from typeName
+
+ s += EventInterface(typeName, d);
+ }
+ }
+ }
+ }
+ }
+ s += closeBlock();
+ s += line("}");
+
+ //s+= line('services -[hidden]- data');
+
+// s += closeBlock();
+ s += line("}");
+ s += line("@enduml");
+ s += line();
+
+ return s;
+}
+
+function MessageInterface(interfaceName: string, definition: P.Definition, useLink : boolean): string {
+
+ console.log("plantuml MessageInterface "+interfaceName);
+
+ let s = '';
+
+ s += openBlock(`class ${interfaceName} `);
+ for (let propName in definition.properties) {
+ const required = definition.required ? definition.required.indexOf(propName) >= 0 : false;
+ const propertyHelper: PropertyHelper = new PropertyHelper(interfaceName, propName, definition.properties[propName]);
+ const prop = definition.properties[propName];
+ if(!useLink || !propertyIsLinkable(prop)){
+ s += property(propName, !required, definition.properties[propName]);
+ }
+ }
+ s += closeBlock();
+ return s;
+}
+
+/**
+ * Create links instead of attribute when possible
+ * @param interfaceName
+ * @param definition
+ * @returns
+ */
+function MessageInterfaceLinks(interfaceName: string, definition: P.Definition): string {
+ let s = '';
+ for (let propName in definition.properties) {
+ const required = definition.required ? definition.required.indexOf(propName) >= 0 : false;
+ const propertyHelper: PropertyHelper = new PropertyHelper(interfaceName, propName, definition.properties[propName]);
+ const prop = definition.properties[propName];
+ const type = propertySimpleType(prop);
+ if(propertyIsLinkable(prop)){
+ if(propertyIsMany(prop)) {
+ s += line(interfaceName + " *-- \"*\" " + type + " : "+propName);
+ } else {
+ s += line(interfaceName + " *-- " + type + " : "+propName);
+ }
+ }
+ }
+ return s;
+}
+
+function RequestInterface(interfaceName: string, definition: P.Definition, responseHelper: ResponseHelper): string {
+
+ // let desc = definition.description;
+ let methodName = "";
+ if (definition.properties && definition.properties.event && definition.properties.event['enum']) {
+ const eventName = `${definition.properties.event['enum'][0]}`;
+ methodName = eventName;
+ // if (eventName) {
+ // desc = `Event message for '${eventName}' event type.\n${desc}`;
+ // }
+ } else if (definition.properties && definition.properties.command && definition.properties.command['enum']) {
+ const requestName = `${definition.properties.command['enum'][0]}`;
+ methodName = requestName;
+ // if (requestName) {
+ // const RequestName = requestName[0].toUpperCase() + requestName.substr(1);
+ // desc = `${RequestName} request; value of command field is '${requestName}'.\n${desc}`;
+ // }
+ }
+ let s = '';
+ //s += comment({ description : desc });
+
+
+ // find response type
+ var returnType = '@JsonRequest| ';
+ if(responseHelper.responseDef && responseHelper.responseDef.properties && responseHelper.responseDef.properties.body){
+ //console.log(interfaceName+': responsedefinition = '+responsedefinition.properties.body);
+ //console.log(responsedefinition.properties.body);
+ //console.table(responsedefinition.properties.body);
+ //console.table(argumentType(responsedefinition.properties.body));
+ if(responseHelper.responseDef.properties.body.type === 'object'){
+ returnType = responseHelper.responseDefTypeName;
+ } else {
+ returnType = returnType+ propertyType(responseHelper.responseDef.properties.body);
+ }
+ }
+ var argsString : string[] = [];
+ for (let propName in definition.properties) {
+
+ const type = propertyType(definition.properties[propName]);
+ const propertyDef = `${type} ${propName}`;
+ if (type[0] === '\'' && type[type.length-1] === '\'' && type.indexOf('|') < 0) {
+ //s += line(`// ${propertyDef};`);
+ } else {
+ argsString.push(`${propertyDef}`);
+ }
+ }
+
+ s += line(`${returnType} ${methodName}(`+argsString.join(', ')+`)`);
+
+ return s;
+}
+
+function EventInterface(interfaceName: string, definition: P.Definition): string {
+
+ let methodName = "";
+ if (definition.properties && definition.properties.event && definition.properties.event['enum']) {
+ const eventName = `${definition.properties.event['enum'][0]}`;
+ methodName = eventName;
+ } else if (definition.properties && definition.properties.command && definition.properties.command['enum']) {
+ const eventName = `${definition.properties.command['enum'][0]}`;
+ methodName = eventName;
+ if (eventName) {
+ const EventtName = eventName[0].toUpperCase() + eventName.substr(1);
+ }
+ }
+ let s = '';
+
+
+ var argsString : string[] = [];
+
+ if(definition.properties['body']){
+ argsString.push(`${interfaceName}Arguments args`);
+ }
+
+
+ /*for (let propName in definition.properties) {
+
+ const type = propertyType(definition.properties[propName]);
+ const propertyDef = `${type} ${propName}`;
+ if (type[0] === '\'' && type[type.length-1] === '\'' && type.indexOf('|') < 0) {
+ //s += line(`// ${propertyDef};`);
+ } else {
+ argsString.push(`${propertyDef}`);
+ }
+ }*/
+
+ s += line(`@JsonNotification| void ${methodName}(`+argsString.join(', ')+`)`);
+ return s;
+}
+
+function Enum(typeName: string, definition: P.StringType): string {
+ let s = line();
+ s += comment(definition);
+ const x = enumAsOrType(definition.enum);
+ s += line(`export type ${typeName} = ${x};`);
+ return s;
+}
+
+function enumAsOrType(enm: string[]) {
+ return enm.map(v => `'${v}'`).join(' | ');
+}
+
+function comment(c: P.Commentable): string {
+
+ let description = c.description || '';
+
+ if ((c).items) { // array
+ c = (c).items;
+ }
+
+ // a 'closed' enum with individual descriptions
+ if (c.enum && c.enumDescriptions) {
+ for (let i = 0; i < c.enum.length; i++) {
+ description += `\n'${c.enum[i]}': ${c.enumDescriptions[i]}`;
+ }
+ }
+
+ // an 'open' enum
+ if (c._enum) {
+ description += '\nValues: ';
+ if (c.enumDescriptions) {
+ for (let i = 0; i < c._enum.length; i++) {
+ description += `\n'${c._enum[i]}': ${c.enumDescriptions[i]}`;
+ }
+ description += '\netc.';
+ } else {
+ description += `${c._enum.map(v => `'${v}'`).join(', ')}, etc.`;
+ }
+ }
+
+ if (description) {
+ description = description.replace(/(.*)<\/code>/g, "'$1'");
+ numIndents++;
+ description = description.replace(/\n/g, '\n' + indent());
+ numIndents--;
+ if (description.indexOf('\n') >= 0) {
+ return line(`/** ${description}\n${indent()}*/`);
+ } else {
+ return line(`/** ${description} */`);
+ }
+ }
+ return '';
+}
+
+function openBlock(str: string, openChar?: string, indent?: boolean): string {
+ indent = typeof indent === 'boolean' ? indent : true;
+ openChar = openChar || ' {';
+ let s = line(`${str}${openChar}`, true, indent);
+ numIndents++;
+ return s;
+}
+
+function closeBlock(closeChar?: string, newline?: boolean): string {
+ newline = typeof newline === 'boolean' ? newline : true;
+ closeChar = closeChar || '}';
+ numIndents--;
+ return line(closeChar, newline);
+}
+
+function propertyIsLinkable(prop: any): boolean {
+ if (prop.$ref) { return true; }
+ switch (prop.type) {
+ case 'array':
+ const s = propertyType(prop.items);
+ return propertyIsLinkable(prop.items);
+ case 'object':
+ return false;
+ case 'string':
+ return false;
+ case 'integer':
+ return false;
+ }
+ return false;
+}
+function propertyIsMany(prop: any): boolean {
+ if (prop.$ref) { return false; }
+ switch (prop.type) {
+ case 'array':
+ return true;
+ case 'object':
+ return false;
+ case 'string':
+ return false;
+ case 'integer':
+ return false;
+ }
+ return false;
+}
+
+/**
+ * simple type name (without multiplicity brackets)
+ * @param prop
+ * @returns
+ */
+function propertySimpleType(prop: any): string {
+ if (prop.$ref) {
+ //console.log("propertyType is aref " + getRef(prop.$ref));
+ return ""+getRef(prop.$ref);
+ }
+ switch (prop.type) {
+ case 'array':
+ const s = propertyType(prop.items);
+ if (s.indexOf(' ') >= 0) {
+ return `${s}`;
+ }
+ return `${s}`;
+ case 'object':
+ return objectType(prop);
+ case 'string':
+ if (prop.enum) {
+ return enumAsOrType(prop.enum);
+ }
+ return `String`;
+ case 'integer':
+ return 'Integer';
+ }
+ if (Array.isArray(prop.type)) {
+ if (prop.type.length === 7 && prop.type.sort().join() === 'array,boolean,integer,null,number,object,string') { // silly way to detect all possible json schema types
+ return 'Object';
+ } else {
+ return prop.type.map(v => v === 'integer' ? 'Integer' : v).join(' | ');
+ }
+ }
+ return prop.type;
+}
+function propertyType(prop: any): string {
+ if (prop.$ref) {
+ //console.log("propertyType is aref " + getRef(prop.$ref));
+ return ""+getRef(prop.$ref);
+ }
+ switch (prop.type) {
+ case 'array':
+ const s = propertyType(prop.items);
+ if (s.indexOf(' ') >= 0) {
+ return `(${s})[]`;
+ }
+ return `${s}[]`;
+ case 'object':
+ return objectType(prop);
+ case 'string':
+ if (prop.enum) {
+ return enumAsOrType(prop.enum);
+ }
+ return `String`;
+ case 'integer':
+ return 'Integer';
+ }
+ if (Array.isArray(prop.type)) {
+ if (prop.type.length === 7 && prop.type.sort().join() === 'array,boolean,integer,null,number,object,string') { // silly way to detect all possible json schema types
+ return 'Object';
+ } else {
+ return prop.type.map(v => v === 'integer' ? 'Integer' : v).join(' | ');
+ }
+ }
+ return prop.type;
+}
+
+function objectType(prop: any): string {
+ if (prop.properties) {
+ let s = openBlock('', '{', false);
+
+ for (let propName in prop.properties) {
+ const required = prop.required ? prop.required.indexOf(propName) >= 0 : false;
+ s += property(propName, !required, prop.properties[propName]);
+ }
+
+ s += closeBlock('}', false);
+ return s;
+ }
+ if (prop.additionalProperties) {
+ return `Map`;
+ //return `{ [key: string]: ${orType(prop.additionalProperties.type)}; }`;
+ }
+ return '{}';
+}
+
+function orType(enm: string | string[]): string {
+ if (typeof enm === 'string') {
+ return 'String';
+ }
+ return enm.join(' | ');
+}
+
+function requestArg(name: string, prop: P.PropertyType): string {
+ let s = '';
+ s += comment(prop);
+ const type = propertyType(prop);
+ const propertyDef = `${type} ${name}`;
+ if (type[0] === '\'' && type[type.length-1] === '\'' && type.indexOf('|') < 0) {
+ s += line(`// ${propertyDef};`);
+ } else {
+ s += line(`${propertyDef};`);
+ }
+ return s;
+}
+
+function property(name: string, optional: boolean, prop: P.PropertyType): string {
+ let s = '';
+ //s += comment(prop);
+ const type = propertyType(prop);
+ const propertyDef = `${type} ${name}`;
+ /*if(prop.type == 'object' && name == 'body') {
+ console.log("this is a body object");
+ }*/
+ if (type[0] === '\'' && type[type.length-1] === '\'' && type.indexOf('|') < 0) {
+ s += line(`\' ${propertyDef};`);
+ } else {
+ // if(!optional) {
+ // s += line('// @NonNull');
+ // }
+ s += line(`${propertyDef}`);
+ }
+ //console.log("type ="+type+"; name="+name);
+ return s;
+}
+
+
+function getRef(ref: string): string {
+ const REXP = /#\/(.+)\/(.+)/;
+ const matches = REXP.exec(ref);
+ if (matches && matches.length === 3) {
+ return matches[2];
+ }
+ console.log('error: ref');
+ return ref;
+}
+
+function indent(): string {
+ return '\t'.repeat(numIndents);
+}
+
+function line(str?: string, newline?: boolean, indnt?: boolean): string {
+ newline = typeof newline === 'boolean' ? newline : true;
+ indnt = typeof indnt === 'boolean' ? indnt : true;
+ let s = '';
+ if (str) {
+ if (indnt) {
+ s += indent();
+ }
+ s += str;
+ }
+ if (newline) {
+ s += '\n';
+ }
+ return s;
+}
+
+
+/// Main
+
+/*
+const debugProtocolSchema = JSON.parse(fs.readFileSync('./ModelExecutionTraceProtocol.json').toString());
+
+const emitStr = TSGeneratorModule('ModelExecutionTraceProtocol', debugProtocolSchema);
+
+fs.writeFileSync(`../metp_protocol/src/ModelExecutionTraceProtocol.ts`, emitStr, { encoding: 'utf-8'});
+*/
diff --git a/protocols/generators/ts/JSONSchema2APIProtocolGenerator/tsconfig.json b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/tsconfig.json
new file mode 100644
index 000000000..54e392337
--- /dev/null
+++ b/protocols/generators/ts/JSONSchema2APIProtocolGenerator/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "noImplicitAny": false,
+ "removeComments": false,
+ "preserveConstEnums": true,
+ "target": "ES6",
+ "sourceMap": true,
+ "inlineSourceMap": false,
+ "outDir": "./dist",
+ "strictNullChecks": false
+ },
+ "include": [
+ "src/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/simulationmodelanimation/pom.xml b/simulationmodelanimation/pom.xml
index a0a6950d9..46e5b5bae 100644
--- a/simulationmodelanimation/pom.xml
+++ b/simulationmodelanimation/pom.xml
@@ -27,7 +27,7 @@
- 2.5.0
+ 2.7.0
scm:git:https://github.com/SiriusLab/ModelDebugging.git