1 package org.andromda.core.metafacade;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashSet;
6 import java.util.Iterator;
7 import java.util.LinkedHashMap;
8 import java.util.LinkedHashSet;
9 import java.util.List;
10 import java.util.ListIterator;
11 import java.util.Map;
12 import java.util.Set;
13 import org.andromda.core.common.ClassUtils;
14 import org.andromda.core.profile.Profile;
15 import org.apache.commons.lang.StringUtils;
16
17 /**
18 * A meta facade mapping class. This class is a child of {@link MetafacadeMappings}
19 * (that is: instances of this class below to an instance of {@link MetafacadeMappings}).
20 *
21 * @author Chad Brandon
22 * @author Bob Fields
23 */
24 public class MetafacadeMapping
25 {
26 /**
27 * The meta facade for which this mapping applies.
28 */
29 private Class metafacadeClass = null;
30
31 /**
32 * Gets the metafacadeClass for this mapping.
33 *
34 * @return Returns the metafacadeClass.
35 */
36 public Class getMetafacadeClass()
37 {
38 return metafacadeClass;
39 }
40
41 /**
42 * Sets the metafacadeClassName for this mapping.
43 *
44 * @param metafacadeClassName The name of the metafacade class to set.
45 */
46 public void setMetafacadeClassName(final String metafacadeClassName)
47 {
48 try
49 {
50 this.metafacadeClass = ClassUtils.loadClass(StringUtils.trimToEmpty(metafacadeClassName));
51 }
52 catch (final Throwable throwable)
53 {
54 throw new MetafacadeMappingsException(throwable);
55 }
56 }
57
58 /**
59 * The names of the mapping classes for which this mapping applies. The {@link #context},{@link #stereotypes}and this
60 * names make up the identifying key for this mapping.
61 */
62 private Set<String> mappingClassNames = new HashSet<String>();
63
64 /**
65 * Gets the names of the metaobject classes used for this mapping.
66 *
67 * @return Returns the mappingClassNames.
68 */
69 protected Set<String> getMappingClassNames()
70 {
71 // - if we have a mappingClassName defined, we use it
72 if (this.mappingClassNames.isEmpty())
73 {
74 // - attempt to get the inherited mapping since it doesn't exist on this class
75 this.mappingClassNames = MetafacadeUtils.getInheritedMappingClassNames(this);
76 }
77 return this.mappingClassNames;
78 }
79
80 /**
81 * Indicates whether or not the mapping class has been present.
82 *
83 * @return whether or not the mapping class is present in this mapping.
84 */
85 final boolean isMappingClassNamePresent()
86 {
87 return !this.mappingClassNames.isEmpty();
88 }
89
90 /**
91 * The name of the metaobject class to use for this mapping.
92 *
93 * @param mappingClassName The mappingClassName to set.
94 */
95 public void setMappingClassName(final String mappingClassName)
96 {
97 if(!StringUtils.isBlank(mappingClassName))
98 {
99 this.mappingClassNames.clear();
100 this.mappingClassNames.add(StringUtils.trimToEmpty(mappingClassName));
101 }
102 }
103
104 /**
105 * Whether or not this mapping represents a <code>contextRoot</code>.
106 */
107 private boolean contextRoot = false;
108
109 /**
110 * <p>
111 * Gets whether or not this mapping represents a <code>contextRoot</code>, by default a mapping is <strong>NOT
112 * </strong> a contextRoot. You'll want to specify this as true when other metafacades need to be created within the
113 * context of this metafacade. </p>
114 *
115 * @return Returns the contextRoot.
116 */
117 public boolean isContextRoot()
118 {
119 return this.contextRoot;
120 }
121
122 /**
123 * Sets the name of the <code>contextRoot</code> for this mapping.
124 *
125 * @param contextRoot The contextRoot to set.
126 * @see #isContextRoot()
127 */
128 public void setContextRoot(final boolean contextRoot)
129 {
130 this.contextRoot = contextRoot;
131 }
132
133 /**
134 * The stereotypes to which this mapping applies (all stereotypes must be present for this mapping to apply).
135 */
136 private final List<String> stereotypes = new ArrayList<String>();
137
138 /**
139 * Adds a <code>stereotype</code> to the stereotypes.
140 *
141 * @param stereotype
142 */
143 public void addStereotype(final String stereotype)
144 {
145 this.stereotypes.add(stereotype);
146 }
147
148 /**
149 * Gets the stereotypes which apply to this mapping.
150 *
151 * @return the names of the stereotypes
152 */
153 final List<String> getStereotypes()
154 {
155 for (final ListIterator<String> iterator = this.stereotypes.listIterator(); iterator.hasNext();)
156 {
157 iterator.set(Profile.instance().get(iterator.next()));
158 }
159 return this.stereotypes;
160 }
161
162 /**
163 * Indicates whether or not this mapping has any stereotypes defined.
164 *
165 * @return true/false
166 */
167 final boolean hasStereotypes()
168 {
169 return !this.stereotypes.isEmpty();
170 }
171
172 /**
173 * Used to hold references to language mapping classes.
174 */
175 private final Collection<String> propertyReferences = new LinkedHashSet<String>();
176
177 /**
178 * Adds a mapping property reference. These are used to populate metafacade impl classes with mapping files, etc.
179 * The property reference applies to the given mapping.
180 *
181 * @param reference the name of the reference.
182 * @see MetafacadeMappings#addPropertyReference(String)
183 */
184 public void addPropertyReference(final String reference)
185 {
186 this.propertyReferences.add(reference);
187 }
188
189 /**
190 * Returns all mapping references for this MetafacadeMapping instance.
191 * @return this.propertyReferences
192 */
193 public Collection<String> getPropertyReferences()
194 {
195 return this.propertyReferences;
196 }
197
198 /**
199 * Used to hold the properties that should apply to the mapping element.
200 */
201 private PropertyGroup mappingProperties = null;
202
203 /**
204 * Adds a mapping property. This are used to narrow the metafacade to which the mapping can apply. The properties
205 * must exist and must evaluate to the specified value if given for the mapping to match.
206 *
207 * @param name the name of the reference.
208 * @param value the default value of the property reference.
209 */
210 public void addMappingProperty(
211 final String name,
212 final String value)
213 {
214 if (value != null)
215 {
216 if (this.mappingProperties == null)
217 {
218 this.mappingProperties = new PropertyGroup();
219
220 // we add the mapping properties to the mappingPropertyGroups
221 // collection only once
222 this.mappingPropertyGroups.add(this.mappingProperties);
223 }
224 this.mappingProperties.addProperty(new Property(
225 name,
226 value));
227 }
228 }
229
230 /**
231 * Stores a collection of all property groups added through {@link #addPropertyReferences(java.util.Collection)}. These are
232 * property groups added from other mappings that return true when executing {@link #match(MetafacadeMapping)}.
233 */
234 private final Collection<PropertyGroup> mappingPropertyGroups = new ArrayList<PropertyGroup>();
235
236 /**
237 * Adds the <code>propertyGroup</code> to the existing mapping property groups within this mapping.
238 *
239 * @param propertyGroup a property group for this mapping
240 */
241 final void addMappingPropertyGroup(final PropertyGroup propertyGroup)
242 {
243 this.mappingPropertyGroups.add(propertyGroup);
244 }
245
246 /**
247 * Returns all mapping property groups for this MetafacadeMapping instance.
248 * @return this.mappingPropertyGroups
249 */
250 final Collection<PropertyGroup> getMappingPropertyGroups()
251 {
252 return this.mappingPropertyGroups;
253 }
254
255 /**
256 * Gets the mapping properties associated this this mapping directly (contained within a {@link
257 * PropertyGroup}instance).
258 *
259 * @return the mapping property group.
260 */
261 final PropertyGroup getMappingProperties()
262 {
263 return this.mappingProperties;
264 }
265
266 /**
267 * Indicates whether or not this mapping contains any mapping properties.
268 *
269 * @return true/false
270 */
271 final boolean hasMappingProperties()
272 {
273 return this.mappingProperties != null && !this.mappingProperties.getProperties().isEmpty();
274 }
275
276 /**
277 * Adds all <code>propertyReferences</code> to the property references contained in this MetafacadeMapping
278 * instance.
279 *
280 * @param propertyReferences the property references to add.
281 */
282 public void addPropertyReferences(final Collection<String> propertyReferences)
283 {
284 if (propertyReferences != null)
285 {
286 this.propertyReferences.addAll(propertyReferences);
287 }
288 }
289
290 /**
291 * The context to which this mapping applies.
292 */
293 private String context = "";
294
295 /**
296 * Sets the context to which this mapping applies.
297 *
298 * @param context The metafacade context name to set.
299 */
300 public void setContext(final String context)
301 {
302 this.context = StringUtils.trimToEmpty(context);
303 }
304
305 /**
306 * Gets the context to which this mapping applies.
307 *
308 * @return the name of the context
309 */
310 final String getContext()
311 {
312 return this.context;
313 }
314
315 /**
316 * Indicates whether or not this mapping has a context.
317 *
318 * @return true/false
319 */
320 final boolean hasContext()
321 {
322 return StringUtils.isNotBlank(this.context);
323 }
324
325 /**
326 * The "parent" metafacade mappings;
327 */
328 private MetafacadeMappings mappings;
329
330 /**
331 * Sets the metafacade mappings instance to which this particular mapping belongs. (i.e. the parent) Note, that this
332 * is populated during the call to {@link MetafacadeMappings#addMapping(MetafacadeMapping)}.
333 *
334 * @param mappings the MetacadeMappings instance to which this mapping belongs.
335 */
336 final void setMetafacadeMappings(final MetafacadeMappings mappings)
337 {
338 this.mappings = mappings;
339 }
340
341 /**
342 * Gets the "parent" MetafacadeMappings instance to which this mapping belongs.
343 *
344 * @return the parent metafacade mappings instance.
345 */
346 final MetafacadeMappings getMetafacadeMappings()
347 {
348 return this.mappings;
349 }
350
351 /**
352 * Indicates whether or not the <code>mapping</code> matches this mapping. It matches on the following: <ul>
353 * <li>metafacadeClass</li> <li>mappingClassName</li> <li>stereotypes</li> </ul>
354 * @param mapping
355 * @return match this.getMappingClassName().equals(mapping.getMappingClassName())
356 */
357 final boolean match(final MetafacadeMapping mapping)
358 {
359 boolean match =
360 mapping != null && this.getMetafacadeClass().equals(mapping.getMetafacadeClass()) &&
361 this.getStereotypes().equals(mapping.getStereotypes()) && this.getContext().equals(mapping.getContext());
362
363 // - if they match and the mappingClassNames are both non-null, verify they match
364 if (match && !this.mappingClassNames.isEmpty() && mapping != null && !mapping.mappingClassNames.isEmpty())
365 {
366 match = this.getMappingClassNames().equals(mapping.getMappingClassNames());
367 }
368 return match;
369 }
370
371 /**
372 * @see Object#toString()
373 */
374 public String toString()
375 {
376 return super.toString() + '[' + this.getMetafacadeClass() + "], mappingClassName[" + this.mappingClassNames +
377 "], properties[" + this.getMappingProperties() + "], stereotypes" + this.stereotypes + ", context[" +
378 this.context + "], propertiesReferences" + this.getPropertyReferences();
379 }
380
381 /**
382 * Represents a group of properties. Properties within a group are evaluated within an 'AND' expression.
383 * PropertyGroups are evaluated together as an 'OR' expressions (i.e. you 'OR' property groups together, and 'AND'
384 * properties together).
385 *
386 * @see MetafacadeMappings#addMapping(MetafacadeMapping)
387 */
388 static final class PropertyGroup
389 {
390 private final Map<String, Property> properties = new LinkedHashMap<String, Property>();
391
392 /**
393 * Adds a property to the internal collection of properties.
394 *
395 * @param property the property to add to this group.
396 */
397 final void addProperty(final Property property)
398 {
399 final String name = property.getName();
400 if (!this.properties.containsKey(name))
401 {
402 this.properties.put(
403 name,
404 property);
405 }
406 }
407
408 /**
409 * Gets the currently internal collection of properties.
410 *
411 * @return the properties collection.
412 */
413 final Collection<Property> getProperties()
414 {
415 return this.properties.values();
416 }
417
418 /**
419 * @see Object#toString()
420 */
421 public String toString()
422 {
423 final StringBuilder toString = new StringBuilder();
424 char separator = ':';
425 for (final Iterator<Property> iterator = this.getProperties().iterator(); iterator.hasNext();)
426 {
427 final Property property = iterator.next();
428 toString.append(property.getName());
429 if (StringUtils.isNotBlank(property.getValue()))
430 {
431 toString.append(separator).append(property.getValue());
432 }
433 if (iterator.hasNext())
434 {
435 toString.append(separator);
436 }
437 }
438 return toString.toString();
439 }
440 }
441
442 /**
443 * Stores and provides access to the mapping element's nested <property/>.
444 */
445 static final class Property
446 {
447 private String name;
448 private String value;
449
450 /**
451 * @param name
452 * @param value
453 */
454 Property(
455 final String name,
456 final String value)
457 {
458 this.name = StringUtils.trimToEmpty(name);
459 this.value = value;
460 }
461
462 /**
463 * Gets the value of the <code>name</code> attribute on the <code>property</code> element.
464 *
465 * @return the name
466 */
467 final String getName()
468 {
469 return StringUtils.trimToEmpty(this.name);
470 }
471
472 /**
473 * Gets the value of the <code>value</code> attribute defined on the <code>property</code> element.
474 *
475 * @return the value
476 */
477 final String getValue()
478 {
479 return StringUtils.trimToEmpty(this.value);
480 }
481 }
482 }