All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.structurizr.dsl.StructurizrDslParser Maven / Gradle / Ivy

The newest version!
package com.structurizr.dsl;

import com.structurizr.PropertyHolder;
import com.structurizr.Workspace;
import com.structurizr.model.*;
import com.structurizr.util.StringUtils;
import com.structurizr.view.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * Main DSL parser class - forms the API for using the parser.
 */
public final class StructurizrDslParser extends StructurizrDslTokens {

    private static final Log log = LogFactory.getLog(StructurizrDslParser.class);

    private static final String BOM = "\uFEFF";

    private static final Pattern EMPTY_LINE_PATTERN = Pattern.compile("^\\s*");

    private static final Pattern COMMENT_PATTERN = Pattern.compile("^\\s*?(//|#).*$");
    private static final String MULTI_LINE_COMMENT_START_TOKEN = "/*";
    private static final String MULTI_LINE_COMMENT_END_TOKEN = "*/";
    private static final String MULTI_LINE_SEPARATOR = "\\";

    private static final Pattern STRING_SUBSTITUTION_PATTERN = Pattern.compile("(\\$\\{[a-zA-Z0-9-_.]+?})");

    private static final String STRUCTURIZR_DSL_IDENTIFIER_PROPERTY_NAME = "structurizr.dsl.identifier";

    private Charset characterEncoding = StandardCharsets.UTF_8;
    private IdentifierScope identifierScope = IdentifierScope.Flat;
    private final Stack contextStack;
    private final Set parsedTokens = new HashSet<>();
    private final IdentifiersRegister identifiersRegister;
    private final Map constantsAndVariables;

    private final List dslSourceLines = new ArrayList<>();
    private Workspace workspace;
    private boolean extendingWorkspace = false;

    private boolean restricted = false;

    /**
     * Creates a new instance of the parser.
     */
    public StructurizrDslParser() {
        contextStack = new Stack<>();
        identifiersRegister = new IdentifiersRegister();
        constantsAndVariables = new HashMap<>();
    }

    /**
     * Provides a way to change the character encoding used by the DSL parser.
     *
     * @param characterEncoding     a Charset instance
     */
    public void setCharacterEncoding(Charset characterEncoding) {
        if (characterEncoding == null) {
            throw new IllegalArgumentException("A character encoding must be specified");
        }

        this.characterEncoding = characterEncoding;
    }

    IdentifierScope getIdentifierScope() {
        return identifierScope;
    }

    void setIdentifierScope(IdentifierScope identifierScope) {
        if (identifierScope == null) {
            identifierScope = IdentifierScope.Flat;
        }

        this.identifierScope = identifierScope;
        this.identifiersRegister.setIdentifierScope(identifierScope);
    }

    /**
     * Sets whether to run this parser in restricted mode (this stops !include, !docs, !adrs from working).
     *
     * @param restricted        true for restricted mode, false otherwise
     */
    public void setRestricted(boolean restricted) {
        this.restricted = restricted;
    }

    /**
     * Gets the workspace that has been created by parsing the Structurizr DSL.
     *
     * @return  a Workspace instance
     */
    public Workspace getWorkspace() {
        if (workspace != null) {
            String value = workspace.getProperties().get(DslUtils.STRUCTURIZR_DSL_RETAIN_SOURCE_PROPERTY_NAME);
            if (value == null || value.equalsIgnoreCase("true")) {
                DslUtils.setDsl(workspace, getParsedDsl());
            }
        }

        return workspace;
    }

    private String getParsedDsl() {
        return String.join(System.lineSeparator(), dslSourceLines);
    }

    void parse(DslParserContext context, File path) throws StructurizrDslParserException {
        parse(path);

        context.copyFrom(identifiersRegister);
    }

    /**
     * Parses the specified Structurizr DSL file.
     *
     * @param dslFile       a File object representing a DSL file
     * @throws StructurizrDslParserException when something goes wrong
     */
    public void parse(File dslFile) throws StructurizrDslParserException {
        if (dslFile == null) {
            throw new StructurizrDslParserException("A file must be specified");
        }

        if (!dslFile.exists()) {
            throw new StructurizrDslParserException("The file at " + dslFile.getAbsolutePath() + " does not exist");
        }

        try {
            parse(Files.readAllLines(dslFile.toPath(), characterEncoding), dslFile, false, true);
        } catch (IOException e) {
            throw new StructurizrDslParserException(e.getMessage());
        }
    }

    void parse(DslParserContext context, String dsl) throws StructurizrDslParserException {
        parse(dsl);

        context.copyFrom(identifiersRegister);
    }

    /**
     * Parses the specified Structurizr DSL, adding the parsed content to the workspace.
     *
     * @param dsl       a Structurizr DSL definition, as a single String
     * @throws StructurizrDslParserException when something goes wrong
     */
    public void parse(String dsl) throws StructurizrDslParserException {
        parse(dsl, new File("."));
    }

    /**
     * Parses the specified Structurizr DSL, adding the parsed content to the workspace.
     *
     * @param dsl       a Structurizr DSL definition, as a single String
     * @param dslFile   a File representing the DSL file, and therefore where includes/images/etc should be loaded relative to
     * @throws StructurizrDslParserException when something goes wrong
     */
    public void parse(String dsl, File dslFile) throws StructurizrDslParserException {
        if (StringUtils.isNullOrEmpty(dsl)) {
            throw new StructurizrDslParserException("A DSL fragment must be specified");
        }

        List lines = Arrays.asList(dsl.split("\\r?\\n"));
        parse(lines, dslFile, false, true);
    }

    void parse(List lines, DslContext dslContext) throws StructurizrDslParserException {
        startContext(dslContext);
        parse(lines, null, true, false);
        endContext();
    }

