ModelerImpl.java

/* $Id$
 *****************************************************************************
 * Copyright (c) 2009-2013 Contributors - see below
 * 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:
 *    Luis Sergio Oliveira (euluis)
 *****************************************************************************
 *
 * Some portions of this file was previously release using the BSD License:
 */

// Copyright (c) 1996-2009 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies.  This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason.  IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package org.argouml.language.cpp.reveng;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import java.util.logging.Logger;

import org.argouml.kernel.Project;
import org.argouml.model.IllegalModelElementConnectionException;
import org.argouml.model.Model;
import static org.argouml.model.Model.*;
import org.argouml.profile.Profile;
import org.argouml.profile.ProfileException;
import org.argouml.language.cpp.profile.ProfileCpp;
import static org.argouml.language.cpp.profile.ProfileCpp.*;

/**
 * Implementation of the <code>Modeler</code> interface. This facade
 * implements part and delegates the rest of the implementation that transforms
 * the parsed information from a C++ translation unit into UML model elements
 * and updating the model with it.
 *
 * @author euluis
 * @since 0.19.3
 */
public class ModelerImpl implements Modeler {
    /**
     * The context stack keeps track of the current parsing context in a stack
     * wise manner.
     */
    private Stack contextStack = new Stack();

    /**
     * The access specifier applicable to the parsing context. May be null if
     * not within a classifier.
     */
    private Object contextAccessSpecifier;

    /**
     * Counts the member declaration level.
     */
    private int memberDeclarationCount;

    /**
     * Counts the compound statement level.
     */
    private int compoundStatementCount;

    private boolean ignoreableFunctionDefinition;

    /**
     * Logger.
     */
    private static final Logger LOG = Logger.getLogger(
            ModelerImpl.class.getName());
    
    private Collection newElements;
    
    private ProfileCpp profile;
    
    private Project project;
    
    private AttributeModeler attributeModeler;
    
    private OperationModeler operationModeler;
    
