Source: composer-common/lib/acl/modelbinding.js

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const IllegalModelException = require('../introspect/illegalmodelexception');
const ModelUtil = require('../modelutil');

/**
 * ModelBinding captures a binding to a model element. A ModelBinding can
 * be to a namespace, a class, or an instance of a class, and may optionally
 * be bound to a named variable.
 *
 * @private
 * @class
 * @memberof module:composer-common
 */
class ModelBinding {

    /**
     * Create an ModelBinding from an Abstract Syntax Tree. The AST is the
     * result of parsing.
     *
     * @param {AclRule} aclRule - the AclRule for this ModelBinding
     * @param {Object} ast - the AST created by the parser
     * @throws {IllegalModelException}
     */
    constructor(aclRule, ast) {
        if(!aclRule || !ast) {
            throw new IllegalModelException('Invalid AclRule or AST');
        }

        this.ast = ast;
        this.aclRule = aclRule;
        this.process();
    }

    /**
     * Visitor design pattern
     * @param {Object} visitor - the visitor
     * @param {Object} parameters  - the parameter
     * @return {Object} the result of visiting or null
     * @private
     */
    accept(visitor,parameters) {
        return visitor.visit(this, parameters);
    }

    /**
     * Returns the AclRule that owns this ModelBinding.
     *
     * @return {AclRule} the owning AclRule
     */
    getAclRule() {
        return this.aclRule;
    }

    /**
     * Process the AST and build the model
     *
     * @throws {InvalidModelException}
     * @private
     */
    process() {
        this.qualifiedName = this.ast.qualifiedName;
        this.instanceId = null;

        if(this.ast.instanceId) {
            this.instanceId = this.ast.instanceId.name;
        }

        this.variableName = null;
        if(this.ast.variableName) {
            this.variableName = this.ast.variableName.name;
        }
    }

    /**
     * Returns strind representation of this object
     *
     * @return {string} the string version of the object
     */
    toString() {
        let result = 'ModelBinding ' + this.qualifiedName;
        if(this.instanceId) {
            result += '#' + this.instanceId;
        }

        if(this.variableName) {
            result += ':' + this.variableName;
        }

        return result;
    }

    /**
     * Returns the fully qualified name of the model element for this ModelBinding.
     *
     * @return {string} the fully qualified model name
     */
    getFullyQualifiedName() {
        return this.qualifiedName;
    }

    /**
     * Returns the identifier of the instance of the model element for this ModelBinding.
     *
     * @return {string} the identifier of the instance, or null
     */
    getInstanceIdentifier() {
        return this.instanceId;
    }

    /**
     * Returns the name of the variable of the model element for this ModelBinding.
     *
     * @return {string} the name of the variable, or null
     */
    getVariableName() {
        return this.variableName;
    }

    /**
     * Semantic validation of the structure of this ModelBinding.
     * <p>
     * Algorithm:
     * </p>
     * <ul>
     * <li>If we have a variableName, then qualifiedName cannot be a namespace
     * <li>If we have an instanceId, then qualifiedName cannot be a namespace
     * </ul>
     * <pre>
     * We assume we have ns.class.property and try to resolve the class in ns
     * - On success
     * -- Check that the property exists
     * - On failure
     * -- Try to resolve ns.class
     * -- On failure
     * --- If instanceId and variableName are null
     * --- Try to resolve ns
     * </pre>
     * @throws {InvalidModelException}
     * @private
     */
    validate() {
        const mm = this.getAclRule().getAclFile().getModelManager();

        // assume qualifiedName is ns.class.property
        const nsDotClass = ModelUtil.getNamespace(this.qualifiedName);
        const ns = ModelUtil.getNamespace(nsDotClass);
        const className = ModelUtil.getShortName(nsDotClass);
        const propertyName = ModelUtil.getShortName(this.qualifiedName);
        const modelFile = mm.getModelFile(ns);

        if(modelFile) {
            const classDeclaration = modelFile.getLocalType(className);
            if(classDeclaration) {
                const property = classDeclaration.getProperty(propertyName);
                if(!property) {
                    throw new Error('Failed to find property ' + this.qualifiedName);
                }
            }
        }
        else {
            // assume qualifiedName is ns.class
            const ns = ModelUtil.getNamespace(this.qualifiedName);
            const className = ModelUtil.getShortName(this.qualifiedName);
            const modelFile = mm.getModelFile(ns);

            if(modelFile) {
                const classDeclaration = modelFile.getLocalType(className);
                if(!classDeclaration) {
                    throw new Error('Failed to find class ' + this.qualifiedName);
                }
            }
            else if(this.instanceId === null && this.variableName === null) {
                // assume namespace
                const modelFile = mm.getModelFile(this.qualifiedName);
                if(!modelFile) {
                    throw new Error('Failed to find namespace ' + this.qualifiedName);
                }
            }
            else {
                throw new Error('Failed to resolve ' + this.qualifiedName);
            }
        }
    }

    /**
     * Returns a new object representing this function declaration that is
     * suitable for serializing as JSON.
     * @return {Object} A new object suitable for serializing as JSON.
     */
    toJSON() {
        let result = {
            qualifiedName: this.qualifiedName,
            instanceId: this.instanceId,
            variableName: this.variableName
        };
        return result;
    }
}

module.exports = ModelBinding;