Package com.asakusafw.dmdl.analyzer

Source Code of com.asakusafw.dmdl.analyzer.DmdlAnalyzer

/**
* Copyright 2011-2014 Asakusa Framework Team.
*
* 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.
*/
package com.asakusafw.dmdl.analyzer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asakusafw.dmdl.Diagnostic;
import com.asakusafw.dmdl.Diagnostic.Level;
import com.asakusafw.dmdl.model.AstAttribute;
import com.asakusafw.dmdl.model.AstExpression;
import com.asakusafw.dmdl.model.AstGrouping;
import com.asakusafw.dmdl.model.AstJoin;
import com.asakusafw.dmdl.model.AstModelDefinition;
import com.asakusafw.dmdl.model.AstModelFolding;
import com.asakusafw.dmdl.model.AstModelMapping;
import com.asakusafw.dmdl.model.AstModelReference;
import com.asakusafw.dmdl.model.AstName;
import com.asakusafw.dmdl.model.AstNode.AbstractVisitor;
import com.asakusafw.dmdl.model.AstPropertyDefinition;
import com.asakusafw.dmdl.model.AstPropertyFolding;
import com.asakusafw.dmdl.model.AstPropertyMapping;
import com.asakusafw.dmdl.model.AstRecord;
import com.asakusafw.dmdl.model.AstRecordDefinition;
import com.asakusafw.dmdl.model.AstSimpleName;
import com.asakusafw.dmdl.model.AstSummarize;
import com.asakusafw.dmdl.model.AstTerm;
import com.asakusafw.dmdl.model.AstUnionExpression;
import com.asakusafw.dmdl.model.ModelDefinitionKind;
import com.asakusafw.dmdl.semantics.Declaration;
import com.asakusafw.dmdl.semantics.DmdlSemantics;
import com.asakusafw.dmdl.semantics.ModelDeclaration;
import com.asakusafw.dmdl.semantics.ModelSymbol;
import com.asakusafw.dmdl.semantics.PropertyDeclaration;
import com.asakusafw.dmdl.semantics.PropertyMappingKind;
import com.asakusafw.dmdl.semantics.PropertySymbol;
import com.asakusafw.dmdl.semantics.Type;
import com.asakusafw.dmdl.semantics.trait.JoinTrait;
import com.asakusafw.dmdl.semantics.trait.MappingFactor;
import com.asakusafw.dmdl.semantics.trait.ProjectionsTrait;
import com.asakusafw.dmdl.semantics.trait.ReduceTerm;
import com.asakusafw.dmdl.semantics.trait.SummarizeTrait;
import com.asakusafw.dmdl.spi.AttributeDriver;
import com.asakusafw.dmdl.spi.TypeDriver;
import com.asakusafw.utils.collections.Lists;
import com.asakusafw.utils.collections.Maps;
import com.asakusafw.utils.collections.Sets;
import com.asakusafw.utils.graph.Graph;
import com.asakusafw.utils.graph.Graphs;

/**
* Analyzes DMDL AST and builds DMDL semantic models.
*/
public class DmdlAnalyzer {

    static final Logger LOG = LoggerFactory.getLogger(DmdlAnalyzer.class);

    final Context context;

    private final Graph<String> modelDependencies;

    /**
     * Creates and returns a new instance.
     * @param typeDrivers type resolvers
     * @param attributeDrivers attributed analyzers
     * @throws IllegalArgumentException if some parameters were {@code null}
     */
    public DmdlAnalyzer(
            Iterable<? extends TypeDriver> typeDrivers,
            Iterable<? extends AttributeDriver> attributeDrivers) {
        if (typeDrivers == null) {
            throw new IllegalArgumentException("typeDrivers must not be null"); //$NON-NLS-1$
        }
        if (attributeDrivers == null) {
            throw new IllegalArgumentException("attributeDrivers must not be null"); //$NON-NLS-1$
        }
        this.context = new Context(new DmdlSemantics(), typeDrivers, attributeDrivers);
        this.modelDependencies = Graphs.newInstance();
    }

    void report(Diagnostic diagnostic) {
        assert diagnostic != null;
        context.getWorld().report(diagnostic);
    }

    /**
     * Addes a model definition to this analyzer.
     * @param definition the model definition
     * @throws IllegalArgumentException if some parameters were {@code null}
     */
    public void addModel(AstModelDefinition<?> definition) {
        if (definition == null) {
            throw new IllegalArgumentException("definition must not be null"); //$NON-NLS-1$
        }
        DmdlSemantics world = context.getWorld();
        if (world.findModelDeclaration(definition.name.identifier) != null) {
            report(new Diagnostic(
                    Diagnostic.Level.ERROR,
                    definition.name,
                    Messages.getString("DmdlAnalyzer.diagnosticModelDuplicated"), //$NON-NLS-1$
                    definition.name.identifier));
        } else {
            world.declareModel(
                    definition,
                    definition.name,
                    definition.description,
                    definition.attributes);
            computeDependencies(definition);
        }
    }

    private void computeDependencies(AstModelDefinition<?> definition) {
        assert definition != null;
        LOG.debug("Computing dependencies: {}", definition.name); //$NON-NLS-1$
        Set<AstSimpleName> references = Sets.create();
        definition.expression.accept(references, ModelSymbolCollector.INSTANCE);
        modelDependencies.addNode(definition.name.identifier);
        for (AstSimpleName target : references) {
            modelDependencies.addEdge(definition.name.identifier, target.identifier);
        }
    }

    /**
     * Resolves the all added definitions and returns the root semantics model.
     * @return the analyzed semantics model
     * @throws DmdlSemanticException If registered models has some errors
     */
    public synchronized DmdlSemantics resolve() throws DmdlSemanticException {
        checkDiagnostics();
        resolveSymbols();
        checkDiagnostics();
        resolveAttributes();
        checkDiagnostics();
        verifyAttributes();
        checkDiagnostics();
        return context.getWorld();
    }

    private void checkDiagnostics() throws DmdlSemanticException {
        if (context.getWorld().hasError()) {
            throw new DmdlSemanticException(
                    Messages.getString("DmdlAnalyzer.errorSemantics"), //$NON-NLS-1$
                    context.getWorld().getDiagnostics());
        }
    }

    private void resolveSymbols() {
        LOG.debug("Resolving symbols"); //$NON-NLS-1$
        Set<Set<String>> circuits = Graphs.findCircuit(modelDependencies);
        if (circuits.isEmpty() == false) {
            for (Set<String> loop : circuits) {
                for (String modelName : loop) {
                    AstSimpleName node = null;
                    ModelDeclaration md = context.getWorld().findModelDeclaration(modelName);
                    if (md != null) {
                        node = md.getName();
                    }
                    report(new Diagnostic(
                            Level.ERROR,
                            node,
                            Messages.getString("DmdlAnalyzer.diagnosticCyclicDependencies"), //$NON-NLS-1$
                            modelName,
                            loop));
                }
            }
            return;
        }

        DmdlSemantics world = context.getWorld();
        for (String name : Graphs.sortPostOrder(modelDependencies)) {
            ModelDeclaration model = world.findModelDeclaration(name);
            if (model == null) {
                // means "model have not been declared, but is referenced."
                continue;
            }
            resolveModelSymbol(model);
        }
    }

    private void resolveModelSymbol(ModelDeclaration model) {
        assert model != null;
        AstModelDefinition<?> definition = model.getOriginalAst();
        LOG.debug("Resolving model definition: {}", definition.name); //$NON-NLS-1$
        switch (definition.kind) {
        case RECORD:
            resolveRecord(model, definition.asRecord().expression);
            break;
        case PROJECTIVE:
            resolveRecord(model, definition.asProjective().expression);
            break;
        case JOINED:
            resolveJoined(model, definition.asJoined().expression);
            break;
        case SUMMARIZED:
            resolveSummarize(model, definition.asSummarized().expression);
            break;
        default:
            throw new AssertionError(definition.kind);
        }
    }

    private void resolveRecord(
            ModelDeclaration model,
            AstExpression<AstRecord> expression) {
        assert model != null;
        assert expression != null;
        LOG.debug("Resolving record: {}", model.getName()); //$NON-NLS-1$
        RecordExpressionResolver resolver = new RecordExpressionResolver();
        expression.accept(model, resolver);
        ProjectionsTrait projections = new ProjectionsTrait(expression, resolver.projections);
        LOG.debug("Record {} has projections: {}", model.getName(), projections.getProjections()); //$NON-NLS-1$
        model.putTrait(ProjectionsTrait.class, projections);
    }

    private void resolveJoined(
            ModelDeclaration model,
            AstExpression<AstJoin> expression) {
        assert model != null;
        assert expression != null;
        LOG.debug("Resolving joined: {}", model.getName()); //$NON-NLS-1$
        List<ReduceTerm<AstJoin>> results = Lists.create();
        List<AstJoin> terms = extract(expression);
        if (terms.size() >= 3) {
            report(new Diagnostic(
                    Level.ERROR,
                    expression,
                    Messages.getString("DmdlAnalyzer.diagnosticTooManyJoinTerms"))); //$NON-NLS-1$
        }
        for (AstJoin term : terms) {
            LOG.debug("Resolving joined term: {} -> {}", model.getName(), term.reference.name); //$NON-NLS-1$
            ModelSymbol source = context.getWorld().createModelSymbol(term.reference.name);
            if (source.findDeclaration() == null) {
                report(new Diagnostic(
                        Level.ERROR,
                        term.reference,
                        Messages.getString("DmdlAnalyzer.diagnosticMissingJoinModel"), //$NON-NLS-1$
                        term.reference.name));
                continue;
            }
            Map<String, PropertyDeclaration> properties = resolveJoinProperties(model, source, term);
            List<MappingFactor> mappings = resolveMapping(model, source, term.mapping);
            List<PropertySymbol> grouping = resolveGrouping(model, properties, term.grouping);
            results.add(new ReduceTerm<AstJoin>(term, source, mappings, grouping));
        }
        if (context.getWorld().hasError() == false && checkJoinTerms(model, results)) {
            model.putTrait(JoinTrait.class, new JoinTrait(expression, results));
        }
    }

    private Map<String, PropertyDeclaration> resolveJoinProperties(
            ModelDeclaration model,
            ModelSymbol sourceModel,
            AstJoin term) {
        assert model != null;
        assert sourceModel != null;
        assert term != null;
        LOG.debug("processing model mapping: {}", term.mapping); //$NON-NLS-1$

        Set<String> groupingPropertyNames = Sets.create();
        if (term.grouping != null) {
            for (AstSimpleName name : term.grouping.properties) {
                groupingPropertyNames.add(name.identifier);
            }
        }
        ModelDeclaration sourceDecl = sourceModel.findDeclaration();
        assert sourceDecl != null;
        Map<String, PropertyDeclaration> results = new HashMap<String, PropertyDeclaration>();
        if (term.mapping == null) {
            for (PropertyDeclaration prop : sourceDecl.getDeclaredProperties()) {
                PropertyDeclaration declared = model.findPropertyDeclaration(prop.getName().identifier);
                if (declared != null) {
                    LOG.debug("property {} is duplicated", prop.getSymbol()); //$NON-NLS-1$
                    results.put(declared.getName().identifier, declared);
                } else {
                    declared = model.declareProperty(
                            sourceModel.getName(),
                            prop.getName(),
                            prop.getType(),
                            prop.getDescription(),
                            prop.getAttributes());
                    results.put(declared.getName().identifier, declared);
                }
            }
        } else {
            Set<String> saw = Sets.create();
            for (AstPropertyMapping property : term.mapping.properties) {
                if (saw.contains(property.target.identifier)) {
                    report(new Diagnostic(
                            Level.ERROR,
                            property,
                            Messages.getString("DmdlAnalyzer.diagnosticDuplicatedJoinMappingProperty"), //$NON-NLS-1$
                            property.target.identifier));
                    continue;
                }
                saw.add(property.target.identifier);
                PropertyDeclaration sourceProp = sourceDecl.findPropertyDeclaration(property.source.identifier);
                if (sourceProp == null) {
                    report(new Diagnostic(
                            Level.ERROR,
                            sourceModel.getName(),
                            Messages.getString("DmdlAnalyzer.diagnosticMissingJoinProperty"), //$NON-NLS-1$
                            property.source.identifier,
                            sourceModel.getName().identifier));
                    continue;
                }
                PropertyDeclaration declared = model.findPropertyDeclaration(property.target.identifier);
                if (declared != null) {
                    LOG.debug("property {} is duplicated", property.target); //$NON-NLS-1$
                    results.put(declared.getName().identifier, declared);
                } else {
                    declared = model.declareProperty(
                            property,
                            property.target,
                            sourceProp.getType(),
                            property.description,
                            property.attributes);
                    results.put(declared.getName().identifier, declared);
                }
            }
        }
        return results;
    }