    /**
     * Parses a list of Structurizr DSL lines.
     *
     * @param lines     a Structurizr DSL definition, as a List of String objects (one per line)
     * @param dslFile   a File representing the DSL file, and therefore where includes/images/etc should be loaded relative to
     * @throws StructurizrDslParserException when something goes wrong
     */
    void parse(List lines, File dslFile, boolean fragment, boolean includeInDslSourceLines) throws StructurizrDslParserException {
        List dslLines = preProcessLines(lines);

        for (DslLine dslLine : dslLines) {
            String line = dslLine.getSource();
            String lineForDslSource = line;

            if (line.startsWith(BOM)) {
                // this caters for files encoded as "UTF-8 with BOM"
                line = line.substring(1);
            }

            try {
                if (EMPTY_LINE_PATTERN.matcher(line).matches()) {
                    // do nothing
                } else if (COMMENT_PATTERN.matcher(line).matches()) {
                    // do nothing
                } else if (inContext(InlineScriptDslContext.class)) {
                    if (DslContext.CONTEXT_END_TOKEN.equals(line.trim())) {
                        endContext();
                    } else {
                        getContext(InlineScriptDslContext.class).addLine(line);
                    }
                } else {
                    List listOfTokens = new Tokenizer().tokenize(line);
                    listOfTokens = listOfTokens.stream().map(this::substituteStrings).collect(Collectors.toList());

                    Tokens tokens = new Tokens(listOfTokens);

                    String identifier = null;
                    if (tokens.size() > 3 && ASSIGNMENT_OPERATOR_TOKEN.equals(tokens.get(1))) {
                        identifier = tokens.get(0);
                        identifiersRegister.validateIdentifierName(identifier);

                        tokens = new Tokens(listOfTokens.subList(2, listOfTokens.size()));
                    }

                    String firstToken = tokens.get(0);

                    if (line.trim().startsWith(MULTI_LINE_COMMENT_START_TOKEN) && line.trim().endsWith(MULTI_LINE_COMMENT_END_TOKEN)) {
                        // do nothing
                    } else if (firstToken.startsWith(MULTI_LINE_COMMENT_START_TOKEN)) {
                        startContext(new CommentDslContext());

                    } else if (inContext(CommentDslContext.class) && line.trim().endsWith(MULTI_LINE_COMMENT_END_TOKEN)) {
                        endContext();

                    } else if (inContext(CommentDslContext.class)) {
                        // do nothing

                    } else if (DslContext.CONTEXT_END_TOKEN.equals(tokens.get(0))) {
                        endContext();

                    } else if (INCLUDE_FILE_TOKEN.equalsIgnoreCase(firstToken)) {
                        if (!restricted || tokens.get(1).startsWith("https://") || tokens.get(1).startsWith("http://")) {
                            String leadingSpace = line.substring(0, line.indexOf(INCLUDE_FILE_TOKEN));

                            IncludedDslContext context = new IncludedDslContext(dslFile);
                            new IncludeParser().parse(context, tokens);
                            for (IncludedFile includedFile : context.getFiles()) {
                                List paddedLines = new ArrayList<>();
                                for (String unpaddedLine : includedFile.getLines()) {
                                    if (unpaddedLine.startsWith(BOM)) {
                                        // this caters for files encoded as "UTF-8 with BOM"
                                        unpaddedLine = unpaddedLine.substring(1);
                                    }
                                    paddedLines.add(leadingSpace + unpaddedLine);
                                }

                                parse(paddedLines, includedFile.getFile(), true, true);
                            }
                        } else {
                            throwRestrictedModeException(firstToken + " ");
                        }

                        // include the !include in the parser DSL as: # !include ...
                        lineForDslSource = null;

                    } else if (PLUGIN_TOKEN.equalsIgnoreCase(firstToken)) {
                        if (!restricted) {
                            String fullyQualifiedClassName = new PluginParser().parse(getContext(), tokens.withoutContextStartToken());
                            startContext(new PluginDslContext(fullyQualifiedClassName, dslFile, this));
                            if (!shouldStartContext(tokens)) {
                                // run the plugin immediately, without looking for parameters
                                endContext();
                            }
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if (inContext(PluginDslContext.class)) {
                        new PluginParser().parseParameter(getContext(PluginDslContext.class), tokens);

                    } else if (SCRIPT_TOKEN.equalsIgnoreCase(firstToken)) {
                        if (!restricted) {
                            ScriptParser scriptParser = new ScriptParser();
                            if (scriptParser.isInlineScript(tokens)) {
                                String language = scriptParser.parseInline(tokens.withoutContextStartToken());
                                startContext(new InlineScriptDslContext(getContext(), dslFile, this, language));
                            } else {
                                String filename = scriptParser.parseExternal(tokens.withoutContextStartToken());
                                startContext(new ExternalScriptDslContext(getContext(), dslFile, this, filename));

                                if (shouldStartContext(tokens)) {
                                    // we'll wait for parameters before executing the script
                                } else {
                                    endContext();
                                }
                            }
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if (inContext(ExternalScriptDslContext.class)) {
                        new ScriptParser().parseParameter(getContext(ExternalScriptDslContext.class), tokens);

                    } else if (tokens.size() > 2 && RELATIONSHIP_TOKEN.equals(tokens.get(1)) && (inContext(ModelDslContext.class) || inContext(DeploymentEnvironmentDslContext.class) || inContext(ElementDslContext.class))) {
                        Relationship relationship = new ExplicitRelationshipParser().parse(getContext(), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new RelationshipDslContext(relationship));
                        }

                        registerIdentifier(identifier, relationship);

                    } else if (tokens.size() >= 2 && RELATIONSHIP_TOKEN.equals(tokens.get(0)) && inContext(ElementDslContext.class)) {
                        Relationship relationship = new ImplicitRelationshipParser().parse(getContext(ElementDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new RelationshipDslContext(relationship));
                        }

                        registerIdentifier(identifier, relationship);

                    } else if (tokens.size() > 2 && RELATIONSHIP_TOKEN.equals(tokens.get(1)) && inContext(ElementsDslContext.class)) {
                        Set relationships = new ExplicitRelationshipParser().parse(getContext(ElementsDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new RelationshipsDslContext(getContext(), relationships));
                        }

                    } else if (tokens.size() >= 2 && RELATIONSHIP_TOKEN.equals(tokens.get(0)) && inContext(ElementsDslContext.class)) {
                        Set relationships = new ImplicitRelationshipParser().parse(getContext(ElementsDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new RelationshipsDslContext(getContext(), relationships));
                        }

                    } else if ((FIND_ELEMENT_TOKEN.equalsIgnoreCase(firstToken) || FIND_RELATIONSHIP_TOKEN.equalsIgnoreCase(firstToken) || REF_TOKEN.equalsIgnoreCase(firstToken) || EXTEND_TOKEN.equalsIgnoreCase(firstToken)) && (inContext(ModelItemDslContext.class) || inContext(ModelDslContext.class))) {
                        ModelItem modelItem = null;

                        if (REF_TOKEN.equalsIgnoreCase(firstToken)) {
                            log.warn(REF_TOKEN + " has been deprecated and will be removed in a future release - please use !element or !relationship instead");
                            modelItem = new RefParser().parse(getContext(), tokens.withoutContextStartToken());
                        } else if (EXTEND_TOKEN.equalsIgnoreCase(firstToken)) {
                            log.warn(EXTEND_TOKEN + " has been deprecated and will be removed in a future release - please use !element or !relationship instead");
                            modelItem = new RefParser().parse(getContext(), tokens.withoutContextStartToken());
                        } else if (FIND_ELEMENT_TOKEN.equalsIgnoreCase(firstToken)) {
                            modelItem = new FindElementParser().parse(getContext(), tokens.withoutContextStartToken());
                        } else if (FIND_RELATIONSHIP_TOKEN.equalsIgnoreCase(firstToken)) {
                            modelItem = new FindRelationshipParser().parse(getContext(), tokens.withoutContextStartToken());
                        }

                        if (shouldStartContext(tokens)) {
                            if (modelItem instanceof Person) {
                                startContext(new PersonDslContext((Person)modelItem));
                            } else if (modelItem instanceof SoftwareSystem) {
                                startContext(new SoftwareSystemDslContext((SoftwareSystem)modelItem));
                            } else if (modelItem instanceof Container) {
                                startContext(new ContainerDslContext((Container) modelItem));
                            } else if (modelItem instanceof Component) {
                                startContext(new ComponentDslContext((Component)modelItem));
                            } else if (modelItem instanceof DeploymentEnvironment) {
                                startContext(new DeploymentEnvironmentDslContext(((DeploymentEnvironment)modelItem).getName()));
                            } else if (modelItem instanceof DeploymentNode) {
                                startContext(new DeploymentNodeDslContext((DeploymentNode)modelItem));
                            } else if (modelItem instanceof InfrastructureNode) {
                                startContext(new InfrastructureNodeDslContext((InfrastructureNode)modelItem));
                            } else if (modelItem instanceof SoftwareSystemInstance) {
                                startContext(new SoftwareSystemInstanceDslContext((SoftwareSystemInstance)modelItem));
                            } else if (modelItem instanceof ContainerInstance) {
                                startContext(new ContainerInstanceDslContext((ContainerInstance)modelItem));
                            } else if (modelItem instanceof Relationship) {
                                startContext(new RelationshipDslContext((Relationship)modelItem));
                            }
                        }

                        if (!StringUtils.isNullOrEmpty(identifier)) {
                            if (modelItem instanceof Element) {
                                registerIdentifier(identifier, (Element)modelItem);
                            } else if (modelItem instanceof Relationship) {
                                registerIdentifier(identifier, (Relationship)modelItem);
                            }
                        }

                    } else if (FIND_ELEMENTS_TOKEN.equalsIgnoreCase(firstToken) && (inContext(ModelDslContext.class) || inContext(DeploymentEnvironmentDslContext.class) || inContext(ElementDslContext.class))) {
                        Set elements = new FindElementsParser().parse(getContext(), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new ElementsDslContext(getContext(), elements));
                        }

                    } else if (FIND_RELATIONSHIPS_TOKEN.equalsIgnoreCase(firstToken) && (inContext(ModelDslContext.class) || inContext(DeploymentEnvironmentDslContext.class) || inContext(ElementDslContext.class))) {
                        Set relationships = new FindRelationshipsParser().parse(getContext(), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new RelationshipsDslContext(getContext(), relationships));
                        }

                    } else if (CUSTOM_ELEMENT_TOKEN.equalsIgnoreCase(firstToken) && (inContext(ModelDslContext.class))) {
                        CustomElement customElement = new CustomElementParser().parse(getContext(ModelDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new CustomElementDslContext(customElement));
                        }

                        registerIdentifier(identifier, customElement);

                    } else if (PERSON_TOKEN.equalsIgnoreCase(firstToken) && (inContext(ModelDslContext.class))) {
                        Person person = new PersonParser().parse(getContext(ModelDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new PersonDslContext(person));
                        }

                        registerIdentifier(identifier, person);

                    } else if (SOFTWARE_SYSTEM_TOKEN.equalsIgnoreCase(firstToken) && (inContext(ModelDslContext.class))) {
                        SoftwareSystem softwareSystem = new SoftwareSystemParser().parse(getContext(ModelDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new SoftwareSystemDslContext(softwareSystem));
                        }

                        registerIdentifier(identifier, softwareSystem);

                    } else if (CONTAINER_TOKEN.equalsIgnoreCase(firstToken) && inContext(SoftwareSystemDslContext.class)) {
                        Container container = new ContainerParser().parse(getContext(SoftwareSystemDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new ContainerDslContext(container));
                        }

                        registerIdentifier(identifier, container);

                    } else if (COMPONENT_TOKEN.equalsIgnoreCase(firstToken) && inContext(ContainerDslContext.class)) {
                        Component component = new ComponentParser().parse(getContext(ContainerDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new ComponentDslContext(component));
                        }

                        registerIdentifier(identifier, component);

                    } else if (COMPONENT_FINDER_TOKEN.equalsIgnoreCase(firstToken) && inContext(ContainerDslContext.class)) {
                        if (!restricted) {
                            if (shouldStartContext(tokens)) {
                                startContext(new ComponentFinderDslContext(this, getContext(ContainerDslContext.class).getContainer()));
                            }
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if (COMPONENT_FINDER_CLASSES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderDslContext.class)) {
                        new ComponentFinderParser().parseClasses(getContext(ComponentFinderDslContext.class), tokens);

                    } else if (COMPONENT_FINDER_SOURCE_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderDslContext.class)) {
                        new ComponentFinderParser().parseSource(getContext(ComponentFinderDslContext.class), tokens);

                    } else if (COMPONENT_FINDER_FILTER_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderDslContext.class)) {
                        new ComponentFinderParser().parseFilter(getContext(ComponentFinderDslContext.class), tokens);

                    } else if (COMPONENT_FINDER_STRATEGY_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderDslContext.class)) {
                        if (shouldStartContext(tokens)) {
                            startContext(new ComponentFinderStrategyDslContext(getContext(ComponentFinderDslContext.class).getComponentFinderBuilder()));
                        }

                    } else if (COMPONENT_FINDER_STRATEGY_TECHNOLOGY_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
                        new ComponentFinderStrategyParser().parseTechnology(getContext(ComponentFinderStrategyDslContext.class), tokens);

                    } else if (COMPONENT_FINDER_STRATEGY_MATCHER_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
                        new ComponentFinderStrategyParser().parseMatcher(getContext(ComponentFinderStrategyDslContext.class), tokens, dslFile);

                    } else if (COMPONENT_FINDER_STRATEGY_FILTER_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
                        new ComponentFinderStrategyParser().parseFilter(getContext(ComponentFinderStrategyDslContext.class), tokens, dslFile);

                    } else if (COMPONENT_FINDER_STRATEGY_SUPPORTING_TYPES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
                        new ComponentFinderStrategyParser().parseSupportingTypes(getContext(ComponentFinderStrategyDslContext.class), tokens, dslFile);

                    } else if (COMPONENT_FINDER_STRATEGY_NAME_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
                        new ComponentFinderStrategyParser().parseName(getContext(ComponentFinderStrategyDslContext.class), tokens, dslFile);

                    } else if (COMPONENT_FINDER_STRATEGY_DESCRIPTION_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
                        new ComponentFinderStrategyParser().parseDescription(getContext(ComponentFinderStrategyDslContext.class), tokens, dslFile);

                    } else if (COMPONENT_FINDER_STRATEGY_URL_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
                        new ComponentFinderStrategyParser().parseUrl(getContext(ComponentFinderStrategyDslContext.class), tokens, dslFile);

                    } else if (COMPONENT_FINDER_STRATEGY_FOREACH_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentFinderStrategyDslContext.class)) {
                        if (shouldStartContext(tokens)) {
                            startContext(new ComponentFinderStrategyForEachDslContext(getContext(ComponentFinderStrategyDslContext.class), this));
                        }

                    } else if (inContext(ComponentFinderStrategyForEachDslContext.class)) {
                        getContext(ComponentFinderStrategyForEachDslContext.class).addLine(line);

                    } else if (ENTERPRISE_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelDslContext.class)) {
                        throw new RuntimeException("The enterprise keyword was previously deprecated, and has now been removed - please use group instead (https://docs.structurizr.com/dsl/language#group)");

                    } else if (GROUP_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelDslContext.class)) {
                        ElementGroup group = new GroupParser().parse(getContext(ModelDslContext.class), tokens);

                        startContext(new ModelDslContext(group));
                        registerIdentifier(identifier, group);
                    } else if (GROUP_TOKEN.equalsIgnoreCase(firstToken) && inContext(SoftwareSystemDslContext.class)) {
                        ElementGroup group = new GroupParser().parse(getContext(SoftwareSystemDslContext.class), tokens);

                        SoftwareSystem softwareSystem = getContext(SoftwareSystemDslContext.class).getSoftwareSystem();
                        group.setParent(softwareSystem);
                        startContext(new SoftwareSystemDslContext(softwareSystem, group));
                        registerIdentifier(identifier, group);
                    } else if (GROUP_TOKEN.equalsIgnoreCase(firstToken) && inContext(ContainerDslContext.class)) {
                        ElementGroup group = new GroupParser().parse(getContext(ContainerDslContext.class), tokens);

                        Container container = getContext(ContainerDslContext.class).getContainer();
                        group.setParent(container);
                        startContext(new ContainerDslContext(container, group));
                        registerIdentifier(identifier, group);
                    } else if (GROUP_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentEnvironmentDslContext.class)) {
                        ElementGroup group = new GroupParser().parse(getContext(DeploymentEnvironmentDslContext.class), tokens);

                        String environment = getContext(DeploymentEnvironmentDslContext.class).getEnvironment();
                        startContext(new DeploymentEnvironmentDslContext(environment, group));
                        registerIdentifier(identifier, group);
                    } else if (GROUP_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentNodeDslContext.class)) {
                        ElementGroup group = new GroupParser().parse(getContext(DeploymentNodeDslContext.class), tokens);

                        DeploymentNode deploymentNode = getContext(DeploymentNodeDslContext.class).getDeploymentNode();
                        startContext(new DeploymentNodeDslContext(deploymentNode, group));
                        registerIdentifier(identifier, group);
                    } else if ((TAGS_TOKEN.equalsIgnoreCase(firstToken) || TAG_TOKEN.equalsIgnoreCase(firstToken)) && inContext(ModelItemDslContext.class) && !isGroup(getContext())) {
                        new ModelItemParser().parseTags(getContext(ModelItemDslContext.class), tokens);

                    } else if ((TAGS_TOKEN.equalsIgnoreCase(firstToken) || TAG_TOKEN.equalsIgnoreCase(firstToken)) && inContext(ModelItemsDslContext.class)) {
                        new ModelItemsParser().parseTags(getContext(ModelItemsDslContext.class), tokens);

                    } else if (DESCRIPTION_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementDslContext.class) && !isGroup(getContext())) {
                        new ModelItemParser().parseDescription(getContext(ElementDslContext.class), tokens);

                    } else if (DESCRIPTION_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementsDslContext.class)) {
                        new ElementsParser().parseDescription(getContext(ElementsDslContext.class), tokens);

                    } else if (TECHNOLOGY_TOKEN.equalsIgnoreCase(firstToken) && inContext(ContainerDslContext.class) && !getContext(ContainerDslContext.class).hasGroup()) {
                        new ContainerParser().parseTechnology(getContext(ContainerDslContext.class), tokens);

                    } else if (TECHNOLOGY_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentDslContext.class) && !getContext(ComponentDslContext.class).hasGroup()) {
                        new ComponentParser().parseTechnology(getContext(ComponentDslContext.class), tokens);

                    } else if (TECHNOLOGY_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentNodeDslContext.class)) {
                        new DeploymentNodeParser().parseTechnology(getContext(DeploymentNodeDslContext.class), tokens);

                    } else if (TECHNOLOGY_TOKEN.equalsIgnoreCase(firstToken) && inContext(InfrastructureNodeDslContext.class)) {
                        new InfrastructureNodeParser().parseTechnology(getContext(InfrastructureNodeDslContext.class), tokens);

                    } else if (TECHNOLOGY_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementsDslContext.class)) {
                        new ElementsParser().parseTechnology(getContext(ElementsDslContext.class), tokens);

                    } else if (INSTANCES_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentNodeDslContext.class)) {
                        new DeploymentNodeParser().parseInstances(getContext(DeploymentNodeDslContext.class), tokens);

                    } else if (URL_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemDslContext.class) && !isGroup(getContext())) {
                        new ModelItemParser().parseUrl(getContext(ModelItemDslContext.class), tokens);

                    } else if (URL_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemsDslContext.class)) {
                        new ModelItemsParser().parseUrl(getContext(ModelItemsDslContext.class), tokens);

                    } else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(WorkspaceDslContext.class)) {
                        startContext(new PropertiesDslContext(workspace));

                    } else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelDslContext.class)) {
                        startContext(new PropertiesDslContext(workspace.getModel()));

                    } else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ConfigurationDslContext.class)) {
                        startContext(new PropertiesDslContext(getContext(ConfigurationDslContext.class).getWorkspace()));

                    } else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemDslContext.class) && !isGroup(getContext())) {
                        startContext(new PropertiesDslContext(getContext(ModelItemDslContext.class).getModelItem()));

                    } else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemsDslContext.class)) {
                        startContext(new PropertiesDslContext(getContext(ModelItemsDslContext.class).getModelItems().stream().map(mi -> (PropertyHolder)mi).toList()));

                    } else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        startContext(new PropertiesDslContext(workspace.getViews().getConfiguration()));

                    } else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewDslContext.class)) {
                        startContext(new PropertiesDslContext(getContext(ViewDslContext.class).getView()));

                    } else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(DynamicViewRelationshipContext.class)) {
                        startContext(new PropertiesDslContext(getContext((DynamicViewRelationshipContext.class)).getRelationshipView()));

                    } else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        startContext(new PropertiesDslContext(getContext((ElementStyleDslContext.class)).getStyle()));

                    } else if (PROPERTIES_TOKEN.equalsIgnoreCase(firstToken) && inContext(RelationshipStyleDslContext.class)) {
                        startContext(new PropertiesDslContext(getContext((RelationshipStyleDslContext.class)).getStyle()));

                    } else if (inContext(PropertiesDslContext.class)) {
                        new PropertyParser().parse(getContext(PropertiesDslContext.class), tokens);

                    } else if (PERSPECTIVES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemDslContext.class) && !isGroup(getContext())) {
                        startContext(new PerspectivesDslContext(getContext(ModelItemDslContext.class).getModelItem()));

                    } else if (PERSPECTIVES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemsDslContext.class)) {
                        startContext(new PerspectivesDslContext(getContext(ModelItemsDslContext.class).getModelItems()));

                    } else if (inContext(PerspectivesDslContext.class)) {
                        new PerspectiveParser().parse(getContext(PerspectivesDslContext.class), tokens);

                    } else if (WORKSPACE_TOKEN.equalsIgnoreCase(firstToken) && contextStack.empty()) {
                        if (parsedTokens.contains(WORKSPACE_TOKEN)) {
                            throw new RuntimeException("Multiple workspaces are not permitted in a DSL definition");
                        }
                        DslParserContext dslParserContext = new DslParserContext(dslFile, restricted);
                        dslParserContext.setIdentifierRegister(identifiersRegister);

                        workspace = new WorkspaceParser().parse(dslParserContext, tokens.withoutContextStartToken());
                        extendingWorkspace = !workspace.getModel().isEmpty();
                        startContext(new WorkspaceDslContext());
                        parsedTokens.add(WORKSPACE_TOKEN);
                    } else if (IMPLIED_RELATIONSHIPS_TOKEN.equalsIgnoreCase(firstToken) || IMPLIED_RELATIONSHIPS_TOKEN.substring(1).equalsIgnoreCase(firstToken)) {
                        new ImpliedRelationshipsParser().parse(getContext(), tokens, dslFile, restricted);

                    } else if (NAME_TOKEN.equalsIgnoreCase(firstToken) && inContext(WorkspaceDslContext.class)) {
                        new WorkspaceParser().parseName(getContext(), tokens);

                    } else if (DESCRIPTION_TOKEN.equalsIgnoreCase(firstToken) && inContext(WorkspaceDslContext.class)) {
                        new WorkspaceParser().parseDescription(getContext(), tokens);

                    } else if (MODEL_TOKEN.equalsIgnoreCase(firstToken) && inContext(WorkspaceDslContext.class)) {
                        if (parsedTokens.contains(MODEL_TOKEN)) {
                            throw new RuntimeException("Multiple models are not permitted in a DSL definition");
                        }

                        startContext(new ModelDslContext());
                        parsedTokens.add(MODEL_TOKEN);

                    } else if (VIEWS_TOKEN.equalsIgnoreCase(firstToken) && inContext(WorkspaceDslContext.class)) {
                        if (parsedTokens.contains(VIEWS_TOKEN)) {
                            throw new RuntimeException("Multiple view sets are not permitted in a DSL definition");
                        }

                        startContext(new ViewsDslContext());
                        parsedTokens.add(VIEWS_TOKEN);

                    } else if (BRANDING_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        startContext(new BrandingDslContext(dslFile));

                    } else if (BRANDING_LOGO_TOKEN.equalsIgnoreCase(firstToken) && inContext(BrandingDslContext.class)) {
                        new BrandingParser().parseLogo(getContext(BrandingDslContext.class), tokens, restricted);

                    } else if (BRANDING_FONT_TOKEN.equalsIgnoreCase(firstToken) && inContext(BrandingDslContext.class)) {
                        new BrandingParser().parseFont(getContext(BrandingDslContext.class), tokens);

                    } else if (STYLES_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        startContext(new StylesDslContext());

                    } else if (ELEMENT_STYLE_TOKEN.equalsIgnoreCase(firstToken) && inContext(StylesDslContext.class)) {
                        ElementStyle elementStyle = new ElementStyleParser().parseElementStyle(getContext(), tokens.withoutContextStartToken());
                        startContext(new ElementStyleDslContext(elementStyle, dslFile));

                    } else if (ELEMENT_STYLE_BACKGROUND_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseBackground(getContext(ElementStyleDslContext.class), tokens);

                    } else if ((ELEMENT_STYLE_COLOUR_TOKEN.equalsIgnoreCase(firstToken) || ELEMENT_STYLE_COLOR_TOKEN.equalsIgnoreCase(firstToken)) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseColour(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_STROKE_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseStroke(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_STROKE_WIDTH_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseStrokeWidth(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_SHAPE_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseShape(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_BORDER_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseBorder(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_OPACITY_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseOpacity(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_WIDTH_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseWidth(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_HEIGHT_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseHeight(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_FONT_SIZE_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseFontSize(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_METADATA_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseMetadata(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_DESCRIPTION_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseDescription(getContext(ElementStyleDslContext.class), tokens);

                    } else if (ELEMENT_STYLE_ICON_TOKEN.equalsIgnoreCase(firstToken) && inContext(ElementStyleDslContext.class)) {
                        new ElementStyleParser().parseIcon(getContext(ElementStyleDslContext.class), tokens, restricted);

                    } else if (RELATIONSHIP_STYLE_TOKEN.equalsIgnoreCase(firstToken) && inContext(StylesDslContext.class)) {
                        RelationshipStyle relationshipStyle = new RelationshipStyleParser().parseRelationshipStyle(getContext(), tokens.withoutContextStartToken());
                        startContext(new RelationshipStyleDslContext(relationshipStyle));

                    } else if (RELATIONSHIP_STYLE_THICKNESS_TOKEN.equalsIgnoreCase(firstToken) && inContext(RelationshipStyleDslContext.class)) {
                        new RelationshipStyleParser().parseThickness(getContext(RelationshipStyleDslContext.class), tokens);

                    } else if ((RELATIONSHIP_STYLE_COLOUR_TOKEN.equalsIgnoreCase(firstToken) || RELATIONSHIP_STYLE_COLOR_TOKEN.equalsIgnoreCase(firstToken)) && inContext(RelationshipStyleDslContext.class)) {
                        new RelationshipStyleParser().parseColour(getContext(RelationshipStyleDslContext.class), tokens);

                    } else if (RELATIONSHIP_STYLE_DASHED_TOKEN.equalsIgnoreCase(firstToken) && inContext(RelationshipStyleDslContext.class)) {
                        new RelationshipStyleParser().parseDashed(getContext(RelationshipStyleDslContext.class), tokens);

                    } else if (RELATIONSHIP_STYLE_OPACITY_TOKEN.equalsIgnoreCase(firstToken) && inContext(RelationshipStyleDslContext.class)) {
                        new RelationshipStyleParser().parseOpacity(getContext(RelationshipStyleDslContext.class), tokens);

                    } else if (RELATIONSHIP_STYLE_WIDTH_TOKEN.equalsIgnoreCase(firstToken) && inContext(RelationshipStyleDslContext.class)) {
                        new RelationshipStyleParser().parseWidth(getContext(RelationshipStyleDslContext.class), tokens);

                    } else if (RELATIONSHIP_STYLE_FONT_SIZE_TOKEN.equalsIgnoreCase(firstToken) && inContext(RelationshipStyleDslContext.class)) {
                        new RelationshipStyleParser().parseFontSize(getContext(RelationshipStyleDslContext.class), tokens);

                    } else if (RELATIONSHIP_STYLE_POSITION_TOKEN.equalsIgnoreCase(firstToken) && inContext(RelationshipStyleDslContext.class)) {
                        new RelationshipStyleParser().parsePosition(getContext(RelationshipStyleDslContext.class), tokens);

                    } else if (RELATIONSHIP_STYLE_LINE_STYLE_TOKEN.equalsIgnoreCase(firstToken) && inContext(RelationshipStyleDslContext.class)) {
                        new RelationshipStyleParser().parseLineStyle(getContext(RelationshipStyleDslContext.class), tokens);

                    } else if (RELATIONSHIP_STYLE_ROUTING_TOKEN.equalsIgnoreCase(firstToken) && inContext(RelationshipStyleDslContext.class)) {
                        new RelationshipStyleParser().parseRouting(getContext(RelationshipStyleDslContext.class), tokens);

                    } else if (DEPLOYMENT_ENVIRONMENT_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelDslContext.class)) {
                        String environment = new DeploymentEnvironmentParser().parse(tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new DeploymentEnvironmentDslContext(environment));
                        }

                        registerIdentifier(identifier, new DeploymentEnvironment(environment));

                    } else if (DEPLOYMENT_GROUP_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentEnvironmentDslContext.class)) {
                        String group = new DeploymentGroupParser().parse(tokens.withoutContextStartToken());

                        registerIdentifier(identifier, new DeploymentGroup(group));

                    } else if (DEPLOYMENT_NODE_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentEnvironmentDslContext.class)) {
                        DeploymentNode deploymentNode = new DeploymentNodeParser().parse(getContext(DeploymentEnvironmentDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new DeploymentNodeDslContext(deploymentNode));
                        }

                        registerIdentifier(identifier, deploymentNode);
                    } else if (DEPLOYMENT_NODE_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentNodeDslContext.class)) {
                        DeploymentNode deploymentNode = new DeploymentNodeParser().parse(getContext(DeploymentNodeDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new DeploymentNodeDslContext(deploymentNode));
                        }

                        registerIdentifier(identifier, deploymentNode);
                    } else if (INFRASTRUCTURE_NODE_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentNodeDslContext.class)) {
                        InfrastructureNode infrastructureNode = new InfrastructureNodeParser().parse(getContext(DeploymentNodeDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new InfrastructureNodeDslContext(infrastructureNode));
                        }

                        registerIdentifier(identifier, infrastructureNode);

                    } else if (SOFTWARE_SYSTEM_INSTANCE_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentNodeDslContext.class)) {
                        SoftwareSystemInstance softwareSystemInstance = new SoftwareSystemInstanceParser().parse(getContext(DeploymentNodeDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new SoftwareSystemInstanceDslContext(softwareSystemInstance));
                        }

                        registerIdentifier(identifier, softwareSystemInstance);

                    } else if (CONTAINER_INSTANCE_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentNodeDslContext.class)) {
                        ContainerInstance containerInstance = new ContainerInstanceParser().parse(getContext(DeploymentNodeDslContext.class), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new ContainerInstanceDslContext(containerInstance));
                        }

                        registerIdentifier(identifier, containerInstance);

                    } else if (HEALTH_CHECK_TOKEN.equalsIgnoreCase(firstToken) && inContext(StaticStructureElementInstanceDslContext.class)) {
                        new HealthCheckParser().parse(getContext(StaticStructureElementInstanceDslContext.class), tokens.withoutContextStartToken());
                    } else if (CUSTOM_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        CustomView view = new CustomViewParser().parse(getContext(), tokens.withoutContextStartToken());
                        startContext(new CustomViewDslContext(view));

                    } else if (SYSTEM_LANDSCAPE_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        SystemLandscapeView view = new SystemLandscapeViewParser().parse(getContext(), tokens.withoutContextStartToken());
                        startContext(new SystemLandscapeViewDslContext(view));

                    } else if (SYSTEM_CONTEXT_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        SystemContextView view = new SystemContextViewParser().parse(getContext(), tokens.withoutContextStartToken());
                        startContext(new SystemContextViewDslContext(view));

                    } else if (CONTAINER_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        ContainerView view = new ContainerViewParser().parse(getContext(), tokens.withoutContextStartToken());
                        startContext(new ContainerViewDslContext(view));

                    } else if (COMPONENT_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        ComponentView view = new ComponentViewParser().parse(getContext(), tokens.withoutContextStartToken());
                        startContext(new ComponentViewDslContext(view));

                    } else if (DYNAMIC_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        DynamicView view = new DynamicViewParser().parse(getContext(), tokens.withoutContextStartToken());
                        startContext(new DynamicViewDslContext(view));

                    } else if (DEPLOYMENT_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        DeploymentView view = new DeploymentViewParser().parse(getContext(), tokens.withoutContextStartToken());
                        startContext(new DeploymentViewDslContext(view));

                    } else if (FILTERED_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        FilteredView view = new FilteredViewParser().parse(getContext(), tokens.withoutContextStartToken());

                        if (shouldStartContext(tokens)) {
                            startContext(new FilteredViewDslContext(view));
                        }

                    } else if (IMAGE_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        ImageView view = new ImageViewParser().parse(getContext(), tokens.withoutContextStartToken());
                        startContext(new ImageViewDslContext(view));

                    } else if (DslContext.CONTEXT_START_TOKEN.equalsIgnoreCase(firstToken) && inContext(DynamicViewDslContext.class)) {
                        startContext(new DynamicViewParallelSequenceDslContext(getContext(DynamicViewDslContext.class)));

                    } else if (INCLUDE_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(CustomViewDslContext.class)) {
                        new CustomViewContentParser().parseInclude(getContext(CustomViewDslContext.class), tokens);

                    } else if (EXCLUDE_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(CustomViewDslContext.class)) {
                        new CustomViewContentParser().parseExclude(getContext(CustomViewDslContext.class), tokens);

                    } else if (ANIMATION_STEP_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(CustomViewDslContext.class)) {
                        new CustomViewAnimationStepParser().parse(getContext(CustomViewDslContext.class), tokens);

                    } else if (ANIMATION_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(CustomViewDslContext.class)) {
                        startContext(new CustomViewAnimationDslContext(getContext(CustomViewDslContext.class).getCustomView()));

                    } else if (inContext(CustomViewAnimationDslContext.class)) {
                        new CustomViewAnimationStepParser().parse(getContext(CustomViewAnimationDslContext.class), tokens);

                    } else if (INCLUDE_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(StaticViewDslContext.class)) {
                        new StaticViewContentParser().parseInclude(getContext(StaticViewDslContext.class), tokens);

                    } else if (EXCLUDE_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(StaticViewDslContext.class)) {
                        new StaticViewContentParser().parseExclude(getContext(StaticViewDslContext.class), tokens);

                    } else if (ANIMATION_STEP_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(StaticViewDslContext.class)) {
                        new StaticViewAnimationStepParser().parse(getContext(StaticViewDslContext.class), tokens);

                    } else if (ANIMATION_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(StaticViewDslContext.class)) {
                        startContext(new StaticViewAnimationDslContext(getContext(StaticViewDslContext.class).getView()));

                    } else if (inContext(StaticViewAnimationDslContext.class)) {
                        new StaticViewAnimationStepParser().parse(getContext(StaticViewAnimationDslContext.class), tokens);

                    } else if (INCLUDE_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentViewDslContext.class)) {
                        new DeploymentViewContentParser().parseInclude(getContext(DeploymentViewDslContext.class), tokens);

                    } else if (EXCLUDE_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentViewDslContext.class)) {
                        new DeploymentViewContentParser().parseExclude(getContext(DeploymentViewDslContext.class), tokens);

                    } else if (ANIMATION_STEP_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentViewDslContext.class)) {
                        new DeploymentViewAnimationStepParser().parse(getContext(DeploymentViewDslContext.class), tokens);

                    } else if (ANIMATION_IN_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentViewDslContext.class)) {
                        startContext(new DeploymentViewAnimationDslContext(getContext(DeploymentViewDslContext.class).getView()));

                    } else if (inContext(DeploymentViewAnimationDslContext.class)) {
                        new DeploymentViewAnimationStepParser().parse(getContext(DeploymentViewAnimationDslContext.class), tokens);

                    } else if (AUTOLAYOUT_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewDslContext.class)) {
                        new AutoLayoutParser().parse(getContext(ModelViewDslContext.class), tokens);

                    } else if (DEFAULT_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewDslContext.class)) {
                        new DefaultViewParser().parse(getContext(ViewDslContext.class));

                    } else if (VIEW_TITLE_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewDslContext.class)) {
                        new ViewParser().parseTitle(getContext(ViewDslContext.class), tokens);

                    } else if (VIEW_DESCRIPTION_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewDslContext.class)) {
                        new ViewParser().parseDescription(getContext(ViewDslContext.class), tokens);

                    } else if (PLANTUML_TOKEN.equalsIgnoreCase(firstToken) && inContext(ImageViewDslContext.class)) {
                        new ImageViewContentParser(restricted).parsePlantUML(getContext(ImageViewDslContext.class), dslFile, tokens);

                    } else if (MERMAID_TOKEN.equalsIgnoreCase(firstToken) && inContext(ImageViewDslContext.class)) {
                        new ImageViewContentParser(restricted).parseMermaid(getContext(ImageViewDslContext.class), dslFile, tokens);

                    } else if (KROKI_TOKEN.equalsIgnoreCase(firstToken) && inContext(ImageViewDslContext.class)) {
                        new ImageViewContentParser(restricted).parseKroki(getContext(ImageViewDslContext.class), dslFile, tokens);

                    } else if (IMAGE_VIEW_TOKEN.equalsIgnoreCase(firstToken) && inContext(ImageViewDslContext.class)) {
                        new ImageViewContentParser(restricted).parseImage(getContext(ImageViewDslContext.class), dslFile, tokens);

                    } else if (inContext(DynamicViewDslContext.class)) {
                        RelationshipView relationshipView = new DynamicViewContentParser().parseRelationship(getContext(DynamicViewDslContext.class), tokens);

                        if (inContext(DynamicViewParallelSequenceDslContext.class)) {
                            getContext(DynamicViewParallelSequenceDslContext.class).hasRelationships(true);
                        }

                        if (shouldStartContext(tokens)) {
                            startContext(new DynamicViewRelationshipContext(relationshipView));
                        }

                    } else if (URL_TOKEN.equalsIgnoreCase(firstToken) && inContext(DynamicViewRelationshipContext.class)) {
                        new DynamicViewRelationshipParser().parseUrl(getContext(DynamicViewRelationshipContext.class), tokens.withoutContextStartToken());

                    } else if (THEME_TOKEN.equalsIgnoreCase(firstToken) && (inContext(ViewsDslContext.class) || inContext(StylesDslContext.class))) {
                        new ThemeParser().parseTheme(getContext(), dslFile, tokens);

                    } else if (THEMES_TOKEN.equalsIgnoreCase(firstToken) && (inContext(ViewsDslContext.class) || inContext(StylesDslContext.class))) {
                        new ThemeParser().parseThemes(getContext(), dslFile, tokens);

                    } else if (TERMINOLOGY_TOKEN.equalsIgnoreCase(firstToken) && inContext(ViewsDslContext.class)) {
                        startContext(new TerminologyDslContext());

                    } else if (PERSON_TOKEN.equalsIgnoreCase(firstToken) && inContext(TerminologyDslContext.class)) {
                        new TerminologyParser().parsePerson(getContext(), tokens);

                    } else if (SOFTWARE_SYSTEM_TOKEN.equalsIgnoreCase(firstToken) && inContext(TerminologyDslContext.class)) {
                        new TerminologyParser().parseSoftwareSystem(getContext(), tokens);

                    } else if (CONTAINER_TOKEN.equalsIgnoreCase(firstToken) && inContext(TerminologyDslContext.class)) {
                        new TerminologyParser().parseContainer(getContext(), tokens);

                    } else if (COMPONENT_TOKEN.equalsIgnoreCase(firstToken) && inContext(TerminologyDslContext.class)) {
                        new TerminologyParser().parseComponent(getContext(), tokens);

                    } else if (DEPLOYMENT_NODE_TOKEN.equalsIgnoreCase(firstToken) && inContext(TerminologyDslContext.class)) {
                        new TerminologyParser().parseDeploymentNode(getContext(), tokens);

                    } else if (INFRASTRUCTURE_NODE_TOKEN.equalsIgnoreCase(firstToken) && inContext(TerminologyDslContext.class)) {
                        new TerminologyParser().parseInfrastructureNode(getContext(), tokens);

                    } else if (TERMINOLOGY_RELATIONSHIP_TOKEN.equalsIgnoreCase(firstToken) && inContext(TerminologyDslContext.class)) {
                        new TerminologyParser().parseRelationship(getContext(), tokens);

                    } else if (CONFIGURATION_TOKEN.equalsIgnoreCase(firstToken) && inContext(WorkspaceDslContext.class)) {
                        startContext(new ConfigurationDslContext());

                    } else if (SCOPE_TOKEN.equalsIgnoreCase(firstToken) && inContext(ConfigurationDslContext.class)) {
                        new ConfigurationParser().parseScope(getContext(), tokens);

                    } else if (VISIBILITY_TOKEN.equalsIgnoreCase(firstToken) && inContext(ConfigurationDslContext.class)) {
                        new ConfigurationParser().parseVisibility(getContext(), tokens);

                    } else if (USERS_TOKEN.equalsIgnoreCase(firstToken) && inContext(ConfigurationDslContext.class)) {
                        startContext(new UsersDslContext());

                    } else if (inContext(UsersDslContext.class)) {
                        new UserRoleParser().parse(getContext(), tokens);

                    } else if (DOCS_TOKEN.equalsIgnoreCase(firstToken) && inContext(WorkspaceDslContext.class)) {
                        if (!restricted) {
                            new DocsParser().parse(getContext(WorkspaceDslContext.class), dslFile, tokens);
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if (DOCS_TOKEN.equalsIgnoreCase(firstToken) && inContext(SoftwareSystemDslContext.class)) {
                        if (!restricted) {
                            new DocsParser().parse(getContext(SoftwareSystemDslContext.class), dslFile, tokens);
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if (DOCS_TOKEN.equalsIgnoreCase(firstToken) && inContext(ContainerDslContext.class)) {
                        if (!restricted) {
                            new DocsParser().parse(getContext(ContainerDslContext.class), dslFile, tokens);
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if (DOCS_TOKEN.equalsIgnoreCase(firstToken) && inContext(ComponentDslContext.class)) {
                        if (!restricted) {
                            new DocsParser().parse(getContext(ComponentDslContext.class), dslFile, tokens);
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if ((ADRS_TOKEN.equalsIgnoreCase(firstToken) || DECISIONS_TOKEN.equalsIgnoreCase(firstToken)) && inContext(WorkspaceDslContext.class)) {
                        if (!restricted) {
                            new DecisionsParser().parse(getContext(WorkspaceDslContext.class), dslFile, tokens);
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if ((ADRS_TOKEN.equalsIgnoreCase(firstToken) || DECISIONS_TOKEN.equalsIgnoreCase(firstToken)) && inContext(SoftwareSystemDslContext.class)) {
                        if (!restricted) {
                            new DecisionsParser().parse(getContext(SoftwareSystemDslContext.class), dslFile, tokens);
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if ((ADRS_TOKEN.equalsIgnoreCase(firstToken) || DECISIONS_TOKEN.equalsIgnoreCase(firstToken)) && inContext(ContainerDslContext.class)) {
                        if (!restricted) {
                            new DecisionsParser().parse(getContext(ContainerDslContext.class), dslFile, tokens);
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if ((ADRS_TOKEN.equalsIgnoreCase(firstToken) || DECISIONS_TOKEN.equalsIgnoreCase(firstToken)) && inContext(ComponentDslContext.class)) {
                        if (!restricted) {
                            new DecisionsParser().parse(getContext(ComponentDslContext.class), dslFile, tokens);
                        } else {
                            throwRestrictedModeException(firstToken);
                        }

                    } else if (CONSTANT_TOKEN.equalsIgnoreCase(firstToken)) {
                        throw new RuntimeException("!constant was previously deprecated, and has now been removed - please use !const or !var instead");

                    } else if (CONST_TOKEN.equalsIgnoreCase(firstToken)) {
                        NameValuePair nameValuePair = new NameValueParser().parseConstant(tokens);
                        try {
                            addConstant(nameValuePair);
                        } catch (IllegalArgumentException e) {
                            throw new StructurizrDslParserException(e.getMessage());
                        }

                    } else if (VAR_TOKEN.equalsIgnoreCase(firstToken)) {
                        NameValuePair nameValuePair = new NameValueParser().parseVariable(tokens);

                        if (constantsAndVariables.containsKey(nameValuePair.getName()) && constantsAndVariables.get(nameValuePair.getName()).getType() == NameValueType.Constant) {
                            throw new StructurizrDslParserException("A constant \"" + nameValuePair.getName() + "\" already exists");
                        }
                        constantsAndVariables.put(nameValuePair.getName(), nameValuePair);

                    } else if (IDENTIFIERS_TOKEN.equalsIgnoreCase(firstToken) && (inContext(WorkspaceDslContext.class) || inContext(ModelDslContext.class))) {
                        setIdentifierScope(new IdentifierScopeParser().parse(getContext(), tokens));

                    } else {
                        String[] expectedTokens;
                        if (getContext() == null) {
                            if (getWorkspace() == null) {
                                // the workspace hasn't yet been created
                                expectedTokens = new String[]{
                                        StructurizrDslTokens.WORKSPACE_TOKEN
                                };
                            } else {
                                expectedTokens = new String[0];
                            }
                        } else {
                            expectedTokens = getContext().getPermittedTokens();
                        }

                        if (expectedTokens.length > 0) {
                            StringBuilder buf = new StringBuilder();
                            for (String expectedToken : expectedTokens) {
                                buf.append(expectedToken);
                                buf.append(", ");
                            }
                            throw new StructurizrDslParserException("Unexpected tokens (expected: " + buf.substring(0, buf.length() - 2) + ")");
                        } else {
                            throw new StructurizrDslParserException("Unexpected tokens");
                        }
                    }
                }

                if (includeInDslSourceLines && lineForDslSource != null) {
                    dslSourceLines.add(lineForDslSource);
                }
            } catch (Exception e) {
                if (e.getMessage() != null) {
                    throw new StructurizrDslParserException(e.getMessage(), dslFile, dslLine.getLineNumber(), line);
                } else {
                    throw new StructurizrDslParserException(e.getClass().getSimpleName(), dslFile, dslLine.getLineNumber(), line);
                }
            }
        }

        if (!fragment && !contextStack.empty()) {
            throw new StructurizrDslParserException("Unexpected end of DSL content - are one or more closing curly braces missing?");
        }
    }

    private List preProcessLines(List lines) {
        List dslLines = new ArrayList<>();

        int lineNumber = 1;
        StringBuilder buf = new StringBuilder();
        boolean lineComplete = true;

        for (String line : lines) {
            if (!COMMENT_PATTERN.matcher(line).matches() && line.endsWith(MULTI_LINE_SEPARATOR)) {
                buf.append(line, 0, line.length()-1);
                lineComplete = false;
            } else {
                if (lineComplete) {
                    buf.append(line);
                } else {
                    buf.append(line.stripLeading());
                    lineComplete = true;
                }
            }

            if (lineComplete) {
                dslLines.add(new DslLine(buf.toString(), lineNumber));
                buf = new StringBuilder();
            }

            lineNumber++;
        }

        return dslLines;
    }

    private void throwRestrictedModeException(String firstToken) {
        throw new RuntimeException(firstToken + " is not available when the parser is running in restricted mode");
    }

    private String substituteStrings(String token) {
        Matcher m = STRING_SUBSTITUTION_PATTERN.matcher(token);
        while (m.find()) {
            String before = m.group(0);
            String after = null;
            String name = before.substring(2, before.length()-1);
            if (constantsAndVariables.containsKey(name)) {
                after = constantsAndVariables.get(name).getValue();
            } else {
                if (!restricted) {
                    String environmentVariable = System.getenv().get(name);
                    if (environmentVariable != null) {
                        after = environmentVariable;
                    }
                }
            }

            if (after != null) {
                token = token.replace(before, after);
            }
        }

        return token;
    }

    private boolean shouldStartContext(Tokens tokens) {
        return DslContext.CONTEXT_START_TOKEN.equalsIgnoreCase(tokens.get(tokens.size()-1));
    }

    private void startContext(DslContext context) {
        context.setWorkspace(workspace);
        context.setIdentifierRegister(identifiersRegister);
        context.setExtendingWorkspace(extendingWorkspace);
        contextStack.push(context);
    }

    private DslContext getContext() {
        if (!contextStack.empty()) {
            return contextStack.peek();
        } else {
            return null;
        }
    }

    private  T getContext(Class clazz) throws StructurizrDslParserException {
        if (inContext(clazz)) {
            return (T)contextStack.peek();
        } else {
            throw new StructurizrDslParserException("Expected " + clazz.getName() + " but got " + contextStack.peek().getClass().getName());
        }
    }

    private void endContext() throws StructurizrDslParserException {
        if (!contextStack.empty()) {
            DslContext context = contextStack.pop();
            context.end();
        } else {
            throw new StructurizrDslParserException("Unexpected end of context");
        }
    }

    private boolean isGroup(DslContext context) {
        if (context instanceof GroupableDslContext) {
            return ((GroupableDslContext)context).hasGroup();
        }

        return false;
    }

    /**
     * Gets the identifier register in use (this is the mapping of DSL identifiers to elements/relationships).
     *
     * @return      an IdentifiersRegister object
     */
    public IdentifiersRegister getIdentifiersRegister() {
        return identifiersRegister;
    }

    void registerIdentifier(String identifier, Element element) {
        identifiersRegister.register(identifier, element);
        element.addProperty(STRUCTURIZR_DSL_IDENTIFIER_PROPERTY_NAME, identifiersRegister.findIdentifier(element));
    }

    void registerIdentifier(String identifier, Relationship relationship) {
        identifiersRegister.register(identifier, relationship);

        if (!StringUtils.isNullOrEmpty(identifier)) {
            relationship.addProperty(STRUCTURIZR_DSL_IDENTIFIER_PROPERTY_NAME, identifiersRegister.findIdentifier(relationship));
        }
    }

    /**
     * Gets the named constant.
     *
     * @param name      the name of the constant
     * @return  the value, or an empty string if the named constant doesn't exist
     */
    public String getConstant(String name) {
        NameValuePair nameValuePair = constantsAndVariables.get(name);
        if (nameValuePair != null) {
            return nameValuePair.getValue();
        } else {
            return "";
        }
    }

    /**
     * Adds a constant to the parser.
     * @param name      the name of the constant
     * @param value     the value of the constant
     */
    public void addConstant(String name, String value) {
        if (StringUtils.isNullOrEmpty(name)) {
            throw new IllegalArgumentException("A constant name must be specified");
        }

        addConstant(new NameValuePair(name, value));
    }

    private void addConstant(NameValuePair nameValuePair) {
        if (constantsAndVariables.containsKey(nameValuePair.getName())) {
            throw new IllegalArgumentException("A constant/variable \"" + nameValuePair.getName() + "\" already exists");
        }
        constantsAndVariables.put(nameValuePair.getName(), nameValuePair);
    }

    private boolean inContext(Class clazz) {
        if (contextStack.empty()) {
            return false;
        }

        return clazz.isAssignableFrom(contextStack.peek().getClass());
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy