001package org.andromda.core.cartridge.template;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.Iterator;
006import org.andromda.core.common.ClassUtils;
007import org.andromda.core.common.ExceptionUtils;
008import org.andromda.core.common.Introspector;
009import org.andromda.core.metafacade.MetafacadeBase;
010import org.andromda.core.profile.Profile;
011import org.apache.commons.lang.StringUtils;
012
013/**
014 * Represents a single template <modelElement/> nested within the <modelElements/> element. It stores the
015 * actual metafacade instances which match the model element criteria (i.e. stereotype, type, etc) defined by this
016 * instance.
017 *
018 * @author Chad Brandon
019 * @author Bob Fields
020 * @see ModelElements
021 */
022public class ModelElement
023{
024    private String stereotype;
025
026    /**
027     * Gets the stereotype of this modelElement.
028     *
029     * @return Returns the stereotype.
030     */
031    public String getStereotype()
032    {
033        return Profile.instance().get(this.stereotype);
034    }
035
036    /**
037     * Returns <code>true</code> or <code>false</code> depending on whether or not this model element has a stereotype
038     * defined.
039     *
040     * @return true/false
041     */
042    public boolean hasStereotype()
043    {
044        return this.stereotype != null;
045    }
046
047    /**
048     * Stores the types defined for this model element.
049     */
050    private final Collection<Type> types = new ArrayList<Type>();
051
052    /**
053     * Gets all types associated with this model element.
054     *
055     * @return the collection of types.
056     */
057    public Collection<Type> getTypes()
058    {
059        return this.types;
060    }
061
062    /**
063     * Returns <code>true</code> or <code>false</code> depending on whether or not this model element has any type
064     * elements defined.
065     *
066     * @return true/false
067     */
068    public boolean hasTypes()
069    {
070        return !this.getTypes().isEmpty();
071    }
072
073    /**
074     * Sets the stereotype of the ModelElement.
075     *
076     * @param stereotype The stereotype to set.
077     */
078    public void setStereotype(final String stereotype)
079    {
080        this.stereotype = stereotype;
081        ExceptionUtils.checkEmpty("stereotype", this.stereotype);
082    }
083
084    /**
085     * Adds the <code>type</code> to the collection of types belonging to this model element.
086     *
087     * @param type the {@link Type}instance.
088     */
089    public void addType(final Type type)
090    {
091        ExceptionUtils.checkNull("type", type);
092        this.types.add(type);
093    }
094
095    /**
096     * Stores the name of the variable for this model element.
097     */
098    private String variable;
099
100    /**
101     * Gets the variable stereotype of this modelElement (this is what is made available to a template during
102     * processing).
103     *
104     * @return Returns the variable.
105     */
106    public String getVariable()
107    {
108        return this.variable;
109    }
110
111    /**
112     * Sets the variable name.
113     *
114     * @param variable The variable to set.
115     */
116    public void setVariable(final String variable)
117    {
118        this.variable = StringUtils.trimToEmpty(variable);
119    }
120
121    /**
122     * The metafacades for this model element.
123     */
124    private Collection<MetafacadeBase> metafacades = new ArrayList<MetafacadeBase>();
125
126    /**
127     * Sets the current metafacades that belong to this ModelElement instance.
128     *
129     * @param metafacades the collection of metafacades
130     */
131    public void setMetafacades(final Collection<MetafacadeBase> metafacades)
132    {
133        ExceptionUtils.checkNull("metafacades", metafacades);
134        this.metafacades = metafacades;
135        this.applyTypeFiltering();
136    }
137
138    /**
139     * Gets the metafacades that belong to this ModelElement instance. These are the actual elements from the model.
140     *
141     * @return the collection of metafacades.
142     */
143    public Collection<MetafacadeBase> getMetafacades()
144    {
145        return this.metafacades;
146    }
147
148    /**
149     * Applies any filtering by any types specified within this model element.
150     */
151    private void applyTypeFiltering()
152    {
153        if (this.hasTypes())
154        {
155            for (final Iterator iterator = this.metafacades.iterator(); iterator.hasNext();)
156            {
157                if (!accept(iterator.next()))
158                {
159                    iterator.remove();
160                }
161            }
162        }
163    }
164
165    /**
166     * Checks the <code>object</code> to see whether or not its acceptable. It matches on the types and each type's
167     * properties. <strong>NOTE:</strong> protected visibility to improve performance from within {@link
168     * #applyTypeFiltering()}
169     *
170     * @param metafacade the metafacade to check
171     * @return true/false
172     */
173    private boolean accept(final Object metafacade)
174    {
175        boolean accept = true;
176        for (Type type : this.types)
177        {
178            if (StringUtils.isNotBlank(type.getName()))
179            {
180                try
181                {
182                    accept = ClassUtils.loadClass(type.getName()).isAssignableFrom(metafacade.getClass());
183
184                    // if the type matches the name, continue
185                    if (accept)
186                    {
187                        for (Type.Property property : type.getProperties())
188                        {
189                            accept =
190                                Introspector.instance().containsValidProperty(
191                                    metafacade,
192                                    property.getName(),
193                                    property.getValue());
194                            if (!accept)
195                            {
196                                // break out of the loop on the first invalid
197                                // property since all properties should be valid.
198                                break;
199                            }
200                        }
201                    }
202                }
203                catch (final Throwable throwable)
204                {
205                    accept = false;
206                }
207            }
208        }
209        return accept;
210    }
211}