    private List<MappingFactor> resolveMapping(ModelDeclaration model, ModelSymbol source, AstModelMapping mapping) {
        assert model != null;
        assert source != null;
        ModelDeclaration sourceModel = source.findDeclaration();
        assert sourceModel != null;
        List<MappingFactor> results = Lists.create();
        if (mapping == null) {
            for (PropertyDeclaration property : sourceModel.getDeclaredProperties()) {
                PropertyDeclaration targetProperty = model.findPropertyDeclaration(property.getName().identifier);
                if (targetProperty != null) {
                    results.add(new MappingFactor(
                            source.getName(),
                            PropertyMappingKind.ANY,
                            source.createPropertySymbol(property.getName()),
                            targetProperty.getSymbol()));
                }
            }
        } else {
            for (AstPropertyMapping propertyMapping : mapping.properties) {
                PropertyDeclaration targetProperty = model.findPropertyDeclaration(propertyMapping.target.identifier);
                if (targetProperty != null) {
                    results.add(new MappingFactor(
                            source.getName(),
                            PropertyMappingKind.ANY,
                            source.createPropertySymbol(propertyMapping.source),
                            targetProperty.getSymbol()));
                }
            }
        }
        return results;
    }

    private boolean checkJoinTerms(ModelDeclaration model, List<ReduceTerm<AstJoin>> terms) {
        assert model != null;
        assert terms != null;
        if (checkGrouping(model, terms) == false) {
            return false;
        }
        boolean green = true;
        Map<String, Type> typeMap = Maps.create();
        for (ReduceTerm<AstJoin> term : terms) {
            Set<String> groupingProperties = Sets.create();
            for (PropertySymbol grouping : term.getGrouping()) {
                groupingProperties.add(grouping.getName().identifier);
            }
            for (MappingFactor factor : term.getMappings()) {
                PropertySymbol target = factor.getTarget();
                Type declared = typeMap.get(target.getName().identifier);
                if (declared == null) {
                    typeMap.put(target.getName().identifier, target.findDeclaration().getType());
                } else if (groupingProperties.contains(target.getName().identifier) == false) {
                    report(new Diagnostic(
                            Level.ERROR,
                            term.getOriginalAst(),
                            Messages.getString("DmdlAnalyzer.diagnosticDuplicatedJoinGroupingProperty"), //$NON-NLS-1$
                            target.getName().identifier));
                    green = false;
                }
            }
        }
        return green;
    }

    private void resolveSummarize(
            ModelDeclaration model,
            AstExpression<AstSummarize> expression) {
        assert model != null;
        assert expression != null;
        LOG.debug("Resolving summarized: {}", model.getName()); //$NON-NLS-1$
        List<ReduceTerm<AstSummarize>> results = Lists.create();
        for (AstSummarize term : extract(expression)) {
            LOG.debug("Resolving summarized term: {} -> {}", model.getName(), term.reference.name); //$NON-NLS-1$
            ModelSymbol source = context.getWorld().createModelSymbol(term.reference.name);
            if (source.findDeclaration() == null) {
                report(new Diagnostic(
                        Level.ERROR,
                        term.reference,
                        Messages.getString("DmdlAnalyzer.diagnosticMissingSummarizeModel"), //$NON-NLS-1$
                        term.reference.name));
                continue;
            }
            Map<String, PropertyDeclaration> properties = resolveSummarizeProperties(model, source, term);
            List<MappingFactor> foldings = resolveFolding(model, source, term.folding);
            List<PropertySymbol> grouping = resolveGrouping(model, properties, term.grouping);
            results.add(new ReduceTerm<AstSummarize>(term, source, foldings, grouping));
        }
        if (checkSummarizeTerms(model, results)) {
            model.putTrait(SummarizeTrait.class, new SummarizeTrait(expression, results));
        }
    }

