com.abstratt.mdd.internal.frontend.textuml.TextUMLCompiler.java Source code

Java tutorial

Introduction

Here is the source code for com.abstratt.mdd.internal.frontend.textuml.TextUMLCompiler.java

Source

/*******************************************************************************
 * Copyright (c) 2006, 2010 Abstratt Technologies
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Rafael Chaves (Abstratt Technologies) - initial API and implementation
 *******************************************************************************/
package com.abstratt.mdd.internal.frontend.textuml;

import java.io.IOException;
import java.io.PushbackReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.TreeMap;

import org.apache.commons.lang.StringUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.uml2.uml.NamedElement;

import com.abstratt.mdd.core.IBasicRepository;
import com.abstratt.mdd.core.IProblem;
import com.abstratt.mdd.core.IProblem.Severity;
import com.abstratt.mdd.frontend.core.SyntaxProblem;
import com.abstratt.mdd.frontend.core.spi.AbortedCompilationException;
import com.abstratt.mdd.frontend.core.spi.CompilationContext;
import com.abstratt.mdd.frontend.core.spi.ICompiler;
import com.abstratt.mdd.frontend.core.spi.IDeferredReference;
import com.abstratt.mdd.frontend.core.spi.IProblemTracker;
import com.abstratt.mdd.frontend.core.spi.IReferenceTracker;
import com.abstratt.mdd.frontend.core.spi.ISourceAnalyzer;
import com.abstratt.mdd.frontend.core.spi.ISourceMiner;
import com.abstratt.mdd.frontend.core.spi.ProblemBuilder;
import com.abstratt.mdd.frontend.textuml.core.TextUMLConstants;
import com.abstratt.mdd.frontend.textuml.core.TextUMLCore;
import com.abstratt.mdd.internal.frontend.textuml.analysis.AnalysisAdapter;
import com.abstratt.mdd.internal.frontend.textuml.analysis.DepthFirstAdapter;
import com.abstratt.mdd.internal.frontend.textuml.lexer.Lexer;
import com.abstratt.mdd.internal.frontend.textuml.lexer.LexerException;
import com.abstratt.mdd.internal.frontend.textuml.node.AAggregationReferenceType;
import com.abstratt.mdd.internal.frontend.textuml.node.AAssociationDef;
import com.abstratt.mdd.internal.frontend.textuml.node.AAssociationHeader;
import com.abstratt.mdd.internal.frontend.textuml.node.AAssociationMemberEnd;
import com.abstratt.mdd.internal.frontend.textuml.node.AAssociationOwnedEnd;
import com.abstratt.mdd.internal.frontend.textuml.node.AAttributeDecl;
import com.abstratt.mdd.internal.frontend.textuml.node.AClassDef;
import com.abstratt.mdd.internal.frontend.textuml.node.AClassHeader;
import com.abstratt.mdd.internal.frontend.textuml.node.AComponentClassType;
import com.abstratt.mdd.internal.frontend.textuml.node.ACompositionReferenceType;
import com.abstratt.mdd.internal.frontend.textuml.node.AEnumerationClassType;
import com.abstratt.mdd.internal.frontend.textuml.node.AFeatureDecl;
import com.abstratt.mdd.internal.frontend.textuml.node.AInterfaceClassType;
import com.abstratt.mdd.internal.frontend.textuml.node.AOperationDecl;
import com.abstratt.mdd.internal.frontend.textuml.node.AOperationHeader;
import com.abstratt.mdd.internal.frontend.textuml.node.APackageHeading;
import com.abstratt.mdd.internal.frontend.textuml.node.AQueryOperationKeyword;
import com.abstratt.mdd.internal.frontend.textuml.node.AReceptionDecl;
import com.abstratt.mdd.internal.frontend.textuml.node.AReferenceDecl;
import com.abstratt.mdd.internal.frontend.textuml.node.ASignalClassType;
import com.abstratt.mdd.internal.frontend.textuml.node.ASignature;
import com.abstratt.mdd.internal.frontend.textuml.node.ASimpleOptionalReturnType;
import com.abstratt.mdd.internal.frontend.textuml.node.ASimpleParamDecl;
import com.abstratt.mdd.internal.frontend.textuml.node.AStart;
import com.abstratt.mdd.internal.frontend.textuml.node.AStateDecl;
import com.abstratt.mdd.internal.frontend.textuml.node.AStateMachineDecl;
import com.abstratt.mdd.internal.frontend.textuml.node.AStereotypeDef;
import com.abstratt.mdd.internal.frontend.textuml.node.AStereotypeDefHeader;
import com.abstratt.mdd.internal.frontend.textuml.node.ASubNamespace;
import com.abstratt.mdd.internal.frontend.textuml.node.ATypeIdentifier;
import com.abstratt.mdd.internal.frontend.textuml.node.EOF;
import com.abstratt.mdd.internal.frontend.textuml.node.Node;
import com.abstratt.mdd.internal.frontend.textuml.node.PAssociationKind;
import com.abstratt.mdd.internal.frontend.textuml.node.PClassType;
import com.abstratt.mdd.internal.frontend.textuml.node.PReferenceType;
import com.abstratt.mdd.internal.frontend.textuml.node.Start;
import com.abstratt.mdd.internal.frontend.textuml.node.Switch;
import com.abstratt.mdd.internal.frontend.textuml.node.TIdentifier;
import com.abstratt.mdd.internal.frontend.textuml.node.TPrivate;
import com.abstratt.mdd.internal.frontend.textuml.node.TSemicolon;
import com.abstratt.mdd.internal.frontend.textuml.node.TWhiteSpace;
import com.abstratt.mdd.internal.frontend.textuml.node.Token;
import com.abstratt.mdd.internal.frontend.textuml.parser.Parser;
import com.abstratt.mdd.internal.frontend.textuml.parser.ParserException;

