MetafacadeLogicImpl.java

package org.andromda.cartridges.meta.metafacades;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.andromda.cartridges.meta.MetaProfile;
import org.andromda.core.metafacade.MetafacadeException;
import org.andromda.metafacades.uml.AssociationEndFacade;
import org.andromda.metafacades.uml.AttributeFacade;
import org.andromda.metafacades.uml.ClassifierFacade;
import org.andromda.metafacades.uml.DependencyFacade;
import org.andromda.metafacades.uml.GeneralizableElementFacade;
import org.andromda.metafacades.uml.GeneralizationFacade;
import org.andromda.metafacades.uml.ModelElementFacade;
import org.andromda.metafacades.uml.OperationFacade;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.Transformer;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * Metaclass facade implementation.
 *
 * @see org.andromda.cartridges.meta.metafacades.Metafacade
 * @author Bob Fields
 */
public class MetafacadeLogicImpl
    extends MetafacadeLogic
{
    private static final long serialVersionUID = 34L;
    /**
     * This defines the metamodel version package name (i.e.
     * org.andromda.metafacades.uml14, org.andromda.metafacades.um22, etc) used
     * by this cartridge to create the generated impl package name, if left
     * empty then the impl package will be the same as the metafacade package
     * (therefore we default to an empty name)
     */
    private static final String METAMODEL_VERSION_PACKAGE = "metamodelVersionPackage";
    private Map<ClassifierFacade, Collection<MethodData>> featureMap = null;
    /**
     * The logger instance.
     */
    private static final Logger logger = Logger.getLogger(MetafacadeLogicImpl.class);

    /**
     * @param metaObjectIn
     * @param context
     */
    public MetafacadeLogicImpl(
        Object metaObjectIn,
        String context)
    {
        super(metaObjectIn, context);
    }

    /**
     * Returns the class tagged with &lt;&lt;metaclass&gt;&gt;&gt; that is
     * connected to the metaobject via a dependency. If no metaclass is directly
     * connected, the method walks up the supertype hierarchy.
     *
     * @return the metaclass object
     */
    @Override
    protected ClassifierFacade handleGetMetaclass()
    {
        // delegate to recursive method
        return getMetaclass(this);
    }

    /**
     * Returns the class tagged with &lt;&lt;metaclass&gt;&gt; that is connected
     * to classifier via a dependency.
     *
     * @param classifier the source classifier
     * @return the metaclass object
     */
    private ClassifierFacade getMetaclass(ClassifierFacade classifier)
    {
        for (DependencyFacade dep : classifier.getSourceDependencies())
        {
            ClassifierFacade target = (ClassifierFacade)dep.getTargetElement();
            Collection<String> stereotypes = target.getStereotypeNames();
            if ((stereotypes != null) && (!stereotypes.isEmpty()))
            {
                String stereotypeName = stereotypes.iterator().next();
                if (stereotypeName.equals(MetaProfile.STEREOTYPE_METACLASS))
                {
                    return target;
                }
            }
        }

        ClassifierFacade superclass = (ClassifierFacade)classifier.getGeneralization();
        return (superclass != null) ? getMetaclass(superclass) : null;
    }

    /**
     * @see Metafacade#isMetaclassDirectDependency()
     */
    @Override
    protected boolean handleIsMetaclassDirectDependency()
    {
        boolean isMetaClassDirectDependency = false;
        Collection<DependencyFacade> dependencies = this.getSourceDependencies();
        if ((dependencies != null) && !dependencies.isEmpty())
        {
            // there should be only one.
            DependencyFacade dependency = dependencies.iterator().next();
            if (dependency != null)
            {
                ModelElementFacade targetElement = dependency.getTargetElement();
                if (targetElement != null)
                {
                    isMetaClassDirectDependency = targetElement.hasStereotype(MetaProfile.STEREOTYPE_METACLASS);
                }
            }
        }
        return isMetaClassDirectDependency;
    }

    /**
     * @see Metafacade#getLogicName()
     */
    @Override
    protected String handleGetLogicName()
    {
        return this.getName() + "Logic";
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getLogicImplName()
     */
    @Override
    protected String handleGetLogicImplName()
    {
        return this.getName() + "LogicImpl";
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getFullyQualifiedLogicImplName()
     */
    @Override
    protected String handleGetFullyQualifiedLogicImplName()
    {
        return this.getMetafacadeSupportClassName(this.getLogicImplName());
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getFullyQualifiedLogicName()
     */
    @Override
    protected String handleGetFullyQualifiedLogicName()
    {
        return this.getMetafacadeSupportClassName(this.getLogicName());
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getLogicFile()
     */
    @Override
    protected String handleGetLogicFile()
    {
        return this.getFullyQualifiedLogicName().replace('.', '/') + ".java";
    }

    /**
     * Gets the metamodel version package name (i.e.
     * org.andromda.metafacades.uml14, org.andromda.metafacades.um20, etc) used
     * by this cartridge to create the generated impl package name, if left
     * empty then the impl package will be the same as the metafacade package
     * (therefore we default to an empty name)
     */
    private String getMetaModelVersionPackage()
    {
        return ObjectUtils.toString(this.getConfiguredProperty(METAMODEL_VERSION_PACKAGE));
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getLogicPackageName()
     */
    @Override
    protected String handleGetLogicPackageName()
    {
        String packageName = this.getMetaModelVersionPackage();
        if (StringUtils.isEmpty(packageName))
        {
            packageName = this.getPackageName();
        }
        return packageName;
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getLogicImplFile()
     */
    @Override
    protected String handleGetLogicImplFile()
    {
        return this.getFullyQualifiedLogicImplName().replace('.', '/') + ".java";
    }

    /**
     * Creates a metafacade support class name from the given
     * <code>metamodelVersionPackage</code> (i.e. the package for the specific
     * meta model version). Support classes are the 'Logic' classes.
     *
     * @param name the name of the class to append to the package.
     * @return the new metafacade support class name.
     */
    private String getMetafacadeSupportClassName(String name)
    {
        StringBuilder fullyQualifiedName = new StringBuilder(this.getLogicPackageName());
        if (StringUtils.isNotBlank(fullyQualifiedName.toString()))
        {
            fullyQualifiedName.append('.');
            fullyQualifiedName.append(name);
        }
        return fullyQualifiedName.toString();
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.MetafacadeLogic#handleGetMethodDataForPSM(org.andromda.metafacades.uml.ClassifierFacade)
     */
    @Override
    protected Collection<MethodData> handleGetMethodDataForPSM(ClassifierFacade facade)
    {
        return this.getMethodDataForPSM(facade, true);
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getMethodDataForPSM()
     */
    @Override
    protected Collection<MethodData> handleGetMethodDataForPSM()
    {
        return this.getMethodDataForPSM(null, false);
    }

    /**
     * Return collection of all methods of all generalization classes for the classifier
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getMethodDataForPSM(boolean)
     */
    private final Collection<MethodData> getMethodDataForPSM(
        final ClassifierFacade facade,
        final boolean includeSuperclasses)
    {
        try
        {
            final Set<String> declarationSet = new LinkedHashSet<String>();
            if (this.featureMap == null)
            {
                this.featureMap = new HashMap<ClassifierFacade, Collection<MethodData>>();
                if (includeSuperclasses && this.getGeneralizations() != null)
                {
                    for (GeneralizableElementFacade general : this.getGeneralizations())
                    {
                        final Map<String, MethodData> methodDataMap = new HashMap<String, MethodData>();
                        final ClassifierFacade metafacade = (ClassifierFacade)general;
                        for (ClassifierFacade classifier = metafacade; classifier instanceof Metafacade;
                             classifier = (ClassifierFacade)classifier.getGeneralization())
                        {
                            this.getAllFeatures(methodDataMap, declarationSet, (Metafacade)classifier);
                        }
                        this.featureMap.put(metafacade, methodDataMap.values());
                    }
                }
            }
            final List<MethodData> result = new ArrayList<MethodData>();
            if (this.featureMap != null)
            {
                Collection<MethodData> features = this.featureMap.get(facade);
                if (features != null)
                {
                    result.addAll(features);
                }
            }
            if (!includeSuperclasses)
            {
                final Map<String, MethodData> methodDataMap = new HashMap<String, MethodData>();
                this.getAllFeatures(methodDataMap, declarationSet, this);
                result.addAll(methodDataMap.values());
            }
            Collections.sort(result);
            return result;
        }
        catch (Throwable th)
        {
            throw new RuntimeException(th);
        }
    }

    /**
     * Returns method data (name, visibility, type, doc) for each property and operation in the Metafacade
     * @param methodDataMap
     * @param declarationSet
     * @param facade
     */
    private final void getAllFeatures(
        final Map<String, MethodData> methodDataMap,
        final Set<String> declarationSet,
        final Metafacade facade)
    {
        try
        {
            final String methodVisibility = "public";
            final String indendation = "     * ";
            final String fullyQualifiedName = facade.getFullyQualifiedName();

            // translate UML attributes and association ends to getter methods
            for (final Object obj : facade.getProperties())
            {
                final ModelElementFacade property = (ModelElementFacade)obj;
                MethodData method = null;
                if (property instanceof AttributeFacade)
                {
                    final AttributeFacade attribute = (AttributeFacade)property;
                    method =
                        new MethodData(
                            fullyQualifiedName,
                            methodVisibility,
                            false,
                            attribute.getGetterSetterTypeName(),
                            attribute.getGetterName(),
                            attribute.getDocumentation(indendation));
                }
                else
                {
                    final AssociationEndFacade association = (AssociationEndFacade)property;
                    method =
                        new MethodData(
                            fullyQualifiedName,
                            methodVisibility,
                            false,
                            association.getGetterSetterTypeName(),
                            association.getGetterName(),
                            association.getDocumentation(indendation));
                }
                final String declaration = method.buildMethodDeclaration(true);

                // don't add the new method data if we already have the
                // declaration from a previous generalization.
                if (!declarationSet.contains(declaration))
                {
                    methodDataMap.put(
                        method.buildCharacteristicKey(),
                        method);
                    declarationSet.add(declaration);
                }
            }

            // translate UML operations to methods
            for (OperationFacade operation : facade.getOperations())
            {
                final UMLOperationData method = new UMLOperationData(fullyQualifiedName, operation);

                // don't add the new method data if we already have the
                // declaration from a previous generalization.
                final String declaration = method.buildMethodDeclaration(true);
                if (!declarationSet.contains(declaration))
                {
                    methodDataMap.put(
                        method.buildCharacteristicKey(),
                        method);
                    declarationSet.add(declaration);
                }
            }
        }
        catch (final Throwable throwable)
        {
            MetafacadeLogicImpl.logger.error(throwable);
            throw new MetafacadeException(throwable);
        }
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#isRequiresInheritanceDelegatation()
     */
    @Override
    protected boolean handleIsRequiresInheritanceDelegatation()
    {
        boolean requiresInheritanceDelegation = false;
        final ModelElementFacade superMetafacade = this.getGeneralization();
        if (superMetafacade != null)
        {
            requiresInheritanceDelegation =
                !superMetafacade.getPackageName().equals(this.getPackageName()) ||
                (this.getGeneralizations().size() > 1);
        }
        return requiresInheritanceDelegation;
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#isConstructorRequiresMetaclassCast()
     */
    @Override
    protected boolean handleIsConstructorRequiresMetaclassCast()
    {
        boolean requiresCast = false;
        final Metafacade superMetafacade = (Metafacade)this.getGeneralization();
        if (superMetafacade != null)
        {
            requiresCast = superMetafacade.isMetaclassDirectDependency() && !this.isRequiresInheritanceDelegatation();
        }
        return requiresCast;
    }

    /**
     * @see org.andromda.metafacades.uml.GeneralizableElementFacade#getGeneralizations()
     */
    @Override
    public Collection<GeneralizableElementFacade> getGeneralizations()
    {
        final List generalizations = new ArrayList(super.getGeneralizationLinks());
        Collections.sort(
            generalizations,
            new GeneralizationPrecedenceComparator());
        CollectionUtils.transform(
            generalizations,
            new Transformer()
            {
                public Object transform(final Object object)
                {
                    return ((GeneralizationFacade)object).getParent();
                }
            });
        CollectionUtils.filter(generalizations,
            new Predicate()
            {
                public boolean evaluate(final Object object)
                {
                    return object instanceof Metafacade;
                }
            });
        return generalizations;
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getGeneralizationCount()
     */
    @Override
    protected int handleGetGeneralizationCount()
    {
        int count = 0;
        final Collection<GeneralizableElementFacade> generalizations = this.getGeneralizations();
        if (generalizations != null)
        {
            count = generalizations.size();
        }
        return count;
    }

    /**
     * Used to sort metafacade generalizations by precedence.
     */
    static final class GeneralizationPrecedenceComparator
        implements Comparator
    {
        /**
         * @see java.util.Comparator#compare(Object, Object)
         */
        public int compare(
            Object objectA,
            Object objectB)
        {
            MetafacadeGeneralization a = (MetafacadeGeneralization)objectA;
            MetafacadeGeneralization b = (MetafacadeGeneralization)objectB;
            return a.getPrecedence().compareTo(b.getPrecedence());
        }
    }

    /**
     * @see org.andromda.cartridges.meta.metafacades.MetafacadeLogic#getAllParents()
     */
    @Override
    protected Collection<GeneralizableElementFacade> handleGetAllParents()
    {
        Set<GeneralizableElementFacade> allParents = new LinkedHashSet<GeneralizableElementFacade> ();
        final Collection<GeneralizableElementFacade> parents = this.getGeneralizations();
        allParents.addAll(parents);
        for (Object object : parents)
        {
            if (object instanceof Metafacade)
            {
                final Metafacade metafacade = (Metafacade)object;
                allParents.addAll(metafacade.getAllParents());
            }
        }
        return allParents;
    }
}