    private Map<String, PropertyDeclaration> resolveSummarizeProperties(
            ModelDeclaration model, ModelSymbol source, AstSummarize term) {
        assert model != null;
        assert source != null;
        assert term != null;
        LOG.debug("processing model folding: {}", term.folding); //$NON-NLS-1$

        ModelDeclaration decl = source.findDeclaration();
        assert decl != null;
        Map<String, PropertyDeclaration> results = new HashMap<String, PropertyDeclaration>();
        for (AstPropertyFolding property : term.folding.properties) {
            PropertyDeclaration original = decl.findPropertyDeclaration(property.source.identifier);
            if (original == null) {
                report(new Diagnostic(
                        Level.ERROR,
                        source.getName(),
                        Messages.getString("DmdlAnalyzer.diagnosticMissingSummarizeFoldingProperty"), //$NON-NLS-1$
                        property.source.identifier,
                        source.getName().identifier));
                continue;
            }
            PropertyMappingKind mapping = resolveAggregateFunction(property.aggregator);
            if (mapping == null) {
                report(new Diagnostic(
                        Level.ERROR,
                        property.aggregator,
                        Messages.getString("DmdlAnalyzer.diagnosticMissingSummarizeAggregateFunction"), //$NON-NLS-1$
                        property.aggregator.toString()));
                continue;
            }
            Type resolved = original.getType().map(mapping);
            if (resolved == null) {
                report(new Diagnostic(
                        Level.ERROR,
                        property,
                        Messages.getString(
                                "DmdlAnalyzer.diagnosticInconsistentSummarizeAggregateFunction"), //$NON-NLS-1$
                        property.aggregator.toString(),
                        property.source.identifier,
                        original.getType()));
                continue;
            }
            PropertyDeclaration declared = model.findPropertyDeclaration(property.target.identifier);
            if (declared != null) {
                report(new Diagnostic(
                        Level.ERROR,
                        property.target,
                        Messages.getString("DmdlAnalyzer.diagnosticDuplicatedSummarizeProperty"), //$NON-NLS-1$
                        property.target.identifier));
                continue;
            }
            PropertyDeclaration result = model.declareProperty(
                    property,
                    property.target,
                    resolved,
                    property.description,
                    property.attributes);
            results.put(result.getName().identifier, result);
        }
        return results;
    }

    private List<MappingFactor> resolveFolding(ModelDeclaration model, ModelSymbol source, AstModelFolding folding) {
        assert model != null;
        assert source != null;
        assert folding != null;
        ModelDeclaration sourceModel = source.findDeclaration();
        assert sourceModel != null;
        List<MappingFactor> results = Lists.create();
        for (AstPropertyFolding propertyFolding : folding.properties) {
            PropertyDeclaration targetProperty = model.findPropertyDeclaration(propertyFolding.target.identifier);
            PropertyMappingKind mapping = resolveAggregateFunction(propertyFolding.aggregator);
            if (targetProperty != null && mapping != null) {
                results.add(new MappingFactor(
                        source.getName(),
                        mapping,
                        source.createPropertySymbol(propertyFolding.source),
                        targetProperty.getSymbol()));
            }
        }
        return results;
    }

    private boolean checkSummarizeTerms(ModelDeclaration model, List<ReduceTerm<AstSummarize>> terms) {
        assert model != null;
        assert terms != null;
        if (checkGrouping(model, terms) == false) {
            return false;
        }
        // always single term currently
        return terms.size() == 1;
    }

    private PropertyMappingKind resolveAggregateFunction(AstName aggregator) {
        assert aggregator != null;
        String name = aggregator.toString().toUpperCase();
        try {
            return PropertyMappingKind.valueOf(name);
        } catch (Exception e) {
            return null;
        }
    }

    private List<PropertySymbol> resolveGrouping(
            ModelDeclaration model,
            Map<String, PropertyDeclaration> properties,
            AstGrouping grouping) {
        assert model != null;
        if (grouping == null) {
            return Collections.emptyList();
        } else {
            List<PropertySymbol> results = Lists.create();
            for (AstSimpleName name : grouping.properties) {
                PropertyDeclaration property = properties.get(name.identifier);
                if (property == null) {
                    report(new Diagnostic(
                            Level.ERROR,
                            name,
                            Messages.getString("DmdlAnalyzer.diagnosticMissingSummarizeGroupingProperty"), //$NON-NLS-1$
                            name.identifier));
                    continue;
                }
                results.add(model.createPropertySymbol(name));
            }
            return results;
        }
    }