/**
 * The compiler for the TextUML language (aka DIL).
 */
public class TextUMLCompiler implements ICompiler, ISourceAnalyzer {

    /*
     * (non-Javadoc)
     * 
     * @see com.abstratt.mdd.core.frontend.spi.ICompiler#compile(java.io.Reader,
     *      com.abstratt.mdd.core.IRepository,
     *      com.abstratt.mdd.core.frontend.spi.IReferenceTracker,
     *      com.abstratt.mdd.core.frontend.spi.IProblemTracker, boolean)
     */
    public void compile(Reader source, CompilationContext context) throws CoreException {
        Start tree = parse(source, context.getProblemTracker());
        if (tree != null)
            compile(tree, context);
    }

    public void compile(final Start tree, final CompilationContext context) {
        try {
            // generate structure
            tree.apply(new StructureGenerator(context));
            context.getReferenceTracker().add(new IDeferredReference() {
                public void resolve(IBasicRepository repository) {
                    if (!context.getProblemTracker().hasProblems(Severity.ERROR)) {
                        // schedule behavior generation as the last step
                        final Switch behaviorGenerator = new StructureBehaviorGenerator(context);
                        context.getReferenceTracker().add(new IDeferredReference() {
                            public void resolve(IBasicRepository repository) {
                                tree.apply(behaviorGenerator);
                            }
                        }, IReferenceTracker.Step.LAST);
                    }
                }
            }, IReferenceTracker.Step.GENERAL_RESOLUTION);
        } catch (AbortedCompilationException e) {
            // just abort...
        }
    }

