001package org.andromda.cartridges.meta.metafacades;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.Collections;
006import java.util.Comparator;
007import java.util.HashMap;
008import java.util.LinkedHashSet;
009import java.util.List;
010import java.util.Map;
011import java.util.Set;
012import org.andromda.cartridges.meta.MetaProfile;
013import org.andromda.core.metafacade.MetafacadeException;
014import org.andromda.metafacades.uml.AssociationEndFacade;
015import org.andromda.metafacades.uml.AttributeFacade;
016import org.andromda.metafacades.uml.ClassifierFacade;
017import org.andromda.metafacades.uml.DependencyFacade;
018import org.andromda.metafacades.uml.GeneralizableElementFacade;
019import org.andromda.metafacades.uml.GeneralizationFacade;
020import org.andromda.metafacades.uml.ModelElementFacade;
021import org.andromda.metafacades.uml.OperationFacade;
022import org.apache.commons.collections.CollectionUtils;
023import org.apache.commons.collections.Predicate;
024import org.apache.commons.collections.Transformer;
025import org.apache.commons.lang.ObjectUtils;
026import org.apache.commons.lang.StringUtils;
027import org.apache.log4j.Logger;
028
029/**
030 * Metaclass facade implementation.
031 *
032 * @see org.andromda.cartridges.meta.metafacades.Metafacade
033 * @author Bob Fields
034 */
035public class MetafacadeLogicImpl
036    extends MetafacadeLogic
037{
038    private static final long serialVersionUID = 34L;
039    /**
040     * This defines the metamodel version package name (i.e.
041     * org.andromda.metafacades.uml14, org.andromda.metafacades.um22, etc) used
042     * by this cartridge to create the generated impl package name, if left
043     * empty then the impl package will be the same as the metafacade package
044     * (therefore we default to an empty name)
045     */
046    private static final String METAMODEL_VERSION_PACKAGE = "metamodelVersionPackage";
047    private Map<ClassifierFacade, Collection<MethodData>> featureMap = null;
048    /**
049     * The logger instance.
050     */
051    private static final Logger logger = Logger.getLogger(MetafacadeLogicImpl.class);
052
053    /**
054     * @param metaObjectIn
055     * @param context
056     */
057    public MetafacadeLogicImpl(
058        Object metaObjectIn,
059        String context)
060    {
061        super(metaObjectIn, context);
062    }
063
064    /**
065     * Returns the class tagged with &lt;&lt;metaclass&gt;&gt;&gt; that is
066     * connected to the metaobject via a dependency. If no metaclass is directly
067     * connected, the method walks up the supertype hierarchy.
068     *
069     * @return the metaclass object
070     */
071    @Override
072    protected ClassifierFacade handleGetMetaclass()
073    {
074        // delegate to recursive method
075        return getMetaclass(this);
076    }
077
078    /**
079     * Returns the class tagged with &lt;&lt;metaclass&gt;&gt; that is connected
080     * to classifier via a dependency.
081     *
082     * @param classifier the source classifier
083     * @return the metaclass object
084     */
085    private ClassifierFacade getMetaclass(ClassifierFacade classifier)
086    {
087        for (DependencyFacade dep : classifier.getSourceDependencies())
088        {
089            ClassifierFacade target = (ClassifierFacade)dep.getTargetElement();
090            Collection<String> stereotypes = target.getStereotypeNames();
091            if ((stereotypes != null) && (!stereotypes.isEmpty()))
092            {
093                String stereotypeName = stereotypes.iterator().next();
094                if (stereotypeName.equals(MetaProfile.STEREOTYPE_METACLASS))
095                {
096                    return target;
097                }
098            }
099        }
100
101        ClassifierFacade superclass = (ClassifierFacade)classifier.getGeneralization();
102        return (superclass != null) ? getMetaclass(superclass) : null;
103    }
104
105    /**
106     * @see Metafacade#isMetaclassDirectDependency()
107     */
108    @Override
109    protected boolean handleIsMetaclassDirectDependency()
110    {
111        boolean isMetaClassDirectDependency = false;
112        Collection<DependencyFacade> dependencies = this.getSourceDependencies();
113        if ((dependencies != null) && !dependencies.isEmpty())
114        {
115            // there should be only one.
116            DependencyFacade dependency = dependencies.iterator().next();
117            if (dependency != null)
118            {
119                ModelElementFacade targetElement = dependency.getTargetElement();
120                if (targetElement != null)
121                {
122                    isMetaClassDirectDependency = targetElement.hasStereotype(MetaProfile.STEREOTYPE_METACLASS);
123                }
124            }
125        }
126        return isMetaClassDirectDependency;
127    }
128
129    /**
130     * @see Metafacade#getLogicName()
131     */
132    @Override
133    protected String handleGetLogicName()
134    {
135        return this.getName() + "Logic";
136    }
137
138    /**
139     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getLogicImplName()
140     */
141    @Override
142    protected String handleGetLogicImplName()
143    {
144        return this.getName() + "LogicImpl";
145    }
146
147    /**
148     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getFullyQualifiedLogicImplName()
149     */
150    @Override
151    protected String handleGetFullyQualifiedLogicImplName()
152    {
153        return this.getMetafacadeSupportClassName(this.getLogicImplName());
154    }
155
156    /**
157     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getFullyQualifiedLogicName()
158     */
159    @Override
160    protected String handleGetFullyQualifiedLogicName()
161    {
162        return this.getMetafacadeSupportClassName(this.getLogicName());
163    }
164
165    /**
166     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getLogicFile()
167     */
168    @Override
169    protected String handleGetLogicFile()
170    {
171        return this.getFullyQualifiedLogicName().replace('.', '/') + ".java";
172    }
173
174    /**
175     * Gets the metamodel version package name (i.e.
176     * org.andromda.metafacades.uml14, org.andromda.metafacades.um20, etc) used
177     * by this cartridge to create the generated impl package name, if left
178     * empty then the impl package will be the same as the metafacade package
179     * (therefore we default to an empty name)
180     */
181    private String getMetaModelVersionPackage()
182    {
183        return ObjectUtils.toString(this.getConfiguredProperty(METAMODEL_VERSION_PACKAGE));
184    }
185
186    /**
187     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getLogicPackageName()
188     */
189    @Override
190    protected String handleGetLogicPackageName()
191    {
192        String packageName = this.getMetaModelVersionPackage();
193        if (StringUtils.isEmpty(packageName))
194        {
195            packageName = this.getPackageName();
196        }
197        return packageName;
198    }
199
200    /**
201     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getLogicImplFile()
202     */
203    @Override
204    protected String handleGetLogicImplFile()
205    {
206        return this.getFullyQualifiedLogicImplName().replace('.', '/') + ".java";
207    }
208
209    /**
210     * Creates a metafacade support class name from the given
211     * <code>metamodelVersionPackage</code> (i.e. the package for the specific
212     * meta model version). Support classes are the 'Logic' classes.
213     *
214     * @param name the name of the class to append to the package.
215     * @return the new metafacade support class name.
216     */
217    private String getMetafacadeSupportClassName(String name)
218    {
219        StringBuilder fullyQualifiedName = new StringBuilder(this.getLogicPackageName());
220        if (StringUtils.isNotBlank(fullyQualifiedName.toString()))
221        {
222            fullyQualifiedName.append('.');
223            fullyQualifiedName.append(name);
224        }
225        return fullyQualifiedName.toString();
226    }
227
228    /**
229     * @see org.andromda.cartridges.meta.metafacades.MetafacadeLogic#handleGetMethodDataForPSM(org.andromda.metafacades.uml.ClassifierFacade)
230     */
231    @Override
232    protected Collection<MethodData> handleGetMethodDataForPSM(ClassifierFacade facade)
233    {
234        return this.getMethodDataForPSM(facade, true);
235    }
236
237    /**
238     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getMethodDataForPSM()
239     */
240    @Override
241    protected Collection<MethodData> handleGetMethodDataForPSM()
242    {
243        return this.getMethodDataForPSM(null, false);
244    }
245
246    /**
247     * Return collection of all methods of all generalization classes for the classifier
248     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getMethodDataForPSM(boolean)
249     */
250    private final Collection<MethodData> getMethodDataForPSM(
251        final ClassifierFacade facade,
252        final boolean includeSuperclasses)
253    {
254        try
255        {
256            final Set<String> declarationSet = new LinkedHashSet<String>();
257            if (this.featureMap == null)
258            {
259                this.featureMap = new HashMap<ClassifierFacade, Collection<MethodData>>();
260                if (includeSuperclasses && this.getGeneralizations() != null)
261                {
262                    for (GeneralizableElementFacade general : this.getGeneralizations())
263                    {
264                        final Map<String, MethodData> methodDataMap = new HashMap<String, MethodData>();
265                        final ClassifierFacade metafacade = (ClassifierFacade)general;
266                        for (ClassifierFacade classifier = metafacade; classifier instanceof Metafacade;
267                             classifier = (ClassifierFacade)classifier.getGeneralization())
268                        {
269                            this.getAllFeatures(methodDataMap, declarationSet, (Metafacade)classifier);
270                        }
271                        this.featureMap.put(metafacade, methodDataMap.values());
272                    }
273                }
274            }
275            final List<MethodData> result = new ArrayList<MethodData>();
276            if (this.featureMap != null)
277            {
278                Collection<MethodData> features = this.featureMap.get(facade);
279                if (features != null)
280                {
281                    result.addAll(features);
282                }
283            }
284            if (!includeSuperclasses)
285            {
286                final Map<String, MethodData> methodDataMap = new HashMap<String, MethodData>();
287                this.getAllFeatures(methodDataMap, declarationSet, this);
288                result.addAll(methodDataMap.values());
289            }
290            Collections.sort(result);
291            return result;
292        }
293        catch (Throwable th)
294        {
295            throw new RuntimeException(th);
296        }
297    }
298
299    /**
300     * Returns method data (name, visibility, type, doc) for each property and operation in the Metafacade
301     * @param methodDataMap
302     * @param declarationSet
303     * @param facade
304     */
305    private final void getAllFeatures(
306        final Map<String, MethodData> methodDataMap,
307        final Set<String> declarationSet,
308        final Metafacade facade)
309    {
310        try
311        {
312            final String methodVisibility = "public";
313            final String indendation = "     * ";
314            final String fullyQualifiedName = facade.getFullyQualifiedName();
315
316            // translate UML attributes and association ends to getter methods
317            for (final Object obj : facade.getProperties())
318            {
319                final ModelElementFacade property = (ModelElementFacade)obj;
320                MethodData method = null;
321                if (property instanceof AttributeFacade)
322                {
323                    final AttributeFacade attribute = (AttributeFacade)property;
324                    method =
325                        new MethodData(
326                            fullyQualifiedName,
327                            methodVisibility,
328                            false,
329                            attribute.getGetterSetterTypeName(),
330                            attribute.getGetterName(),
331                            attribute.getDocumentation(indendation));
332                }
333                else
334                {
335                    final AssociationEndFacade association = (AssociationEndFacade)property;
336                    method =
337                        new MethodData(
338                            fullyQualifiedName,
339                            methodVisibility,
340                            false,
341                            association.getGetterSetterTypeName(),
342                            association.getGetterName(),
343                            association.getDocumentation(indendation));
344                }
345                final String declaration = method.buildMethodDeclaration(true);
346
347                // don't add the new method data if we already have the
348                // declaration from a previous generalization.
349                if (!declarationSet.contains(declaration))
350                {
351                    methodDataMap.put(
352                        method.buildCharacteristicKey(),
353                        method);
354                    declarationSet.add(declaration);
355                }
356            }
357
358            // translate UML operations to methods
359            for (OperationFacade operation : facade.getOperations())
360            {
361                final UMLOperationData method = new UMLOperationData(fullyQualifiedName, operation);
362
363                // don't add the new method data if we already have the
364                // declaration from a previous generalization.
365                final String declaration = method.buildMethodDeclaration(true);
366                if (!declarationSet.contains(declaration))
367                {
368                    methodDataMap.put(
369                        method.buildCharacteristicKey(),
370                        method);
371                    declarationSet.add(declaration);
372                }
373            }
374        }
375        catch (final Throwable throwable)
376        {
377            MetafacadeLogicImpl.logger.error(throwable);
378            throw new MetafacadeException(throwable);
379        }
380    }
381
382    /**
383     * @see org.andromda.cartridges.meta.metafacades.Metafacade#isRequiresInheritanceDelegatation()
384     */
385    @Override
386    protected boolean handleIsRequiresInheritanceDelegatation()
387    {
388        boolean requiresInheritanceDelegation = false;
389        final ModelElementFacade superMetafacade = this.getGeneralization();
390        if (superMetafacade != null)
391        {
392            requiresInheritanceDelegation =
393                !superMetafacade.getPackageName().equals(this.getPackageName()) ||
394                (this.getGeneralizations().size() > 1);
395        }
396        return requiresInheritanceDelegation;
397    }
398
399    /**
400     * @see org.andromda.cartridges.meta.metafacades.Metafacade#isConstructorRequiresMetaclassCast()
401     */
402    @Override
403    protected boolean handleIsConstructorRequiresMetaclassCast()
404    {
405        boolean requiresCast = false;
406        final Metafacade superMetafacade = (Metafacade)this.getGeneralization();
407        if (superMetafacade != null)
408        {
409            requiresCast = superMetafacade.isMetaclassDirectDependency() && !this.isRequiresInheritanceDelegatation();
410        }
411        return requiresCast;
412    }
413
414    /**
415     * @see org.andromda.metafacades.uml.GeneralizableElementFacade#getGeneralizations()
416     */
417    @Override
418    public Collection<GeneralizableElementFacade> getGeneralizations()
419    {
420        final List generalizations = new ArrayList(super.getGeneralizationLinks());
421        Collections.sort(
422            generalizations,
423            new GeneralizationPrecedenceComparator());
424        CollectionUtils.transform(
425            generalizations,
426            new Transformer()
427            {
428                public Object transform(final Object object)
429                {
430                    return ((GeneralizationFacade)object).getParent();
431                }
432            });
433        CollectionUtils.filter(generalizations,
434            new Predicate()
435            {
436                public boolean evaluate(final Object object)
437                {
438                    return object instanceof Metafacade;
439                }
440            });
441        return generalizations;
442    }
443
444    /**
445     * @see org.andromda.cartridges.meta.metafacades.Metafacade#getGeneralizationCount()
446     */
447    @Override
448    protected int handleGetGeneralizationCount()
449    {
450        int count = 0;
451        final Collection<GeneralizableElementFacade> generalizations = this.getGeneralizations();
452        if (generalizations != null)
453        {
454            count = generalizations.size();
455        }
456        return count;
457    }
458
459    /**
460     * Used to sort metafacade generalizations by precedence.
461     */
462    static final class GeneralizationPrecedenceComparator
463        implements Comparator
464    {
465        /**
466         * @see java.util.Comparator#compare(Object, Object)
467         */
468        public int compare(
469            Object objectA,
470            Object objectB)
471        {
472            MetafacadeGeneralization a = (MetafacadeGeneralization)objectA;
473            MetafacadeGeneralization b = (MetafacadeGeneralization)objectB;
474            return a.getPrecedence().compareTo(b.getPrecedence());
475        }
476    }
477
478    /**
479     * @see org.andromda.cartridges.meta.metafacades.MetafacadeLogic#getAllParents()
480     */
481    @Override
482    protected Collection<GeneralizableElementFacade> handleGetAllParents()
483    {
484        Set<GeneralizableElementFacade> allParents = new LinkedHashSet<GeneralizableElementFacade> ();
485        final Collection<GeneralizableElementFacade> parents = this.getGeneralizations();
486        allParents.addAll(parents);
487        for (Object object : parents)
488        {
489            if (object instanceof Metafacade)
490            {
491                final Metafacade metafacade = (Metafacade)object;
492                allParents.addAll(metafacade.getAllParents());
493            }
494        }
495        return allParents;
496    }
497}