    private <T extends AstTerm<T>> boolean checkGrouping(ModelDeclaration model, List<ReduceTerm<T>> terms) {
        assert model != null;
        assert terms != null;
        Iterator<ReduceTerm<T>> iter = terms.iterator();
        if (iter.hasNext() == false) {
            return false;
        }

        boolean green = true;
        ReduceTerm<T> first = iter.next();
        List<PropertyDeclaration> firstSources = resolveGroupingSources(first);
        while (iter.hasNext()) {
            ReduceTerm<T> next = iter.next();
            if (first.getGrouping().size() != next.getGrouping().size()) {
                report(new Diagnostic(
                        Level.ERROR,
                        next.getOriginalAst(),
                        Messages.getString("DmdlAnalyzer.diagnosticInconsistentNumberGroupingProperties"), //$NON-NLS-1$
                        model.getName()));
                return false;
            }
            List<PropertyDeclaration> nextSources = resolveGroupingSources(next);
            assert firstSources.size() == nextSources.size();
            for (int i = 0, n = firstSources.size(); i < n; i++) {
                PropertyDeclaration left = firstSources.get(i);
                PropertyDeclaration right = nextSources.get(i);
                if (left.getType().isSame(right.getType()) == false) {
                    PropertySymbol rightSymbol = next.getGrouping().get(i);
                    report(new Diagnostic(
                            Level.ERROR,
                            rightSymbol.getOriginalAst(),
                            Messages.getString("DmdlAnalyzer.diagnosticInconsistentTypeGroupingProperty"), //$NON-NLS-1$
                            rightSymbol.getName()));
                    green = false;
                }
            }
        }
        return green;
    }

    private List<PropertyDeclaration> resolveGroupingSources(ReduceTerm<?> term) {
        assert term != null;
        Map<PropertySymbol, PropertySymbol> rmap = new HashMap<PropertySymbol, PropertySymbol>();
        for (MappingFactor entry : term.getMappings()) {
            rmap.put(entry.getTarget(), entry.getSource());
        }
        List<PropertyDeclaration> results = new ArrayList<PropertyDeclaration>();
        for (PropertySymbol prop : term.getGrouping()) {
            PropertySymbol source = rmap.get(prop);
            if (source == null) {
                source = prop;
            }
            results.add(source.findDeclaration());
        }
        return results;
    }

    private void resolveAttributes() {
        for (ModelDeclaration model : context.getWorld().getDeclaredModels()) {
            LOG.debug("Resolving attributes: {}", model.getName()); //$NON-NLS-1$
            resolveAttributes(model);
            for (PropertyDeclaration property : model.getDeclaredProperties()) {
                resolveAttributes(property);
            }
        }
    }

    private void resolveAttributes(Declaration declaration) {
        assert declaration != null;
        for (AstAttribute attribute : declaration.getAttributes()) {
            String name = attribute.name.toString();
            LOG.debug("Resolving attribute: {} -> {}", declaration.getName(), name); //$NON-NLS-1$
            AttributeDriver driver = context.findAttributeDriver(attribute);
            if (driver == null) {
                report(new Diagnostic(
                        Level.ERROR,
                        attribute.name,
                        Messages.getString("DmdlAnalyzer.diagnosticUnknownAttribute"), //$NON-NLS-1$
                        name));
                continue;
            }
            LOG.debug("Processing attribute: {} -> {}", name, driver); //$NON-NLS-1$
            driver.process(context.getWorld(), declaration, attribute);
        }
    }

    private void verifyAttributes() {
        for (ModelDeclaration model : context.getWorld().getDeclaredModels()) {
            LOG.debug("Verifying attributes: {}", model.getName()); //$NON-NLS-1$
            verifyAttributes(model);
            for (PropertyDeclaration property : model.getDeclaredProperties()) {
                verifyAttributes(property);
            }
        }
    }

    private void verifyAttributes(Declaration declaration) {
        assert declaration != null;
        for (AstAttribute attribute : declaration.getAttributes()) {
            String name = attribute.name.toString();
            AttributeDriver driver = context.findAttributeDriver(attribute);
            if (driver == null) {
                // may not occur
                continue;
            }
            LOG.debug("Verifying attribute: {} -> {}", name, driver); //$NON-NLS-1$
            driver.verify(context.getWorld(), declaration, attribute);
        }
    }