    public String findModelName(String toParse) {
        PushbackReader in = new PushbackReader(new StringReader(toParse), 1);
        Lexer lexer = new Lexer(in);
        Parser parser = new Parser(lexer);
        try {
            Start parsed = parser.parse();
            final String[] packageIdentifier = { null };
            parsed.getPStart().apply(new DepthFirstAdapter() {
                @Override
                public void caseAStart(AStart node) {
                    ((APackageHeading) node.getPackageHeading()).getQualifiedIdentifier()
                            .apply(new DepthFirstAdapter() {
                                @Override
                                public void caseTIdentifier(TIdentifier node) {
                                    if (packageIdentifier[0] == null)
                                        packageIdentifier[0] = Util.stripEscaping(node.getText());
                                }
                            });
                }
            });
            return packageIdentifier[0];
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            // fall through
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                // not interested in failures while closing
            }
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.abstratt.mdd.core.frontend.spi.ICompiler#format(java.lang.String)
     */
    public String format(String toFormat) {
        PushbackReader in = new PushbackReader(new StringReader(toFormat), 64 * 1024);
        Lexer lexer = new Lexer(in);
        Parser parser = new Parser(lexer);
        try {
            Start parsed = parser.parse();
            return new TextUMLFormatter().format(parsed.getPStart(), parser.ignoredTokens);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            // fall through
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                // not interested in failures while closing
            }
        }
        return toFormat;
    }

