001package org.andromda.core.metafacade;
002
003import java.io.Serializable;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.List;
007import org.apache.commons.lang.StringUtils;
008import org.apache.commons.lang.builder.ToStringBuilder;
009import org.apache.log4j.Logger;
010
011/**
012 * Base class for all metafacades.
013 *
014 * @author <a href="http://www.mbohlen.de">Matthias Bohlen </a>
015 * @author Chad Brandon
016 * @author Wouter Zoons
017 * @author Bob Fields
018 */
019public class MetafacadeBase implements Serializable, Comparable
020{
021    private static final long serialVersionUID = 34L;
022    /**
023     * The meta object which this metafacade wraps.
024     */
025    private Object metaObject;
026
027    /**
028     * Constructs a new instance of this class with the given <code>metaObject</code>
029     * and <code>context</code>.  The metaObject is the meta model element which
030     * this metafacade insulates. The <code>context</code> is the name of the
031     * context for this metafacade instance.
032     *
033     * @param metaObjectIn the meta object.
034     * @param contextIn the context of this meta object.
035     */
036    public MetafacadeBase(
037        final Object metaObjectIn,
038        final String contextIn)
039    {
040        this.metaObject = metaObjectIn;
041        this.context = contextIn;
042    }
043
044    /**
045     * Retrieves the <code>owner</code> of this metafacade (for example: an operation owns its parameters, a class owns
046     * its attributes).
047     * <p>
048     * By default <code>null</code> is returned, however this method is overridden by subclasses which have a
049     * <code>parent</code> or <code>owner</code>. This is used to give the model validation messages more context as to
050     * where the validation error occurred. </p>
051     *
052     * @return the owner of this metafacade.
053     */
054    public Object getValidationOwner()
055    {
056        return null;
057    }
058
059    /**
060     * Retrieves the <code>name</code> of this metafacade used within the validation messages.
061     * <p>
062     * By default <code>null</code> is returned, however this method is overridden by subclasses model elements that do
063     * have a name. </p>
064     *
065     * @return the owner of this metafacade.
066     */
067    public String getValidationName()
068    {
069        return null;
070    }
071
072    /**
073     * Stores whether or not this metafacade has
074     * been initialized.
075     */
076    private boolean initialized = false;
077
078    /**
079     * Sets the flag indicating this metafacade has been initialized.
080     */
081    final void setInitialized()
082    {
083        this.initialized = true;
084    }
085
086    /**
087     * Indicates if this metafacade has been initialized.
088     *
089     * @return true/false
090     */
091    final boolean isInitialized()
092    {
093        return this.initialized;
094    }
095
096    /**
097     * Validates that this facade's meta object is in a valid state.
098     * <p>
099     * Validate is called during metafacade creation by the factory. In the lifecycle of a metafacade it is validated
100     * only once, this is enforced by the caching within the metafacade factory.</p>
101     *
102     * @param validationMessages any messages generated during validation.
103     */
104    public final void validate(final Collection<ModelValidationMessage> validationMessages)
105    {
106        this.validateInvariants(validationMessages);
107    }
108
109    /**
110     * <p>
111     * The logic of modeled OCL invariants from derived metafacades will be generated into this method and validation
112     * messages created and collected into the <code>messages</code> collection. This method is called by {@link #validate(Collection validationMessages)}
113     * </p>
114     * By default this method is empty. </p>
115     * @param messages Collection of org.andromda.core.metafacade.ModelValidationMessage
116     */
117    public void validateInvariants(final Collection<ModelValidationMessage> messages)
118    {
119        // By default this does nothing
120    }
121
122    /**
123     * A lifecycle method, providing the ability for sub classes to take any action after the factory has completely
124     * initialized a metafacade, but before it has been validated for completeness.
125     */
126    public void initialize()
127    {
128        // By default this does nothing
129    }
130
131    /**
132     * Returns one facade for a particular metaObject. Contacts the MetafacadeFactory to manufacture the proper
133     * metafacade. In certain cases <code>metaObject</code> can also be a metafacade instance; in that case the actual
134     * meta model element is retrieved from the metafacade and a metafacade is constructed from that.
135     *
136     * @param metaObjectIn the underlying meta model element. A metafacade is created for each.
137     * @return MetafacadeBase the facade
138     * @see MetafacadeFactory
139     */
140    protected MetafacadeBase shieldedElement(final Object metaObjectIn)
141    {
142        MetafacadeBase metafacade = null;
143        if (metaObjectIn != null)
144        {
145            final String contextIn = this.getContext();
146            metafacade = MetafacadeFactory.getInstance().createMetafacade(
147                    metaObjectIn,
148                    contextIn);
149
150            // - The metafacade we've just got may have been found in the cache.
151            //   If so, it can have an arbitrary context (because it's cached).
152            //   We now need to set the context once again, so that all
153            //   other metafacade mappings based on the context work as expected.
154            if(metafacade != null)
155            {
156                metafacade.resetMetafacadeContext(contextIn);
157            }
158        }
159        return metafacade;
160    }
161
162    /**
163     * Returns a collection of facades for a collection of metaobjects. Contacts the MetafacadeFactory to manufacture
164     * the proper facades.
165     *
166     * @param metaobjects the objects to decorate
167     * @return Collection of MetafacadeBase-derived objects
168     * @see MetafacadeFactory
169     */
170    protected List shieldedElements(final Collection metaobjects)
171    {
172        final List metafacades = new ArrayList();
173        if (metaobjects != null)
174        {
175            for (final Object metaobject : metaobjects)
176            {
177                metafacades.add(this.shieldedElement(metaobject));
178            }
179        }
180        return metafacades;
181    }
182
183    /**
184     * Stores the context for this metafacade
185     */
186    private String context = null;
187
188    /**
189     * Gets the context for this metafacade.
190     *
191     * @return the context name.
192     */
193    final String getContext()
194    {
195        String contextIn = this.context;
196        if (StringUtils.isBlank(contextIn))
197        {
198            contextIn = this.getMetafacadeName();
199        }
200        return contextIn;
201    }
202
203    /**
204     * Sets the context for this metafacade. This is used to pass the context along from a metafacade specializing this
205     * metafacade (since we use delegate inheritance between shared and non-shared metafacades), as well as to pass the
206     * context to a metafacade being created within another.
207     *
208     * @param contextIn the metafacade interface name representing the context.
209     * @see MetafacadeMapping#isContextRoot()
210     */
211    public void setMetafacadeContext(final String contextIn)
212    {
213        this.context = contextIn;
214    }
215
216    /**
217     * Resets the metafacade context after the metafacade was retrieved from the metafacade cache.
218     * DO NOT CALL THIS METHOD BY HAND, it is reserved for use in the MetafacadeFactory.
219     * @see org.andromda.core.metafacade.MetafacadeFactory
220     * @param contextIn the context defined by MetafacadeFactory
221     */
222    public void resetMetafacadeContext(String contextIn)
223    {
224        throw new IllegalStateException("Method resetMetafacadeContext() must be overridden by concrete metafacade class (" + this.getClass().getName() + ")! Please re-generate your metafacades using the new andromda-meta cartridge.");
225    }
226
227    /**
228     * Stores the namespace for this metafacade
229     */
230    private String metafacadeNamespace = null;
231
232    /**
233     * Gets the current namespace for this metafacade
234     *
235     * @return String
236     */
237    final String getMetafacadeNamespace()
238    {
239        return this.metafacadeNamespace;
240    }
241
242    /**
243     * Sets the namespace for this metafacade.
244     *
245     * @param namespaceIn
246     */
247    final void setNamespace(final String namespaceIn)
248    {
249        this.metafacadeNamespace = namespaceIn;
250    }
251
252    /**
253     * Returns true or false depending on whether the <code>property</code> is registered or not.
254     *
255     * @param property the name of the property to check.
256     * @return true/false on whether or not its registered.
257     */
258    protected boolean isConfiguredProperty(final String property)
259    {
260        return MetafacadeFactory.getInstance().isPropertyRegistered(
261            this,
262            property);
263    }
264
265    /**
266     * Gets a configured property from the container. Note that the configured property must be registered first.
267     * Needs to be public so that a metafacade reference passed to a utility class can call this method.
268     *
269     * @param property the property name
270     * @return Object the configured property instance (mappings, etc)
271     */
272    public Object getConfiguredProperty(final String property)
273    {
274        return MetafacadeFactory.getInstance().getRegisteredProperty(
275            this,
276            property);
277    }
278
279    /**
280     * Attempts to set the property with <code>name</code> having the specified <code>value</code> on this metafacade.
281     * @param nameIn
282     * @param value
283     */
284    protected void setProperty(
285        final String nameIn,
286        final Object value)
287    {
288        MetafacadeFactory.getInstance().registerProperty(
289            this.getMetafacadeName(),
290            nameIn,
291            value);
292    }
293
294    /**
295     * Gets the current meta model object for this metafacade. This is used from {@link MetafacadeFactory} when
296     * attempting to construct a metafacade from a metafacade. This allows us to get the meta object for this metafacade
297     * so that the meta object can be used instead.
298     *
299     * @return the underlying model's meta object instance.
300     */
301    public final Object getMetaObject()
302    {
303        return this.metaObject;
304    }
305
306    /**
307     * The metafacade logger instance.
308     */
309    protected Logger logger;
310
311    /**
312     * Package-local setter, called by facade factory. Sets the logger to use inside the facade's code.
313     *
314     * @param loggerIn the logger to set
315     */
316    final void setLogger(final Logger loggerIn)
317    {
318        this.logger = loggerIn;
319    }
320
321    /**
322     * The flag indicating whether or not this metafacade is a context root.
323     */
324    protected boolean contextRoot = false;
325
326    /**
327     * Sets whether or not this metafacade represents a contextRoot. If it does represent a context root, then {@link
328     * #getMetafacadeContext()}returns the metafacade interface for this metafacade, otherwise the regular
329     * <code>context</code> is returned.
330     *
331     * @param contextRootIn
332     */
333    final void setContextRoot(final boolean contextRootIn)
334    {
335        this.contextRoot = contextRootIn;
336    }
337
338    /**
339     * Gets the <code>context</code> for this metafacade. This is either the <code>contextRoot</code> (if one exists),
340     * or the regular <code>context</code>.
341     *
342     * @return the metafacade's context.
343     */
344    public String getMetafacadeContext()
345    {
346        String metafacadeContext = this.getContext();
347        if (this.contextRoot)
348        {
349            metafacadeContext = this.getMetafacadeName();
350        }
351        return metafacadeContext;
352    }
353
354    /**
355     * Stores the name of the interface for this metafacade
356     */
357    private String metafacadeName = null;
358
359    /**
360     * Gets the name for this metafacade.
361     *
362     * @return the metafacade's name.
363     */
364    final String getMetafacadeName()
365    {
366        if (this.metafacadeName == null)
367        {
368            this.metafacadeName = MetafacadeImpls.instance().getMetafacadeClass(this.getClass().getName()).getName();
369        }
370        return this.metafacadeName;
371    }
372
373    /**
374     * @see Object#equals(Object)
375     */
376    @Override
377    public boolean equals(Object object)
378    {
379        boolean equals = false;
380        if (object instanceof MetafacadeBase)
381        {
382            MetafacadeBase that = (MetafacadeBase)object;
383            equals = this.metaObject.equals(that.metaObject);
384        }
385        return equals;
386    }
387
388    /**
389     * @see Object#hashCode()
390     */
391    @Override
392    public int hashCode()
393    {
394        return this.metaObject.hashCode();
395    }
396
397    /**
398     * In order to speed up the check for this property (which will happen many times), we cache it :-)
399     */
400    private Boolean metafacadePropertyCachingEnabled = null;
401
402    /**
403     * A check to verify whether or not to make use of metafacade property caching. This method check if the {@link
404     * MetafacadeProperties#ENABLE_METAFACADE_PROPERTY_CACHING} namespace property has been set, if this is not the case
405     * then the caching will be enabled by default.
406     * @return this.metafacadePropertyCachingEnabled.booleanValue()
407     */
408    public final boolean isMetafacadePropertyCachingEnabled()
409    {
410        if (this.metafacadePropertyCachingEnabled == null)
411        {
412            final String enableCache =
413                (String)this.getConfiguredProperty(MetafacadeProperties.ENABLE_METAFACADE_PROPERTY_CACHING);
414            this.metafacadePropertyCachingEnabled = Boolean.valueOf(enableCache);
415        }
416        return this.metafacadePropertyCachingEnabled;
417    }
418
419    /**
420     * The instance of this class as the appropriate metafacade instance.
421     */
422    private MetafacadeBase THIS = null;
423
424    /**
425     * The metafacade instance of <code>this</code>.  This should be used when
426     * you'd need to check if <code>this</code> was an instance of a given metafacade.
427     * For example: <code>THIS() instanceof SomeMetafacade</code>.
428     *
429     * This <strong>MUST</strong> be used instead of <em>this</em> in order to access the correct
430     * metafacade instance in the hierarchy (since we use delegate inheritance).
431     * @return this.shieldedElement(this.metaObject)
432     */
433    protected final MetafacadeBase THIS()
434    {
435        return this.THIS == null ? this.THIS = this.shieldedElement(this.metaObject) : this.THIS;
436    }
437
438    /**
439     * @see Object#toString()
440     */
441    @Override
442    public String toString()
443    {
444        return super.toString() + '[' + this.metafacadeName + ": " + this.metaObject.getClass().getName() + ']';
445    }
446
447    /**
448     * Allow sorting and use in TreeSet. ValidationName is overridden in descendants.
449     * @see Comparable#compareTo(Object)
450     */
451    public int compareTo(Object object)
452    {
453        if (object==null || !(object instanceof MetafacadeBase))
454        {
455            return -1;
456        }
457        MetafacadeBase metafacade = (MetafacadeBase)object;
458        if (metafacade.getValidationName()==null)
459        {
460            return -1;
461        }
462        return metafacade.getValidationName().compareTo(this.getValidationName());
463    }
464
465    /**
466     * For debug purposes, when we need more than just class and metaclass name
467     * @return String representation of all properties including metaObject info
468     * @see Object#toString()
469     */
470    public String getDebug()
471    {
472        return ToStringBuilder.reflectionToString(this) + '[' + this.metafacadeName + ": " + this.metaObject.getClass().getName() + ": " + ToStringBuilder.reflectionToString(this.metaObject) + ']';
473    }
474}