    private <T extends AstTerm<T>> List<T> extract(AstExpression<T> expression) {
        if (expression instanceof AstTerm<?>) {
            AstTerm<T> term = (AstTerm<T>) expression;
            return Collections.singletonList(term.getUnit());
        } else if (expression instanceof AstUnionExpression<?>) {
            AstUnionExpression<T> union = (AstUnionExpression<T>) expression;
            return union.terms;
        } else {
            throw new AssertionError(expression);
        }
    }

    private class RecordExpressionResolver extends AbstractVisitor<ModelDeclaration, Void> {

        final List<ModelSymbol> projections = Lists.create();

        /**
         * Creates and returns a new instance.
         */
        RecordExpressionResolver() {
            return;
        }

        @Override
        public <T extends AstTerm<T>> Void visitUnionExpression(
                ModelDeclaration model,
                AstUnionExpression<T> node) {
            for (T term : node.terms) {
                term.accept(model, this);
            }
            return null;
        }

        @Override
        public Void visitModelReference(ModelDeclaration model, AstModelReference node) {
            LOG.debug("processing model reference: {}", node); //$NON-NLS-1$
            ModelDeclaration decl = context.getWorld().findModelDeclaration(node.name.identifier);
            if (decl == null) {
                report(new Diagnostic(
                        Level.ERROR,
                        node.name,
                        Messages.getString("DmdlAnalyzer.diagnosticMissingModel"), //$NON-NLS-1$
                        node.name.identifier));
                return null;
            }
            for (PropertyDeclaration property : decl.getDeclaredProperties()) {
                PropertyDeclaration other = model.findPropertyDeclaration(property.getName().identifier);
                if (other != null) {
                    LOG.debug("property {} is duplicated", property.getSymbol()); //$NON-NLS-1$
                    if (property.getType().isSame(other.getType()) == false) {
                        report(new Diagnostic(
                                Level.ERROR,
                                node,
                                Messages.getString("DmdlAnalyzer.diagnosticInconsistentTypeProperty"), //$NON-NLS-1$
                                property.getName(),
                                model.getName()));
                    }
                    continue;
                }
                model.declareProperty(
                        node,
                        property.getName(),
                        property.getType(),
                        property.getDescription(),
                        property.getAttributes());
            }
            if (decl.getOriginalAst().kind == ModelDefinitionKind.PROJECTIVE) {
                projections.add(context.getWorld().createModelSymbol(node.name));
            }
            return null;
        }

        @Override
        public Void visitRecordDefinition(ModelDeclaration model, AstRecordDefinition node) {
            LOG.debug("processing record definition: {}", node); //$NON-NLS-1$
            Set<String> sawPropertyName = Sets.create();
            for (AstPropertyDefinition property : node.properties) {
                if (sawPropertyName.contains(property.name.identifier)) {
                    report(new Diagnostic(
                            Level.ERROR,
                            property.name,
                            Messages.getString("DmdlAnalyzer.diagnosticDuplicatedProperty"), //$NON-NLS-1$
                            property.name.identifier));
                }
                sawPropertyName.add(property.name.identifier);
                Type type = context.resolveType(property.type);
                if (type == null) {
                    report(new Diagnostic(
                            Level.ERROR,
                            property.type,
                            Messages.getString("DmdlAnalyzer.diagnosticUnknownTypeProperty"), //$NON-NLS-1$
                            property.type.toString()));
                    continue;
                }

                PropertyDeclaration other = model.findPropertyDeclaration(property.name.identifier);
                if (other != null) {
                    LOG.debug("property {} is duplicated", property.name); //$NON-NLS-1$
                    if (type.equals(other.getType()) == false) {
                        report(new Diagnostic(
                                Level.ERROR,
                                property.name,
                                Messages.getString(
                                        "DmdlAnalyzer.diagnosticInconsistentTypeRepordProperty"), //$NON-NLS-1$
                                property.name,
                                model.getName()));
                    }
                    continue;
                }
                model.declareProperty(
                        property,
                        property.name,
                        type,
                        property.description,
                        property.attributes);
            }
            return null;
        }
    }
}
TOP

Related Classes of com.asakusafw.dmdl.analyzer.DmdlAnalyzer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.