    private Start parse(Reader source, IProblemTracker problems) throws CoreException {
        ProblemBuilder<Node> problemBuilder = new ProblemBuilder<Node>(problems, new SCCTextUMLSourceMiner());
        PushbackReader in = new PushbackReader(source, 64 * 1024);
        Lexer lexer = new Lexer(in);
        Parser parser = new Parser(lexer);
        try {
            return parser.parse();
        } catch (ParserException e) {
            if (problems != null)
                problemBuilder.addProblem(
                        new SyntaxProblem("Found: '" + e.getToken().getText() + "'. " + e.getMessage()),
                        e.getToken());
        } catch (LexerException e) {
            if (problems != null) {
                SyntaxProblem problem = new SyntaxProblem(e.getMessage());
                problem.setAttribute(IProblem.LINE_NUMBER, SCCTextUMLSourceMiner.parseLineNumber(e.getMessage()));
                problemBuilder.addProblem(problem, null);
            }
        } catch (IOException e) {
            IStatus status = new Status(IStatus.ERROR, TextUMLConstants.PLUGIN_ID, 0,
                    "Error reading source unit: " + source.toString(), e);
            throw new CoreException(status);
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                // not interested in failures while closing
            }
        }
        return null;
    }

    /**
      * Parses the given compilation unit, returning the parsed syntax tree.
      *
      * @param toParse source of compilation unit to be parsed
      * @return compilation unit syntax tree
      */
    public Start parse(String toParse) {
        try {
            return parse(new StringReader(toParse), null);
        } catch (CoreException e) {
            return null;
        }
    }

    /**
     * Given a position in a compilation unit, finds the contextual package name. 
     * 
     * @param toParse source of compilation unit
     * @param line line number, starting from 1
     * @param col column number, starting from 1
     * @return the name of the contextual package
     */
    public String findPackageName(String toParse, final int line, final int col) {
        Token token = findTokenAt(toParse, line, col);
        if (token == null)
            return null;
        final Stack<String> segments = new Stack<String>();
        for (Node current = token; current != null; current = current.parent()) {
            current.apply(new AnalysisAdapter() {
                @Override
                public void caseAStart(AStart node) {
                    segments.push(TextUMLCore.getSourceMiner().getQualifiedIdentifier(
                            ((APackageHeading) node.getPackageHeading()).getQualifiedIdentifier()));
                }

                public void caseASubNamespace(ASubNamespace node) {
                    segments.push(TextUMLCore.getSourceMiner()
                            .getQualifiedIdentifier(((APackageHeading) node.getPackageHeading())));
                }
            });
        }
        if (segments.isEmpty())
            return null;
        StringBuffer result = new StringBuffer();
        while (!segments.isEmpty()) {
            result.append(segments.pop());
            result.append(NamedElement.SEPARATOR);
        }
        result.delete((result.length() - NamedElement.SEPARATOR.length()), result.length());
        return result.toString();
    }

    /**
     * Given a position in a compilation unit, finds the contextual namespace. 
     * 
     * @param toParse source of compilation unit
     * @param line line number, starting from 1
     * @param col column number, starting from 1
     * @return the name of the contextual namespace
     */
    public String findNamespace(String toParse, final int line, final int col) {
        Token token = findTokenAt(toParse, line, col, false, true);
        if (token == null)
            return null;
        final Stack<String> segments = new Stack<String>();
        for (Node current = token; current != null; current = current.parent()) {
            current.apply(new AnalysisAdapter() {
                @Override
                public void caseAStart(AStart node) {
                    segments.push(TextUMLCore.getSourceMiner().getQualifiedIdentifier(
                            ((APackageHeading) node.getPackageHeading()).getQualifiedIdentifier()));
                }

                public void caseASubNamespace(ASubNamespace node) {
                    segments.push(TextUMLCore.getSourceMiner()
                            .getQualifiedIdentifier(((APackageHeading) node.getPackageHeading())));
                }

                @Override
                public void caseAClassDef(AClassDef node) {
                    segments.push(((AClassHeader) node.getClassHeader()).getIdentifier().getText());
                }

                @Override
                public void caseAStereotypeDef(AStereotypeDef node) {
                    segments.push(((AStereotypeDefHeader) node.getStereotypeDefHeader()).getIdentifier().getText());
                }

                @Override
                public void caseAAssociationDef(AAssociationDef node) {
                    final String associationName = ((AAssociationHeader) node.getAssociationHeader())
                            .getIdentifier().getText();
                    if (associationName.length() > 0)
                        segments.push(associationName);
                }
            });
        }
        if (segments.isEmpty())
            return null;
        StringBuffer result = new StringBuffer();
        while (!segments.isEmpty()) {
            result.append(segments.pop());
            result.append(NamedElement.SEPARATOR);
        }
        result.delete((result.length() - NamedElement.SEPARATOR.length()), result.length());
        return result.toString();
    }

    /**
     * Returns the token found at the given position in the given compilation unit.
     * 
     * @param toParse source of compilation unit
     * @param line line number, starting from 1
     * @param col column number, starting from 1
     * @return the token found
     */
    public Token findTokenAt(String toParse, final int line, final int col) {
        return findTokenAt(toParse, line, col, true, false);
    }

    /**
     * Returns the token found at the given position in the given compilation unit.
     * 
     * @param toParse source of compilation unit
     * @param line line number, starting from 1
     * @param col column number, starting from 1
      * @param ignoreWhitespace whether whitespaces should be ignored
      * @param ignoreSemicolon whether semicolons should be ignored
     * @return the token found
     */
    public Token findTokenAt(String toParse, final int line, final int col, final boolean ignoreWhitespace,
            final boolean ignoreSemicolon) {
        Start parsed = parse(toParse);
        final Token[] target = { null };
        parsed.apply(new DepthFirstAdapter() {
            @Override
            public void defaultCase(Node node) {
                if (!(node instanceof Token))
                    return;
                Token asToken = (Token) node;
                if (asToken instanceof EOF)
                    return;
                if (ignoreWhitespace && asToken instanceof TWhiteSpace)
                    return;
                if (ignoreSemicolon && asToken instanceof TSemicolon)
                    return;
                if (line < asToken.getLine())
                    return;
                int start = asToken.getPos();
                int end = start + asToken.getText().length() - 1;
                if (line > asToken.getLine() || col >= start)
                    target[0] = asToken;
            }
        });
        return target[0];
    }

    @Override
    public List<SourceElement> analyze(String source) {
        final ISourceMiner<Node> sourceMiner = TextUMLCore.getSourceMiner();
        final List<SourceElement> elements = new ArrayList<SourceElement>();
        Start parsed = parse(source);
        if (parsed == null)
            return Collections.emptyList();
        final List<SourceElement> parent = new LinkedList<ISourceAnalyzer.SourceElement>();
        parsed.apply(new DepthFirstAdapter() {
            @Override
            public void caseAClassDef(AClassDef node) {
                PClassType classType = sourceMiner.findChild(node.getClassHeader(), PClassType.class, true);
                String classTypeName = sourceMiner.getText(classType);
                String className = sourceMiner.getIdentifier(node.getClassHeader());
                ElementKind kind;
                if (classType instanceof ASignalClassType)
                    kind = ElementKind.Signals;
                else if (classType instanceof AInterfaceClassType)
                    kind = ElementKind.Interfaces;
                else if (classType instanceof AComponentClassType)
                    kind = ElementKind.Components;
                else if (classType instanceof AInterfaceClassType)
                    kind = ElementKind.Interfaces;
                else if (classType instanceof AEnumerationClassType)
                    kind = ElementKind.Enumerations;
                else
                    kind = ElementKind.Classes;
                SourceElement element = new SourceElement(classTypeName + " " + className,
                        sourceMiner.getLineNumber(node.getClassHeader()), kind);
                elements.add(element);
                parent.add(element);
                super.caseAClassDef(node);
                parent.remove(parent.size() - 1);
            }

            @Override
            public void caseAAssociationDef(AAssociationDef node) {
                PAssociationKind assocKind = sourceMiner.findChild(node.getAssociationHeader(),
                        PAssociationKind.class, true);
                String assocKindTypeName = sourceMiner.getText(assocKind);
                String assocName = sourceMiner.getIdentifier(node.getAssociationHeader());
                SourceElement element = new SourceElement(
                        assocKindTypeName + " " + StringUtils.trimToEmpty(assocName),
                        sourceMiner.getLineNumber(node.getAssociationHeader()), ElementKind.Associations);
                elements.add(element);
                parent.add(element);
                super.caseAAssociationDef(node);
                parent.remove(parent.size() - 1);
            }

            @Override
            public void caseAAssociationOwnedEnd(AAssociationOwnedEnd node) {
                String attributeName = node.getIdentifier().getText();
                SourceElement element = new SourceElement(attributeName, node.getRole().getLine(),
                        ElementKind.Ends);
                parent.get(parent.size() - 1).getChildren().add(element);
            }

            @Override
            public void caseAAssociationMemberEnd(AAssociationMemberEnd node) {
                String attributeName = node.getProperty().getText();
                SourceElement element = new SourceElement(attributeName, node.getRole().getLine(),
                        ElementKind.Ends);
                parent.get(parent.size() - 1).getChildren().add(element);
            }

            @Override
            public void caseAStateMachineDecl(AStateMachineDecl node) {
                String smName = sourceMiner.getIdentifier(node.getIdentifier());
                SourceElement element = new SourceElement("statemachine" + " " + smName,
                        sourceMiner.getLineNumber(node.getIdentifier()), ElementKind.StateMachines);
                parent.get(parent.size() - 1).getChildren().add(element);
                parent.add(element);
                super.caseAStateMachineDecl(node);
                parent.remove(parent.size() - 1);
            }

            @Override
            public void caseAStateDecl(AStateDecl node) {
                String stateName = sourceMiner.getText(node.getIdentifier());
                String modifier = sourceMiner.getText(node.getStateModifierList());
                String label = StringUtils.isBlank(stateName) ? modifier
                        : (StringUtils.isBlank(modifier) ? stateName : (stateName + " (" + modifier + ")"));
                SourceElement element = new SourceElement(label, sourceMiner.getLineNumber(node.getState()),
                        ElementKind.States);
                parent.get(parent.size() - 1).getChildren().add(element);
            }

            @Override
            public void caseAReferenceDecl(AReferenceDecl node) {
                PReferenceType refType = node.getReferenceType();
                ElementKind elementKind = refType instanceof ACompositionReferenceType ? ElementKind.Compositions
                        : (refType instanceof AAggregationReferenceType ? ElementKind.Aggregations
                                : ElementKind.References);
                addAttribute(isPrivateFeature(node), node.getIdentifier(), node.getTypeIdentifier(), elementKind);
            }

            @Override
            public void caseAAttributeDecl(AAttributeDecl node) {
                addAttribute(isPrivateFeature(node), node.getIdentifier(), node.getTypeIdentifier(),
                        ElementKind.Attributes);
            }

            private void addAttribute(boolean isPrivate, Node identifierNode, Node typeNode,
                    ElementKind elementKind) {
                String attributeName = sourceMiner.getText(identifierNode);
                String attributeType = sourceMiner.getText(typeNode);
                SourceElement element = new SourceElement(
                        (isPrivate ? "-" : "") + attributeName + " : " + attributeType,
                        sourceMiner.getLineNumber(identifierNode), elementKind);
                parent.get(parent.size() - 1).getChildren().add(element);
            }

            @Override
            public void caseAOperationDecl(AOperationDecl node) {
                AOperationHeader header = (AOperationHeader) node.getOperationHeader();
                boolean isQuery = header.getOperationKeyword() instanceof AQueryOperationKeyword;
                boolean isPrivate = isPrivateFeature(node);
                ASignature signature = (ASignature) header.getSignature();
                ASimpleOptionalReturnType returnType = sourceMiner.findChild(header.getSignature(),
                        ASimpleOptionalReturnType.class, true);
                String operationName = header.getIdentifier().getText();

                String operationType = returnType == null ? "" : sourceMiner.getText(returnType);

                List<String> parameterTypes = new ArrayList<String>();
                for (ASimpleParamDecl paramDecl : sourceMiner.findChildren(signature.getParamDeclList(),
                        ASimpleParamDecl.class))
                    parameterTypes.add(sourceMiner.getText(paramDecl.getTypeIdentifier()));
                String allParameters = StringUtils.join(parameterTypes, ", ");
                String representation = (isPrivate ? "-" : "") + operationName + (isQuery ? "?" : "") + "("
                        + allParameters + ")" + operationType;
                SourceElement element = new SourceElement(representation,
                        sourceMiner.getLineNumber(header.getOperationKeyword()), ElementKind.Operations);
                parent.get(parent.size() - 1).getChildren().add(element);
            }

            protected boolean isPrivateFeature(Node node) {
                return sourceMiner.findChild(sourceMiner.findParent(node, AFeatureDecl.class).getModifiers(),
                        TPrivate.class, true) != null;
            }

            @Override
            public void caseAReceptionDecl(AReceptionDecl node) {
                ATypeIdentifier signalType = sourceMiner.findChild(node.getSimpleParamDecl(), ATypeIdentifier.class,
                        true);
                String operationName = node.getReceptionName().getText();
                String representation = operationName + "(" + signalType + ")";
                SourceElement element = new SourceElement(representation, node.getReception().getLine(),
                        ElementKind.Receptions);
                parent.get(parent.size() - 1).getChildren().add(element);
            }
        });
        Collections.sort(elements, SourceElementComparator.INSTANCE);

        List<SourceElement> result = elements;
        if (elements.size() > 10) {
            Map<ElementKind, SourceElement> groups = new TreeMap<ElementKind, SourceElement>();
            for (SourceElement sourceElement : elements) {
                SourceElement group = groups.get(sourceElement.getKind());
                if (group == null)
                    groups.put(sourceElement.getKind(), group = new SourceElement(sourceElement.getKind().name(),
                            sourceElement.getLine(), ElementKind.Groups));
                group.getChildren().add(sourceElement);
            }
            if (groups.size() > 2)
                result = new ArrayList<SourceElement>(groups.values());
        }
        return result;
    }
}