@accordproject/concerto-core
Advanced tools
Comparing version 3.19.3-20241014162440 to 3.19.3-20241021135045
/*! | ||
* Concerto v3.19.3-20241014162440 | ||
* Concerto v3.19.3-20241021135045 | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
@@ -4,0 +4,0 @@ * you may not use this file except in compliance with the License. |
@@ -30,2 +30,3 @@ /* | ||
const { getRootModel } = require('./rootmodel'); | ||
const { getDecoratorModel } = require('./decoratormodel'); | ||
const MetamodelException = require('./metamodelexception'); | ||
@@ -61,2 +62,12 @@ | ||
// default decorator validation configuration | ||
const DEFAULT_DECORATOR_VALIDATION = { | ||
missingDecorator: undefined, // 'error' | 'warn' (see Logger.levels)..., | ||
invalidDecorator: undefined, // 'error' | 'warn' ... | ||
}; | ||
// these namespaces are internal and excluded by default by getModelFiles | ||
// and ignored by fromAst | ||
const EXCLUDE_NS = ['concerto@1.0.0', 'concerto', 'concerto.decorator@1.0.0']; | ||
/** | ||
@@ -88,2 +99,5 @@ * Manages the Concerto model files. | ||
* @param {boolean} [options.importAliasing] - When true, the Concerto Aliasing feature is enabled | ||
* @param {object} [options.decoratorValidation] - the decorator validation configuration | ||
* @param {string} [options.decoratorValidation.missingDecorator] - the validation log level for missingDecorator decorators: off, warning, error | ||
* @param {string} [options.decoratorValidation.invalidDecorator] - the validation log level for invalidDecorator decorators: off, warning, error | ||
* @param {*} [processFile] - how to obtain a concerto AST from an input to the model manager | ||
@@ -99,3 +113,5 @@ */ | ||
this.options = options; | ||
this.addDecoratorModel(); | ||
this.addRootModel(); | ||
this.decoratorValidation = options?.decoratorValidation ? options?.decoratorValidation : DEFAULT_DECORATOR_VALIDATION; | ||
@@ -106,2 +122,3 @@ // TODO Remove on release of MapType | ||
this.importAliasing = process?.env?.IMPORT_ALIASING === 'true' || !!options?.importAliasing; | ||
// Cache a copy of the Metamodel ModelFile for use when validating the structure of ModelFiles later. | ||
@@ -164,2 +181,12 @@ this.metamodelModelFile = new ModelFile(this, MetaModelUtil.metaModelAst, undefined, MetaModelNamespace); | ||
/** | ||
* Adds decorator types | ||
* @private | ||
*/ | ||
addDecoratorModel() { | ||
const {decoratorModelAst, decoratorModelCto, decoratorModelFile} = getDecoratorModel(); | ||
const m = new ModelFile(this, decoratorModelAst, decoratorModelCto, decoratorModelFile); | ||
this.addModelFile(m, decoratorModelCto, decoratorModelFile, true); | ||
} | ||
/** | ||
* Visitor design pattern | ||
@@ -479,2 +506,10 @@ * @param {Object} visitor - the visitor | ||
/** | ||
* Returns the status of the decorator validation options | ||
* @returns {object} returns an object that indicates the log levels for defined and undefined decorators | ||
*/ | ||
getDecoratorValidation() { | ||
return this.decoratorValidation; | ||
} | ||
/** | ||
* Get the array of model file instances | ||
@@ -492,3 +527,3 @@ * @param {Boolean} [includeConcertoNamespace] - whether to include the concerto namespace | ||
const ns = keys[n]; | ||
if(includeConcertoNamespace || (ns !== 'concerto@1.0.0' && ns !== 'concerto')) { | ||
if(includeConcertoNamespace || (!EXCLUDE_NS.includes(ns))) { | ||
result.push(this.modelFiles[ns]); | ||
@@ -572,2 +607,3 @@ } | ||
this.modelFiles = {}; | ||
this.addDecoratorModel(); | ||
this.addRootModel(); | ||
@@ -763,3 +799,3 @@ } | ||
resolveMetaModel(metaModel) { | ||
const priorModels = this.getAst(); | ||
const priorModels = this.getAst(false, true); | ||
return MetaModelUtil.resolveLocalNames(priorModels, metaModel); | ||
@@ -775,4 +811,6 @@ } | ||
ast.models.forEach( model => { | ||
const modelFile = new ModelFile( this, model ); | ||
this.addModelFile( modelFile, null, null, true ); | ||
if(!EXCLUDE_NS.includes(model.namespace)) { // excludes the internal namespaces, already added | ||
const modelFile = new ModelFile( this, model ); | ||
this.addModelFile( modelFile, null, null, true ); | ||
} | ||
}); | ||
@@ -785,5 +823,6 @@ this.validateModelFiles(); | ||
* @param {boolean} [resolve] - whether to resolve names | ||
* @param {boolean} [includeConcertoNamespaces] - whether to include the concerto namespaces | ||
* @returns {*} the metamodel | ||
*/ | ||
getAst(resolve) { | ||
getAst(resolve,includeConcertoNamespaces) { | ||
const result = { | ||
@@ -793,3 +832,3 @@ $class: `${MetaModelNamespace}.Models`, | ||
}; | ||
const modelFiles = this.getModelFiles(); | ||
const modelFiles = this.getModelFiles(includeConcertoNamespaces); | ||
modelFiles.forEach((thisModelFile) => { | ||
@@ -796,0 +835,0 @@ let metaModel = thisModelFile.getAst(); |
@@ -34,6 +34,6 @@ /* | ||
const DCS_VERSION = '0.3.0'; | ||
const DCS_VERSION = '0.4.0'; | ||
const DCS_MODEL = `concerto version "^3.0.0" | ||
namespace org.accordproject.decoratorcommands@0.3.0 | ||
namespace org.accordproject.decoratorcommands@0.4.0 | ||
@@ -87,2 +87,3 @@ import concerto.metamodel@1.0.0.Decorator | ||
o CommandType type | ||
o String decoratorNamespace optional | ||
} | ||
@@ -330,3 +331,3 @@ | ||
DCS_MODEL, | ||
'decoratorcommands@0.3.0.cto' | ||
'decoratorcommands@0.4.0.cto' | ||
); | ||
@@ -371,2 +372,3 @@ const factory = new Factory(validationModelManager); | ||
* @param {boolean} [options.migrate] - migrate the decoratorCommandSet $class to match the dcs model version | ||
* @param {boolean} [options.defaultNamespace] - the default namespace to use for decorator commands that include a decorator without a namespace | ||
* @param {boolean} [options.enableDcsNamespaceTarget] - flag to control applying namespace targeted decorators on top of the namespace instead of all declarations in that namespace | ||
@@ -379,6 +381,27 @@ * @returns {ModelManager} a new model manager with the decorations applied | ||
// we create synthetic imports for all decorator declarations | ||
// along with any of their type reference arguments | ||
const decoratorImports = decoratorCommandSet.commands.map(command => { | ||
return [{ | ||
$class: `${MetaModelNamespace}.ImportType`, | ||
name: command.decorator.name, | ||
namespace: command.decorator.namespace ? command.decorator.namespace : options?.defaultNamespace | ||
}].concat(command.decorator.arguments ? command.decorator.arguments?.filter(a => a.type) | ||
.map(a => { | ||
return { | ||
$class: `${MetaModelNamespace}.ImportType`, | ||
name: a.type.name, | ||
namespace: a.type.namespace ? a.type.namespace : options?.defaultNamespace | ||
}; | ||
}) | ||
: []); | ||
}).flat().filter(i => i.namespace); | ||
const { namespaceCommandsMap, declarationCommandsMap, propertyCommandsMap, mapElementCommandsMap, typeCommandsMap } = this.getDecoratorMaps(decoratorCommandSet); | ||
const ast = modelManager.getAst(true); | ||
const ast = modelManager.getAst(true, true); | ||
const decoratedAst = JSON.parse(JSON.stringify(ast)); | ||
decoratedAst.models.forEach((model) => { | ||
// remove the imports for types defined in this namespace | ||
const neededImports = decoratorImports.filter(i => i.namespace !== model.namespace); | ||
// add the imports for decorators, in case they get added below | ||
model.imports = model.imports ? model.imports.concat(neededImports) : neededImports; | ||
model.declarations.forEach((decl) => { | ||
@@ -429,4 +452,8 @@ const declarationDecoratorCommandSets = []; | ||
}); | ||
const enableMapType = modelManager?.enableMapType ? true : false; | ||
const newModelManager = new ModelManager({ enableMapType }); | ||
const newModelManager = new ModelManager({ | ||
strict: modelManager.isStrict(), | ||
enableMapType, | ||
decoratorValidation: modelManager.getDecoratorValidation()}); | ||
newModelManager.fromAst(decoratedAst); | ||
@@ -456,3 +483,3 @@ return newModelManager; | ||
}; | ||
const sourceAst = modelManager.getAst(true); | ||
const sourceAst = modelManager.getAst(true, true); | ||
const decoratorExtrator = new DecoratorExtractor(options.removeDecoratorsFromModel, options.locale, DCS_VERSION, sourceAst, DecoratorExtractor.Action.EXTRACT_ALL); | ||
@@ -480,3 +507,3 @@ const collectionResp = decoratorExtrator.extract(); | ||
}; | ||
const sourceAst = modelManager.getAst(true); | ||
const sourceAst = modelManager.getAst(true, true); | ||
const decoratorExtrator = new DecoratorExtractor(options.removeDecoratorsFromModel, options.locale, DCS_VERSION, sourceAst, DecoratorExtractor.Action.EXTRACT_VOCAB); | ||
@@ -784,3 +811,3 @@ const collectionResp = decoratorExtrator.extract(); | ||
Warning.printDeprecationWarning( | ||
'Functionality for namespace targeted Decorator Command Sets has beed changed. Using namespace targets to apply decorators on all declarations in a namespace will be deprecated soon.', | ||
'Functionality for namespace targeted Decorator Command Sets has changed. Using namespace targets to apply decorators on all declarations in a namespace will be deprecated soon.', | ||
ErrorCodes.DEPRECATION_WARNING, | ||
@@ -787,0 +814,0 @@ ErrorCodes.CONCERTO_DEPRECATION_001, |
@@ -18,2 +18,5 @@ /* | ||
const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); | ||
const { Logger } = require('@accordproject/concerto-util'); | ||
const ModelUtil = require('../modelutil'); | ||
const IllegalModelException = require('./illegalmodelexception'); | ||
@@ -49,2 +52,16 @@ // Types needed for TypeScript generation. | ||
/** | ||
* Handles a validation error, logging and throwing as required | ||
* @param {string} level the log level | ||
* @param {*} err the error to log | ||
* @param {*} [fileLocation] the file location | ||
* @private | ||
*/ | ||
handleError(level, err) { | ||
Logger.dispatch(level, err); | ||
if (level === 'error') { | ||
throw new IllegalModelException(err, this.getParent().getModelFile(), this.ast.location); | ||
} | ||
} | ||
/** | ||
* Visitor design pattern | ||
@@ -81,3 +98,2 @@ * @param {Object} visitor - the visitor | ||
if (thing.$class === `${MetaModelNamespace}.DecoratorTypeReference`) { | ||
// XXX Is this really what we want? | ||
this.arguments.push({ | ||
@@ -97,3 +113,3 @@ type: 'Identifier', | ||
/** | ||
* Validate the property | ||
* Validate the decorator | ||
* @throws {IllegalModelException} | ||
@@ -103,8 +119,75 @@ * @private | ||
validate() { | ||
// check that all type ref arguments can be resolved | ||
const typeRefs = this.arguments.filter(a => a?.type === 'Identifier'); | ||
typeRefs.forEach(typeRef => { | ||
const mf = this.getParent().getModelFile(); | ||
mf.resolveType(`Decorator ${this.getName()} on ${this.getParent().getName()}`, typeRef.name); | ||
}); | ||
const mf = this.getParent().getModelFile(); | ||
const decoratedName = this.getParent().getFullyQualifiedName?.(); | ||
const mm = mf.getModelManager(); | ||
const validationOptions = mm.getDecoratorValidation(); | ||
if (validationOptions.missingDecorator || validationOptions.invalidDecorator) { | ||
try { | ||
// this throws if the type does not exist | ||
mf.resolveType(decoratedName, this.getName(), this.ast.location); | ||
const decoratorDecl = mf.getType(this.getName()); | ||
const requiredProperties = decoratorDecl.getProperties().filter(p => !p.isOptional()); | ||
const optionalProperties = decoratorDecl.getProperties().filter(p => p.isOptional()); | ||
const allProperties = [...requiredProperties, ...optionalProperties]; | ||
if (this.getArguments().length < requiredProperties.length) { | ||
const err = `Decorator ${this.getName()} has too few arguments. Required properties are: [${requiredProperties.map(p => p.getName()).join()}]`; | ||
this.handleError(validationOptions.invalidDecorator, err); | ||
} | ||
const args = this.getArguments(); | ||
for (let n = 0; n < args.length; n++) { | ||
const arg = args[n]; | ||
if (n > allProperties.length - 1) { | ||
const err = `Decorator ${this.getName()} has too many arguments. Properties are: [${allProperties.map(p => p.getName()).join()}]`; | ||
this.handleError(validationOptions.invalidDecorator, err); | ||
} | ||
else { | ||
const property = allProperties[n]; | ||
const argType = typeof arg; | ||
switch (property.getType()) { | ||
case 'Integer': | ||
case 'Double': | ||
case 'Long': | ||
if (argType !== 'number') { | ||
const err = `Decorator ${this.getName()} has invalid decorator argument. Expected number. Found ${argType}, with value ${JSON.stringify(arg)}`; | ||
this.handleError(validationOptions.invalidDecorator, err); | ||
} | ||
break; | ||
case 'String': | ||
if (argType !== 'string') { | ||
const err = `Decorator ${this.getName()} has invalid decorator argument. Expected string. Found ${argType}, with value ${JSON.stringify(arg)}`; | ||
this.handleError(validationOptions.invalidDecorator, err); | ||
} | ||
break; | ||
case 'Boolean': | ||
if (argType !== 'boolean') { | ||
const err = `Decorator ${this.getName()} has invalid decorator argument. Expected boolean. Found ${argType}, with value ${JSON.stringify(arg)}`; | ||
this.handleError(validationOptions.invalidDecorator, err); | ||
} | ||
break; | ||
default: { | ||
if (argType !== 'object' || arg?.type !== 'Identifier') { | ||
const err = `Decorator ${this.getName()} has invalid decorator argument. Expected object. Found ${argType}, with value ${JSON.stringify(arg)}`; | ||
this.handleError(validationOptions.invalidDecorator, err); | ||
} | ||
const typeDecl = mf.getType(arg.name); | ||
if (!typeDecl) { | ||
const err = `Decorator ${this.getName()} references a type ${arg.name} which has not been defined/imported.`; | ||
this.handleError(validationOptions.invalidDecorator, err); | ||
} | ||
else { | ||
if (!ModelUtil.isAssignableTo(typeDecl.getModelFile(), typeDecl.getFullyQualifiedName(), property)) { | ||
const err = `Decorator ${this.getName()} references a type ${arg.name} which cannot be assigned to the declared type ${property.getFullyQualifiedTypeName()}`; | ||
this.handleError(validationOptions.invalidDecorator, err); | ||
} | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
catch (err) { | ||
this.handleError(validationOptions.missingDecorator, err); | ||
} | ||
} | ||
} | ||
@@ -111,0 +194,0 @@ |
@@ -62,4 +62,10 @@ /* | ||
* @param {Object} [options.regExp] - An alternative regular expression engine. | ||
* @param {boolean} [options.metamodelValidation] - When true, modelfiles will be validated | ||
* @param {boolean} [options.addMetamodel] - When true, the Concerto metamodel is added to the model manager | ||
* @param {boolean} [options.enableMapType] - When true, the Concerto Map Type feature is enabled | ||
* @param {boolean} [options.importAliasing] - When true, the Concerto Map Type feature is enabled | ||
* @param {boolean} [options.importAliasing] - When true, the Concerto Aliasing feature is enabled | ||
* @param {object} [options.decoratorValidation] - the decorator validation configuration | ||
* @param {string} [options.decoratorValidation.missingDecorator] - the validation log level for missingDecorator decorators: off, warning, error | ||
* @param {string} [options.decoratorValidation.invalidDecorator] - the validation log level for invalidDecorator decorators: off, warning, error | ||
* @param {*} [processFile] - how to obtain a concerto AST from an input to the model manager | ||
*/ | ||
@@ -66,0 +72,0 @@ constructor(options) { |
@@ -30,2 +30,3 @@ /* | ||
namespace ${ns} | ||
import concerto.decorator@1.0.0.DotNetNamespace | ||
abstract concept Concept {} | ||
@@ -42,2 +43,2 @@ abstract concept Asset identified {} | ||
module.exports = { getRootModel }; | ||
module.exports = { getRootModel }; |
@@ -16,3 +16,9 @@ { | ||
"namespace": "concerto@1.0.0", | ||
"imports": [], | ||
"imports": [ | ||
{ | ||
"$class": "concerto.metamodel@1.0.0.ImportType", | ||
"name": "DotNetNamespace", | ||
"namespace": "concerto.decorator@1.0.0" | ||
} | ||
], | ||
"declarations": [ | ||
@@ -56,2 +62,2 @@ { | ||
] | ||
} | ||
} |
{ | ||
"name": "@accordproject/concerto-core", | ||
"version": "3.19.3-20241014162440", | ||
"version": "3.19.3-20241021135045", | ||
"description": "Core Implementation for the Concerto Modeling Language", | ||
@@ -17,4 +17,2 @@ "homepage": "https://github.com/accordproject/concerto", | ||
"lint": "eslint .", | ||
"postlint": "npm run licchk", | ||
"licchk": "license-check-and-add", | ||
"postlicchk": "npm run doc", | ||
@@ -58,3 +56,2 @@ "doc": "jsdoc --pedantic --recurse -c jsdoc.json", | ||
"klaw": "3.0.0", | ||
"license-check-and-add": "2.3.6", | ||
"mocha": "10.0.0", | ||
@@ -89,45 +86,2 @@ "mockery": "2.1.0", | ||
"browserslist": "> 0.25%, not dead", | ||
"license-check-and-add-config": { | ||
"folder": "./lib", | ||
"license": "HEADER", | ||
"exact_paths_method": "EXCLUDE", | ||
"exact_paths": [ | ||
"api.txt", | ||
"composer-logs", | ||
"coverage", | ||
"index.d.ts", | ||
"./system", | ||
"LICENSE", | ||
"node_modules", | ||
".nyc-output", | ||
"out", | ||
"dist", | ||
".tern-project" | ||
], | ||
"file_type_method": "EXCLUDE", | ||
"file_types": [ | ||
".yml", | ||
".yaml", | ||
".zip", | ||
".tgz" | ||
], | ||
"insert_license": false, | ||
"license_formats": { | ||
"js|njk|pegjs|cto|acl|qry": { | ||
"prepend": "/*", | ||
"append": " */", | ||
"eachLine": { | ||
"prepend": " * " | ||
} | ||
}, | ||
"npmrc|editorconfig|txt": { | ||
"eachLine": { | ||
"prepend": "# " | ||
} | ||
}, | ||
"md": { | ||
"file": "HEADER.md" | ||
} | ||
} | ||
}, | ||
"nyc": { | ||
@@ -134,0 +88,0 @@ "produce-source-map": "true", |
@@ -28,2 +28,5 @@ export = BaseModelManager; | ||
* @param {boolean} [options.importAliasing] - When true, the Concerto Aliasing feature is enabled | ||
* @param {object} [options.decoratorValidation] - the decorator validation configuration | ||
* @param {string} [options.decoratorValidation.missingDecorator] - the validation log level for missingDecorator decorators: off, warning, error | ||
* @param {string} [options.decoratorValidation.invalidDecorator] - the validation log level for invalidDecorator decorators: off, warning, error | ||
* @param {*} [processFile] - how to obtain a concerto AST from an input to the model manager | ||
@@ -38,2 +41,6 @@ */ | ||
importAliasing?: boolean; | ||
decoratorValidation?: { | ||
missingDecorator?: string; | ||
invalidDecorator?: string; | ||
}; | ||
}, processFile?: any); | ||
@@ -53,3 +60,14 @@ processFile: any; | ||
importAliasing?: boolean; | ||
decoratorValidation?: { | ||
missingDecorator?: string; | ||
invalidDecorator?: string; | ||
}; | ||
}; | ||
decoratorValidation: { | ||
missingDecorator: any; | ||
invalidDecorator: any; | ||
} | { | ||
missingDecorator?: string; | ||
invalidDecorator?: string; | ||
}; | ||
enableMapType: boolean; | ||
@@ -79,2 +97,7 @@ importAliasing: boolean; | ||
/** | ||
* Adds decorator types | ||
* @private | ||
*/ | ||
private addDecoratorModel; | ||
/** | ||
* Visitor design pattern | ||
@@ -194,2 +217,7 @@ * @param {Object} visitor - the visitor | ||
/** | ||
* Returns the status of the decorator validation options | ||
* @returns {object} returns an object that indicates the log levels for defined and undefined decorators | ||
*/ | ||
getDecoratorValidation(): object; | ||
/** | ||
* Get the array of model file instances | ||
@@ -333,5 +361,6 @@ * @param {Boolean} [includeConcertoNamespace] - whether to include the concerto namespace | ||
* @param {boolean} [resolve] - whether to resolve names | ||
* @param {boolean} [includeConcertoNamespaces] - whether to include the concerto namespaces | ||
* @returns {*} the metamodel | ||
*/ | ||
getAst(resolve?: boolean): any; | ||
getAst(resolve?: boolean, includeConcertoNamespaces?: boolean): any; | ||
/** | ||
@@ -338,0 +367,0 @@ * A function type definition for use as an argument to the filter function |
@@ -84,2 +84,3 @@ export = DecoratorManager; | ||
* @param {boolean} [options.migrate] - migrate the decoratorCommandSet $class to match the dcs model version | ||
* @param {boolean} [options.defaultNamespace] - the default namespace to use for decorator commands that include a decorator without a namespace | ||
* @param {boolean} [options.enableDcsNamespaceTarget] - flag to control applying namespace targeted decorators on top of the namespace instead of all declarations in that namespace | ||
@@ -92,2 +93,3 @@ * @returns {ModelManager} a new model manager with the decorations applied | ||
migrate?: boolean; | ||
defaultNamespace?: boolean; | ||
enableDcsNamespaceTarget?: boolean; | ||
@@ -94,0 +96,0 @@ }): ModelManager; |
@@ -19,2 +19,10 @@ export = Decorator; | ||
/** | ||
* Handles a validation error, logging and throwing as required | ||
* @param {string} level the log level | ||
* @param {*} err the error to log | ||
* @param {*} [fileLocation] the file location | ||
* @private | ||
*/ | ||
private handleError; | ||
/** | ||
* Visitor design pattern | ||
@@ -39,3 +47,3 @@ * @param {Object} visitor - the visitor | ||
/** | ||
* Validate the property | ||
* Validate the decorator | ||
* @throws {IllegalModelException} | ||
@@ -42,0 +50,0 @@ * @private |
@@ -22,4 +22,10 @@ export = ModelManager; | ||
* @param {Object} [options.regExp] - An alternative regular expression engine. | ||
* @param {boolean} [options.metamodelValidation] - When true, modelfiles will be validated | ||
* @param {boolean} [options.addMetamodel] - When true, the Concerto metamodel is added to the model manager | ||
* @param {boolean} [options.enableMapType] - When true, the Concerto Map Type feature is enabled | ||
* @param {boolean} [options.importAliasing] - When true, the Concerto Map Type feature is enabled | ||
* @param {boolean} [options.importAliasing] - When true, the Concerto Aliasing feature is enabled | ||
* @param {object} [options.decoratorValidation] - the decorator validation configuration | ||
* @param {string} [options.decoratorValidation.missingDecorator] - the validation log level for missingDecorator decorators: off, warning, error | ||
* @param {string} [options.decoratorValidation.invalidDecorator] - the validation log level for invalidDecorator decorators: off, warning, error | ||
* @param {*} [processFile] - how to obtain a concerto AST from an input to the model manager | ||
*/ | ||
@@ -29,4 +35,10 @@ constructor(options?: { | ||
regExp?: any; | ||
metamodelValidation?: boolean; | ||
addMetamodel?: boolean; | ||
enableMapType?: boolean; | ||
importAliasing?: boolean; | ||
decoratorValidation?: { | ||
missingDecorator?: string; | ||
invalidDecorator?: string; | ||
}; | ||
}); | ||
@@ -33,0 +45,0 @@ /** |
Sorry, the diff of this file is too big to display
1363938
28
140
15819