    ModelerImpl(Project p) throws ProfileException {
        project = p;
        List<Profile> projectProfiles = 
            project.getProfileConfiguration().getProfiles();
        Profile cppProfile = null;
        for (Profile projectProfile : projectProfiles) {
            if (projectProfile.getDisplayName() != null 
                && projectProfile.getDisplayName().contains("C++")) {
                cppProfile = projectProfile;
                break;
            }
        }
        if (cppProfile != null) {
            profile = new ProfileCpp(project.getUserDefinedModelList(), 
                cppProfile.getProfilePackages().iterator().next());
        }
        else {
            profile = new ProfileCpp(project.getModels());
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginTranslationUnit()
     */
    public void beginTranslationUnit() {
        newElements = new HashSet();
        contextStack.push(getModel());
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endTranslationUnit()
     */
    public void endTranslationUnit() {
        // for now we don't need to do anything here
    }
    
    /*
     * @see org.argouml.language.cpp.reveng.Modeler#getNewElements()
     */
    public Collection getNewElements() {
        return newElements;
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#enterNamespaceScope(java.lang.String)
     */
    public void enterNamespaceScope(String nsName) {
        if (!ignore()) {
            Object parentNs = getCurrentNamespace();
            Object ns = findNamespace(nsName, parentNs);
            if (ns == null) {
                ns = Model.getModelManagementFactory().buildPackage(nsName);
                newElements.add(ns);
                getCoreHelper().setNamespace(ns, parentNs);
            }
            contextStack.push(ns);
        }
    }

    /**
     * Get the current namespace from the {@link #contextStack contextStack} or
     * the model.
     * 
     * @return the parent namespace
     */
    private Object getCurrentNamespace() {
        Object parentNs = null;
        if (contextStack.isEmpty()) {
            parentNs = getModel();
        } else {
            parentNs = contextStack.peek();
            assert getFacade().isANamespace(parentNs);
        }
        return parentNs;
    }

    /**
     * Find the namespace with the given name which parent is
     * <code>parentNs</code>.
     *
     * @param nsName namespace name
     * @param parentNs the parent namespace of the namespace to get
     * @return the namespace if it exists, <code>null</code> otherwise.
     */
    private Object findNamespace(String nsName, Object parentNs) {
        Collection nss =
	    Model.getModelManagementHelper().getAllNamespaces(getModel());
        Iterator it = nss.iterator();
        Object ns = null;
        while (it.hasNext()) {
            Object tmpNs = it.next();
            if (nsName.equals(getFacade().getName(tmpNs))) {
                // NOTE: equality by reference may be deceiving if the
                // implementation uses proxies - not likely that different
                // proxies are used, so at least we should be comparing the
                // references of the same proxy object!
                if (getFacade().getNamespace(tmpNs) == parentNs) {
                    ns = tmpNs;
                    break;
                }
            }
        }
        return ns;
    }
    
    private Object model;

    /**
     * FIXME: the user model should be received via constructor.
     * @return the user model
     */
    private Object getModel() {
        if (model != null) {
            return model;
        }
        for (Object userModel : getProject().getUserDefinedModelList()) {
            if (!getModelManagementHelper().isReadOnly(userModel)) {
                model = userModel;
                return model;
            }
        }
        throw new IllegalStateException("An editable user model wasn't found!");
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#exitNamespaceScope()
     */
    public void exitNamespaceScope() {
        if (!ignore()) {
            Object ns = contextStack.pop();
            assert getFacade().isANamespace(ns) : "The popped context (\""
                + ns + "\") isn't a namespace!";
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#makeNamespaceAlias(java.lang.String,
     *      java.lang.String)
     */
    public void makeNamespaceAlias(String ns, String alias) {
        // TODO: implement after defining the way this is supposed to be
        // modeled
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginClassDefinition(java.lang.String,
     *      java.lang.String)
     */
    public void beginClassDefinition(String oType, String identifier) {
        if (!ignore()) {
            // a class is defined, so, check if it exists in the model or
            // create it...
            Object ns = getCurrentNamespace();
            // FIXME: we're not checking for oType here! Is it needed? Is it
            // possible to have struct X and class X in the same namespace in
            // C++?
            Object cls = findClass(identifier, ns);
            if (cls == null) {
                cls = getCoreFactory().buildClass(identifier, ns);
                profile.applyCppClassStereotype(cls);
                newElements.add(cls);
            }
            contextStack.push(cls);
            if (CPPvariables.OT_CLASS.equals(oType)) {
                // the default visibility for a C++ class
                contextAccessSpecifier = getVisibilityKind().getPrivate();
            } else if (CPPvariables.OT_STRUCT.equals(oType)) {
                contextAccessSpecifier = getVisibilityKind().getPublic();
                profile.applyClassSpecifierTaggedValue(cls, "struct");
            } else if (CPPvariables.OT_UNION.equals(oType)) {
                // TODO: implement union specifics.
                ;
            } else {
                assert false
                : "Not expecting any other oType than class, struct and "
                    + "union!";
            }
        }
    }

    /**
     * Find a class within the given namespace that has the given identifier.
     *
     * @param identifier the class identifier
     * @param ns namespace to look in
     * @return the class if found, null otherwise
     */
    private static Object findClass(String identifier, Object ns) {
        Collection classes = getCoreHelper().getAllClasses(ns);
        Iterator it = classes.iterator();
        while (it.hasNext()) {
            Object candidateClass = it.next();
            if (Model.getFacade().getName(candidateClass).equals(identifier)) {
                return candidateClass;
            }
        }
        return null;
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endClassDefinition()
     */
    public void endClassDefinition() {
        if (!ignore()) {
            Object cls = contextStack.pop();
            assert getFacade().isAClass(cls) : "The popped context (\""
                + getFacade().getName(cls) + "\") isn't a class!";
            contextAccessSpecifier = null;
        }
    }

    /*
     * FIXME: I think that with nested classes having only one access specifier
     * won't work. This must be implemented in a stack scheme, where the
     * constructs that can work with access specifiers will need to manage the
     * stack.
     *
     * @see org.argouml.language.cpp.reveng.Modeler#accessSpecifier(java.lang.String)
     */
    public void accessSpecifier(String accessSpec) {
        if (!ignore()) {
            if ("public".equals(accessSpec)) {
                contextAccessSpecifier = Model.getVisibilityKind().getPublic();
            } else if ("protected".equals(accessSpec)) {
                contextAccessSpecifier =
		    Model.getVisibilityKind().getProtected();
            } else if ("private".equals(accessSpec)) {
                contextAccessSpecifier = Model.getVisibilityKind().getPrivate();
            } else {
                assert false : "Unknown C++ access specifier: " + accessSpec;
            }
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginFunctionDeclaration()
     */
    public void beginFunctionDeclaration() {
        if (!ignore()) {
            operationModeler = new OperationModeler(contextStack.peek(), 
                    contextAccessSpecifier, getVoid(), false, profile);
            contextStack.push(operationModeler.getOperation());
        }
    }
    
    private Project getProject() {
        return project;
    }

    /**
     * @return the void DataType
     */
    private Object getVoid() {
        return getProject().findType("void");
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endFunctionDeclaration()
     */
    public void endFunctionDeclaration() {
        if (!ignore()) {
            assert operationModeler != null : "operationModeler is null.";
            Object oper = contextStack.pop();
            assert Model.getFacade().isAOperation(oper) : ""
                + "The popped context (\"" + oper + "\") isn't an operation!";
            operationModeler.finish();
            operationModeler = null;
        }
    }

    private TypedefModeler typedefModeler;

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#declarationSpecifiers(java.util.List)
     */
    public void declarationSpecifiers(List declSpecs) {
        if (declSpecs.contains("typedef")) {
            assert typedefModeler == null;
            typedefModeler = new TypedefModeler(contextStack.peek(), 
                contextAccessSpecifier, profile);
        } else if (getFacade().isAOperation(contextStack.peek())) {
            OperationModeler modeler = operationModeler != null
                ? operationModeler : xtorModeler;
            modeler.declarationSpecifiers(declSpecs);
        }
    }
    
    /*
     * @see org.argouml.language.cpp.reveng.Modeler#simpleTypeSpecifier(java.util.List)
     */
    public void simpleTypeSpecifier(List sts) {
        if (!ignore()) {
            StringBuffer stsString = new StringBuffer();
            Iterator i = sts.iterator();
            while (i.hasNext()) {
                stsString.append(i.next().toString()).append(" ");
            }
            LOG.finer("In simpleTypeSpecifier, stsString = " + stsString);
            Object theType = findOrCreateType(stsString.toString().trim());
            if (memberModeler != null) {
                memberModeler.setType(theType);
            }
            // now, depending on the context, this might be the return type of a
            // function declaration or an attribute of a class or a variable
            // declaration; of course, this is rather incomplete(!)
            Object contextModelElement = contextStack.peek();
            if (getFacade().isAOperation(contextModelElement)) {
                assert operationModeler != null
                    : "operationModeler is null in the context of operation "
                        + getFacade().getName(contextModelElement) + ".";
                operationModeler.setType(theType);
            } else if (getFacade().isAClass(contextModelElement)) {
                // an attribute or an enumeration... handled elsewhere
            } else if (getFacade().isAParameter(contextModelElement)) {
                getCoreHelper().setType(contextModelElement, theType);
            } else if (getFacade().isAModel(contextModelElement) 
                    || getFacade().isANamespace(contextModelElement)) {
                // we either have a global variable or a typedef
                if (typedefModeler != null) {
                    typedefModeler.setType(theType);
                }
            }
        }
    }

    /**
     * Finds or creates a type with the given name. This method
     * delegates the call to ArgoUML helper method, but, first takes
     * care of C++ specific issues, such as pointer and reference
     * stripping and buit-in types which shouldn't be created as
     * classes (the way ArgoUML does), but, as DataType.
     *
     * @param typeName the name of the type
     * @return A model element that represents the given type
     */
    private Object findOrCreateType(String typeName) {
        Object theType = null;
        List taggedValues = new LinkedList();
        processPtrOperators(typeName, taggedValues);
        if (profile.isBuiltIn(typeName)) {
            theType = profile.getBuiltIn(typeName);
        } else {
            theType = getProject().findType(typeName.toString(), true);
        }
        return theType;
    }

    /**
     * Process a type specification by stripping pointer operators
     * from the type name and processing them to tagged values that
     * are added to the provide list.
     *
     * @param typeName unprocessed C++ type name
     * @param taggedValues list of tagged values where any processing result is
     *            added to
     * @return the stripped type name
     */
    private String processPtrOperators(String typeName, List taggedValues) {
        // TODO: implement
        return typeName;
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#directDeclarator(java.lang.String, boolean)
     */
    public void directDeclarator(String id, boolean typedef) {
        if (!ignore()) {
            LOG.finer("In directDeclarator: id = \"" + id + "\"; typedef = " 
                + typedef);
            if (typedef) {
                assert typedefModeler != null;
                typedefModeler.directDeclarator(id);
                typedefModeler = null;
            } else {
                getCoreHelper().setName(contextStack.peek(), id);
            }
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#storageClassSpecifier(java.lang.String)
     */
    public void storageClassSpecifier(String storageClassSpec) {
        // TODO: Auto-generated method stub
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#typeQualifier(java.lang.String)
     */
    public void typeQualifier(String typeQualifier) {
        // TODO: Auto-generated method stub

    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginFunctionDefinition()
     */
    public void beginFunctionDefinition() {
        if (!ignore()) {
            if (isMemberDeclaration()) {
                beginFunctionDeclaration();
                operationModeler.setDefinedInClass();
            } else {
                // TODO: here we should set the method of the corresponding
                // operation, if it exists, or create a global operation and
                // set the corresponding method
                ignoreableFunctionDefinition = true;
            }
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endFunctionDefinition()
     */
    public void endFunctionDefinition() {
        if (!ignore()) {
            if (isMemberDeclaration()) {
                endFunctionDeclaration();
            } else {
                // TODO: here we should set the method of the corresponding
                // operation, if it exists, or create a global operation and
                // set the corresponding method
                ignoreableFunctionDefinition = false;
            }
        }
    }

    /**
     * @return true if this call occurs within a member declaration
     */
    private boolean isMemberDeclaration() {
        return memberDeclarationCount > 0;
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#functionDirectDeclarator(java.lang.String)
     */
    public void functionDirectDeclarator(String identifier) {
        if (!ignore()) {
            assert getFacade().isAOperation(contextStack.peek());
            getCoreHelper().setName(contextStack.peek(), identifier);
        }
    }

    /**
     * @return true if the call should be ignored
     */
    private boolean ignore() {
        return compoundStatementCount > 0 || ignoreableFunctionDefinition;
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginParameterDeclaration()
     */
    public void beginParameterDeclaration() {
        if (!ignore()) {
            Object oper = contextStack.peek();
            if (Model.getFacade().isAOperation(oper)) {
                // create a parameter within the operation
                Object param =
		    Model.getCoreFactory().buildParameter(oper, getVoid());
                // add the created parameter to the stack
                contextStack.push(param);
            }
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endParameterDeclaration()
     */
    public void endParameterDeclaration() {
        if (!ignore()) {
            // NOTE: this is different from the other endXxx() methods, since,
            // we may be called within the context of a function definition
            // without it being a member.
            Object param = contextStack.peek();
            if (Model.getFacade().isAParameter(param)) {
                contextStack.pop();
                // set the parameter kind according to the tagged value details
                // FIXME: this won't work when we have const reference
                // parameters!
                if (Model.getFacade().getTaggedValueValue(param,
                    TV_NAME_REFERENCE).equals("true")
                    || Model.getFacade().getTaggedValueValue(param,
                        TV_NAME_POINTER).equals("true")) {
                    Model.getCoreHelper().setKind(param,
                        Model.getDirectionKind().getInOutParameter());
                }
            }
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginInitializer()
     */
    public void beginInitializer() {
        if (!ignore()) {
            Object context = contextStack.peek();
            if (Model.getFacade().isAOperation(context)) {
                // we don't really need to see what it is being initialized
                // to, for sure it is to 0 => abstract operation
                Model.getCoreHelper().setAbstract(context, true);
            }
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endInitializer()
     */
    public void endInitializer() {
        // do nothing
    }
    
    private MemberModeler memberModeler;
    
    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginMemberDeclaration()
     */
    public void beginMemberDeclaration() {
        Object owner = contextStack.peek();
        assertIsAClassifier(owner);
        memberModeler = new MemberModeler(owner, contextAccessSpecifier,
                profile);
        memberDeclarationCount++;
    }

    void assertIsAClassifier(Object modelElement) {
        assert getFacade().isAClassifier(modelElement) 
            : "modelElement must be a Classifier; its name is \"" 
                + getFacade().getName(modelElement) + "\".";
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endMemberDeclaration()
     */
    public void endMemberDeclaration() {
        memberDeclarationCount--;
        memberModeler.finish();
        memberModeler = null;
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginCompoundStatement()
     */
    public void beginCompoundStatement() {
        compoundStatementCount++;
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endCompoundStatement()
     */
    public void endCompoundStatement() {
        compoundStatementCount--;
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginPtrOperator()
     */
    public void beginPtrOperator() {
        if (!ignore()) {
            Object ptrTV = Model.getExtensionMechanismsFactory().
                buildTaggedValue(ProfileCpp.getTagDefinition("dummy"), 
                    new String[] {""});
            contextStack.push(ptrTV);
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endPtrOperator()
     */
    public void endPtrOperator() {
        if (!ignore()) {
            Object ptrTV = contextStack.pop();
            assert Model.getFacade().isATaggedValue(ptrTV) 
                : "A Tagged Value was expected, but, got: \"" + ptrTV + "\".";
            Object meToBeTagged = contextStack.peek();
            if (getFacade().isAOperation(meToBeTagged)) {
                Collection rps = getCoreHelper().getReturnParameters(
                    meToBeTagged);
                assert rps.size() == 1;
                meToBeTagged = rps.iterator().next();
            }
            Model.getExtensionMechanismsHelper().addTaggedValue(
                meToBeTagged, ptrTV);
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#ptrOperator(java.lang.String)
     */
    public void ptrOperator(String ptrSymbol) {
        if (!ignore()) {
            if (ptrSymbol.equals("&") || ptrSymbol.equals("*")) {
                // arrrg we must discard the tagged value created before cause
                // we can't change its name
                Object discardedTV = contextStack.pop();
                assert getFacade().isATaggedValue(discardedTV);

                Object paramOrAttribute = contextStack.peek();
                assert getFacade().isAParameter(paramOrAttribute) 
                    || getFacade().isAAttribute(paramOrAttribute) 
                    || getFacade().isAOperation(paramOrAttribute);
                String stereoName = null;
                if (getFacade().isAParameter(paramOrAttribute)) {
                    stereoName = STEREO_NAME_PARAMETER;
                }
                else if (getFacade().isAAttribute(paramOrAttribute)) {
                    stereoName = STEREO_NAME_ATTRIBUTE;
                }
                else if (getFacade().isAOperation(paramOrAttribute)) {
                    Collection rps = getCoreHelper().getReturnParameters(
                        paramOrAttribute);
                    assert rps.size() == 1;
                    paramOrAttribute = rps.iterator().next();
                    stereoName = STEREO_NAME_PARAMETER;
                }
                else {
                    LOG.warning("Unexpected reveng context: " 
                            + paramOrAttribute);
                    return;
                }
                profile.applyStereotype(stereoName, paramOrAttribute);
                String tvName = null;
                if (ptrSymbol.equals("&")) {
                    tvName = TV_NAME_REFERENCE;
                } else if (ptrSymbol.equals("*")) {
                    tvName = TV_NAME_POINTER;
                }
                profile.applyTaggedValue(stereoName, tvName, paramOrAttribute, 
                        "true");
                Object tv = getFacade().getTaggedValue(paramOrAttribute, 
                        tvName);
                contextStack.push(tv);
            } else {
                LOG.warning("unprocessed ptrSymbol: " + ptrSymbol);
            }
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#ptrToMember(java.lang.String,
     *      java.lang.String)
     */
    public void ptrToMember(String scopedItem, String star) {
        // TODO: Auto-generated method stub
    }

    /**
     * Modeler for the base_specifier rule.
     */
    private BaseSpecifierModeler baseSpecifierModeler;

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginBaseSpecifier()
     */
    public void beginBaseSpecifier() {
        if (!ignore()) {
            baseSpecifierModeler = new BaseSpecifierModeler();
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endBaseSpecifier()
     */
    public void endBaseSpecifier() {
        if (!ignore()) {
            baseSpecifierModeler.finish();
            baseSpecifierModeler = null;
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#baseSpecifier(java.lang.String,
     *      boolean)
     */
    public void baseSpecifier(String identifier, boolean isVirtual) {
        if (!ignore()) {
            baseSpecifierModeler.baseSpecifier(identifier, isVirtual);
	}
    }

    /**
     * A class that models the base_specifier rule, mapping it to a UML
     * generalization.
     */
    private class BaseSpecifierModeler {
        private Object previousAccessSpecifier;

        private Object generalization;

        /**
         * The constructor of the BaseSpecifierModeler retrieves information
         * about the current parsing context, storing it to enable context aware
         * processing of the baseSpecifier call, and resetting the context to
         * its previous state after processing.
         */
        BaseSpecifierModeler() {
            previousAccessSpecifier = contextAccessSpecifier;
            contextAccessSpecifier = null;
        }

        /**
         *
         * @param identifier the base class identifier
         * @param isVirtual flags virtual inheritance
         */
        void baseSpecifier(String identifier, boolean isVirtual) {
            // create a generalization for the current class
            Object parent = findOrCreateType(identifier);
            generalization =
		findOrCreateGeneralization(parent, contextStack.peek());
            profile.applyVirtualInheritanceTaggedValue(generalization, 
                    Boolean.toString(isVirtual));
        }

        /**
         * Finish processing the base specifier rule.
         */
        void finish() {
            // set the visibility of the generalization
            if (contextAccessSpecifier == null) { // default is private
                contextAccessSpecifier = Model.getVisibilityKind().getPrivate();
            }
            profile.applyInheritanceVisibilityTaggedValue2Generalization(
                    generalization, 
                    getFacade().getName(contextAccessSpecifier));
            // finish the base specifier by setting the context to the
            // previous state
            contextAccessSpecifier = previousAccessSpecifier;
        }
    }

    /**
     * Find or create a Generalization between the given parent and child
     * Classifiers.
     *
     * @param parent the parent Classifier
     * @param child the child Classifier
     * @return the found Generalization model element if found, otherwise a
     *         newly created
     */
    private Object findOrCreateGeneralization(Object parent,
            Object child) {
        Object generalization =
	    Model.getFacade().getGeneralization(child, parent);
        Object stereotype = null;
        if (generalization == null) {
            try {
                generalization = getUmlFactory().buildConnection(
                        getMetaTypes().getGeneralization(), child, null, 
                        parent, null, null, null);
            } catch (IllegalModelElementConnectionException e) {
                LOG.severe("Exception while creating generalization, " + e);
                throw new RuntimeException(e);
            }
	} else {
            Collection stereotypes = getFacade().getStereotypes(generalization);
            for (Object aStereotype : stereotypes) {
                if (STEREO_NAME_GENERALIZATION.equals(
                        getFacade().getName(aStereotype))) {
                    stereotype = aStereotype;
                }
            }
        }
        assert generalization != null;
        if (stereotype == null) {
            stereotype = profile.getCppGeneralizationStereotype();
            assert stereotype != null;
            getCoreHelper().addStereotype(generalization, stereotype);
        }
        return generalization;
    }
    
    private boolean isXtorIgnorable() {
        return contextStack.size() == 0 
            || getFacade().isAModel(contextStack.peek());
    }

    /**
     * Modeler for constructors and destructors.
     */
    private XtorModeler xtorModeler;
    
    private static interface XtorModelerCreator {
        XtorModeler create(Object owner, Object visibility, Object returnType, 
                boolean ignorable);
    }

    private void beginXtor(final XtorModelerCreator modelerCreator) {
        if (!ignore()) {
            assert xtorModeler == null;
            boolean ignorable = isXtorIgnorable();
            Object owner = contextStack.peek();
            xtorModeler = modelerCreator.create(owner, contextAccessSpecifier,
                getVoid(), ignorable);
            if (!ignorable) {
                assertIsAClassifier(owner);
                contextStack.push(xtorModeler.getOperation());
            }
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginCtorDefinition()
     */
    public void beginCtorDefinition() {
        beginCtor();
        if (!ignore()) {
            xtorModeler.setDefinedInClass();
        }
    }

    private void beginCtor() {
        final XtorModelerCreator modelerCreator = new XtorModelerCreator() {
            public XtorModeler create(Object owner, Object visibility,
                    Object returnType, boolean ignorable) {
                return new CtorModeler(owner, visibility, returnType,
                        ignorable, profile);
            }
        };
        beginXtor(modelerCreator);
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#beginDtorHead()
     */
    public void beginDtorHead() {
        final XtorModelerCreator modelerCreator = new XtorModelerCreator() {
            public XtorModeler create(Object owner, Object visibility,
                    Object returnType, boolean ignorable) {
                return new DtorModeler(owner, visibility, returnType,
                        ignorable, profile);
            }
        };
        beginXtor(modelerCreator);
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endCtorDefinition()
     */
    public void endCtorDefinition() {
        endXtor();
    }

    private void endXtor() {
        if (!ignore()) {
            if (!xtorModeler.isIgnorable()) {
                Object poppedXtor = contextStack.pop();
                assert xtorModeler.isTheXtor(poppedXtor);
            }
            xtorModeler.finish();
            xtorModeler = null;
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#qualifiedCtorId(java.lang.String)
     */
    public void qualifiedCtorId(String identifier) {
        if (!ignore()) {
            boolean onlyDeclaration = false;
            if (xtorModeler == null) {
                beginCtor();
                onlyDeclaration = true;
            }
            xtorModeler.setName(identifier);
            if (!xtorModeler.isIgnorable()) {
                assert xtorModeler.isTheXtor(contextStack.peek());
            }
            if (onlyDeclaration) {
                endXtor();
            }
        }
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#endDtorHead()
     */
    public void endDtorHead() {
        endXtor();
    }

    /*
     * @see org.argouml.language.cpp.reveng.Modeler#dtorDeclarator(java.lang.String)
     */
    public void dtorDeclarator(String qualifiedId) {
        if (!xtorModeler.isIgnorable()) {
            assert xtorModeler.isTheXtor(contextStack.peek());
        }
        xtorModeler.setName(qualifiedId);
    }

    public void beginMemberDeclarator() {
        Object theType = memberModeler.getType();
        attributeModeler = new AttributeModeler(contextStack.peek(),
            contextAccessSpecifier, theType, profile);
        contextStack.push(attributeModeler.getAttribute());
    }

    public void endMemberDeclarator() {
        if (getFacade().isAAttribute(contextStack.peek())) {
            attributeModeler.finish();
            assert attributeModeler.getAttribute() == contextStack.peek();
            contextStack.pop();
        }
    }

    public void beginMemberDeclaratorList() {
        // TODO
    }

    public void endMemberDeclaratorList() {
        // TODO
    }

}