001: package org.andromda.core.mapping;
002:
003: import java.io.File;
004: import java.io.FileReader;
005:
006: import java.net.MalformedURLException;
007: import java.net.URL;
008:
009: import java.util.Collection;
010: import java.util.Iterator;
011: import java.util.LinkedHashMap;
012: import java.util.Map;
013: import java.util.HashMap;
014:
015: import org.andromda.core.common.ExceptionUtils;
016: import org.andromda.core.common.ResourceUtils;
017: import org.andromda.core.common.XmlObjectFactory;
018: import org.apache.commons.lang.StringUtils;
019: import org.apache.commons.lang.builder.ToStringBuilder;
020:
021: /**
022: * <p> An object responsible for mapping multiple <code>from</code> values to
023: * single <code>to</code>. The public constructor should NOT be used to
024: * construct this instance. An instance of this object should be retrieved
025: * through the method getInstance(java.net.URL).
026: * </p>
027: * <p> The mappings will change based upon the language, database, etc being
028: * used. </p>
029: *
030: * @author Chad Brandon
031: * @author Wouter Zoons
032: * @see org.andromda.core.common.XmlObjectFactory
033: */
034: public class Mappings {
035: /**
036: * Contains the set of Mapping objects keyed by the 'type' element defined
037: * within the from type mapping XML file.
038: */
039: private final Map mappings = new LinkedHashMap();
040:
041: /**
042: * A static mapping containing all logical mappings currently available.
043: */
044: private static final Map logicalMappings = new LinkedHashMap();
045:
046: /**
047: * Holds the resource path from which this Mappings object was loaded.
048: */
049: private URL resource;
050:
051: /**
052: * Returns a new configured instance of this Mappings configured from the
053: * mappings configuration URI string.
054: *
055: * @param mappingsUri the URI to the XML type mappings configuration file.
056: * @return Mappings the configured Mappings instance.
057: */
058: public static Mappings getInstance(String mappingsUri) {
059: mappingsUri = StringUtils.trimToEmpty(mappingsUri);
060: ExceptionUtils.checkEmpty("mappingsUri", mappingsUri);
061: try {
062: Mappings mappings = (Mappings) logicalMappings
063: .get(mappingsUri);
064: if (mappings == null) {
065: try {
066: mappings = getInstance(new URL(mappingsUri));
067: } catch (final MalformedURLException exception) {
068: throw new MappingsException("The given URI --> '"
069: + mappingsUri + "' is invalid", exception);
070: }
071: }
072: return getInheritedMappings(mappings);
073: } catch (final Throwable throwable) {
074: throw new MappingsException(throwable);
075: }
076: }
077:
078: /**
079: * Attempts to get any inherited mappings for the
080: * given <code>mappings</code>.
081: *
082: * @param mappings the mappings instance for which to
083: * get the inherited mappings.
084: * @return the Mappings populated with any inherited mappings
085: * or just the same mappings unchanged if the
086: * <code>mappings</code> doesn't extend anything.
087: * @throws Exception if an exception occurs.
088: */
089: private static Mappings getInheritedMappings(final Mappings mappings)
090: throws Exception {
091: return getInheritedMappings(mappings, false);
092: }
093:
094: /**
095: * Attempts to get any inherited mappings for the
096: * given <code>mappings</code>.
097: * This method may only be called when the logical mappings have been initialized.
098: *
099: * @param mappings the mappings instance for which to
100: * get the inherited mappings.
101: * @param ignoreInheritanceFailure whether or not a failure retrieving the parent
102: * should be ignored (an exception will be thrown otherwise).
103: * @return the Mappings populated with any inherited mappings
104: * or just the same mappings unchanged if the
105: * <code>mappings</code> doesn't extend anything.
106: * @throws Exception if an exception occurs.
107: * @see #initializeLogicalMappings()
108: */
109: private static Mappings getInheritedMappings(
110: final Mappings mappings,
111: final boolean ignoreInheritanceFailure) throws Exception {
112: // if we have a parent then we add the child mappings to
113: // the parent's (so we can override any duplicates in the
114: // parent) and set the child mappings to the parent's
115: if (mappings != null
116: && StringUtils.isNotBlank(mappings.extendsUri)) {
117: Mappings parentMappings = (Mappings) logicalMappings
118: .get(mappings.extendsUri);
119: if (parentMappings == null) {
120: try {
121: // since we didn't find the parent in the logical
122: // mappings, try a relative path
123: parentMappings = getInstance(new File(mappings
124: .getCompletePath(mappings.extendsUri)));
125: } catch (final Exception exception) {
126: if (!ignoreInheritanceFailure) {
127: throw exception;
128: }
129: }
130: }
131: if (parentMappings != null) {
132: mergeWithoutOverriding(parentMappings, mappings);
133: }
134: }
135: return mappings;
136: }
137:
138: /**
139: * Returns a new configured instance of this Mappings configured from the
140: * mappings configuration URI.
141: *
142: * @param mappingsUri the URI to the XML type mappings configuration file.
143: * @return Mappings the configured Mappings instance.
144: */
145: public static Mappings getInstance(final URL mappingsUri) {
146: return getInstance(mappingsUri, false);
147: }
148:
149: /**
150: * Returns a new configured instance of this Mappings configured from the
151: * mappings configuration URI.
152: *
153: * @param mappingsUri the URI to the XML type mappings configuration file.
154: * @param ignoreInheritanceFailure a flag indicating whether or not failures while attempting
155: * to retrieve the mapping's inheritance should be ignored.
156: * @return Mappings the configured Mappings instance.
157: */
158: private static Mappings getInstance(final URL mappingsUri,
159: final boolean ignoreInheritanceFailure) {
160: ExceptionUtils.checkNull("mappingsUri", mappingsUri);
161: try {
162: final Mappings mappings = (Mappings) XmlObjectFactory
163: .getInstance(Mappings.class).getObject(mappingsUri);
164: mappings.resource = mappingsUri;
165: return getInheritedMappings(mappings,
166: ignoreInheritanceFailure);
167: } catch (final Throwable throwable) {
168: throw new MappingsException(throwable);
169: }
170: }
171:
172: /**
173: * Returns a new configured instance of this Mappings configured from the
174: * mappingsFile.
175: *
176: * @param mappingsFile the XML type mappings configuration file.
177: * @return Mappings the configured Mappings instance.
178: */
179: private static Mappings getInstance(final File mappingsFile)
180: throws Exception {
181: final Mappings mappings = (Mappings) XmlObjectFactory
182: .getInstance(Mappings.class).getObject(
183: new FileReader(mappingsFile));
184: mappings.resource = mappingsFile.toURL();
185: return mappings;
186: }
187:
188: /**
189: * This initializes all logical mappings that
190: * are contained with global Mapping set. This
191: * <strong>MUST</strong> be called after all logical
192: * mappings have been added through {@link #addLogicalMappings(java.net.URL)}
193: * otherwise inheritance between logical mappings will not work correctly.
194: */
195: public static void initializeLogicalMappings() {
196: // !!! no calls to getInstance(..) must be made in this method !!!
197:
198: // reorder the logical mappings so that they can safely be loaded
199: // (top-level mappings first)
200:
201: final Map unprocessedMappings = new HashMap(logicalMappings);
202: final Map processedMappings = new LinkedHashMap(); // these will be in the good order
203:
204: // keep looping until there are no more unprocessed mappings
205: // if nothing more can be processed but there are unprocessed mappings left
206: // then we have an error (cyclic dependency or unknown parent mappings) which cannot be solved
207: boolean processed = true;
208: while (processed) {
209: // we need to have at least one entry processed before the routine qualifies for the next iteration
210: processed = false;
211:
212: // we only process mappings if they have parents that have already been processed
213: for (final Iterator iterator = unprocessedMappings
214: .entrySet().iterator(); iterator.hasNext();) {
215: final Map.Entry logicalMapping = (Map.Entry) iterator
216: .next();
217: final String name = (String) logicalMapping.getKey();
218: final Mappings mappings = (Mappings) logicalMapping
219: .getValue();
220:
221: if (mappings.extendsUri == null) {
222: // no parent mappings are always safe to add
223:
224: // move to the map of processed mappings
225: processedMappings.put(name, mappings);
226: // remove from the map of unprocessed mappings
227: iterator.remove();
228: // set the flag
229: processed = true;
230: } else if (processedMappings
231: .containsKey(mappings.extendsUri)) {
232: final Mappings parentMappings = (Mappings) processedMappings
233: .get(mappings.extendsUri);
234: if (parentMappings != null) {
235: mergeWithoutOverriding(parentMappings, mappings);
236: }
237:
238: // move to the map of processed mappings
239: processedMappings.put(name, mappings);
240: // remove from the map of unprocessed mappings
241: iterator.remove();
242: // set the flag
243: processed = true;
244: }
245: }
246:
247: }
248:
249: if (!unprocessedMappings.isEmpty()) {
250: throw new MappingsException(
251: "Logical mappings cannot be initialized due to invalid inheritance: "
252: + unprocessedMappings.keySet());
253: }
254:
255: logicalMappings.putAll(processedMappings);
256: }
257:
258: /**
259: * Clears the entries from the logical mappings cache.
260: */
261: public static void clearLogicalMappings() {
262: logicalMappings.clear();
263: }
264:
265: /**
266: * Holds the name of this mapping. This corresponds usually to some language
267: * (i.e. Java, or a database such as Oracle, Sql Server, etc).
268: */
269: private String name = null;
270:
271: /**
272: * Returns the name name (this is the name for which the type mappings are
273: * for).
274: *
275: * @return String the name name
276: */
277: public String getName() {
278: final String methodName = "Mappings.getName";
279: if (StringUtils.isEmpty(this .name)) {
280: throw new MappingsException(methodName
281: + " - name can not be null or empty");
282: }
283: return name;
284: }
285:
286: /**
287: * Sets the name name.
288: *
289: * @param name
290: */
291: public void setName(final String name) {
292: this .name = name;
293: }
294:
295: /**
296: * Stores the URI that this mappings extends.
297: */
298: private String extendsUri;
299:
300: /**
301: * Sets the name of the mappings which this
302: * instance extends.
303: *
304: * @param extendsUri the URI of the mapping which
305: * this one extends.
306: */
307: public void setExtendsUri(final String extendsUri) {
308: this .extendsUri = extendsUri;
309: }
310:
311: /**
312: * Adds a Mapping object to the set of current mappings.
313: *
314: * @param mapping the Mapping instance.
315: */
316: public void addMapping(final Mapping mapping) {
317: ExceptionUtils.checkNull("mapping", mapping);
318: final Collection fromTypes = mapping.getFroms();
319: ExceptionUtils.checkNull("mapping.fromTypes", fromTypes);
320: for (final Iterator typeIterator = fromTypes.iterator(); typeIterator
321: .hasNext();) {
322: mapping.setMappings(this );
323: this .mappings.put(typeIterator.next(), mapping);
324: }
325: }
326:
327: /**
328: * Adds the <code>mappings</code> instance to this Mappings instance
329: * overriding any mappings with duplicate names.
330: *
331: * @param mappings the Mappings instance to add this instance.
332: */
333: public void addMappings(final Mappings mappings) {
334: if (mappings != null && mappings.mappings != null) {
335: this .mappings.putAll(mappings.mappings);
336: }
337: }
338:
339: /**
340: * Reads the argument parent mappings and copies any mapping entries that do not already exist in this instance.
341: * This method preserves ordering and add new entries to the end.
342: *
343: * @param sourceMappings the mappings from which to read possible new entries
344: * @param targetMappings the mappings to which to store possible new entries from the sourceMappings
345: */
346: private static void mergeWithoutOverriding(Mappings sourceMappings,
347: Mappings targetMappings) {
348: final Map allMappings = new LinkedHashMap(
349: targetMappings.mappings.size()
350: + sourceMappings.mappings.size());
351: allMappings.putAll(sourceMappings.mappings);
352: allMappings.putAll(targetMappings.mappings);
353: targetMappings.mappings.clear();
354: targetMappings.mappings.putAll(allMappings);
355: }
356:
357: /**
358: * Returns the <code>to</code> mapping from a given <code>from</code>
359: * mapping.
360: *
361: * @param from the <code>from</code> mapping, this is the type/identifier
362: * that is in the model.
363: * @return String to the <code>to</code> mapping (this is the mapping that
364: * can be retrieved if a corresponding 'from' is found).
365: */
366: public String getTo(String from) {
367: from = StringUtils.trimToEmpty(from);
368: final String initialFrom = from;
369: String to = null;
370:
371: // first we check to see if there's an array
372: // type mapping directly defined in the mappings
373: final Mapping mapping = this .getMapping(from);
374: if (mapping != null) {
375: to = mapping.getTo();
376: }
377: if (to == null) {
378: to = initialFrom;
379: }
380: return StringUtils.trimToEmpty(to);
381: }
382:
383: /**
384: * Adds a mapping to the globally available mappings, these are used by this
385: * class to instantiate mappings from logical names as opposed to physical
386: * names.
387: *
388: * @param mappingsUri the Mappings URI to add to the globally available Mapping
389: * instances.
390: */
391: public static void addLogicalMappings(final URL mappingsUri) {
392: final Mappings mappings = Mappings.getInstance(mappingsUri,
393: true);
394: logicalMappings.put(mappings.getName(), mappings);
395: }
396:
397: /**
398: * Returns true if the mapping contains the <code>from</code> value
399: *
400: * @param from the value of the from mapping.
401: * @return true if it contains <code>from</code>, false otherwise.
402: */
403: public boolean containsFrom(final String from) {
404: return this .getMapping(from) != null;
405: }
406:
407: /**
408: * Returns the resource URI from which this Mappings object was loaded.
409: *
410: * @return URL of the resource.
411: */
412: public URL getResource() {
413: return this .resource;
414: }
415:
416: /**
417: * Gets all Mapping instances for for this Mappings instance.
418: *
419: * @return a collection containing <strong>all </strong> Mapping instances.
420: */
421: public Collection getMappings() {
422: return this .mappings.values();
423: }
424:
425: /**
426: * Gets the mapping having the given <code>from</code>.
427: *
428: * @param from the <code>from</code> mapping.
429: * @return the Mapping instance (or null if it doesn't exist).
430: */
431: public Mapping getMapping(final String from) {
432: return (Mapping) this .mappings.get(StringUtils
433: .trimToEmpty(from));
434: }
435:
436: /**
437: * Caches the complete path.
438: */
439: private final Map completePaths = new LinkedHashMap();
440:
441: /**
442: * Constructs the complete path from the given <code>relativePath</code>
443: * and the resource of the parent {@link Mappings#getResource()} as the root
444: * of the path.
445: *
446: * @return the complete path.
447: */
448: final String getCompletePath(final String relativePath) {
449: String completePath = (String) this .completePaths
450: .get(relativePath);
451: if (completePath == null) {
452: final StringBuffer path = new StringBuffer();
453: if (this .mappings != null) {
454: final URL resource = this .getResource();
455: if (resource != null) {
456: String rootPath = resource.getFile().replace('\\',
457: '/');
458: rootPath = rootPath.substring(0, rootPath
459: .lastIndexOf('/') + 1);
460: path.append(rootPath);
461: }
462: }
463: if (relativePath != null) {
464: path.append(StringUtils.trimToEmpty(relativePath));
465: }
466: completePath = path.toString();
467: this .completePaths.put(relativePath, completePath);
468: }
469: return ResourceUtils.unescapeFilePath(completePath);
470: }
471:
472: /**
473: * @see java.lang.Object#toString()
474: */
475: public String toString() {
476: return ToStringBuilder.reflectionToString(this);
477: }
478: }
|