View Javadoc
1   package org.andromda.core.configuration;
2   
3   import java.io.IOException;
4   import java.io.Serializable;
5   import java.net.URL;
6   import java.util.ArrayList;
7   import java.util.Arrays;
8   import java.util.Collection;
9   import java.util.HashMap;
10  import java.util.Iterator;
11  import java.util.List;
12  import java.util.Map;
13  import org.andromda.core.common.ResourceUtils;
14  import org.apache.commons.lang.StringUtils;
15  
16  /**
17   * Stores the model information for each model that AndroMDA will process.
18   *
19   * @author Chad Brandon
20   */
21  public class Model
22      implements Serializable
23  {
24      private static final long serialVersionUID = 34L;
25  
26      /**
27       * Stores whether or not a last modified check
28       * should be performed.
29       */
30      private boolean lastModifiedCheck = false;
31  
32      /**
33       * Whether or not to perform a last modified check on the model.
34       *
35       * @return Returns the lastModifiedCheck.
36       */
37      public boolean isLastModifiedCheck()
38      {
39          return lastModifiedCheck;
40      }
41  
42      /**
43       * Sets whether or not to perform a last modified check when processing the model. If
44       * <code>true</code> the model will be checked for a timestamp before processing occurs.
45       *
46       * @param lastModifiedCheck true/false
47       */
48      public void setLastModifiedCheck(final boolean lastModifiedCheck)
49      {
50          this.lastModifiedCheck = lastModifiedCheck;
51      }
52  
53      /**
54       * Stores the information about what packages should and shouldn't
55       * be processed.
56       */
57      private Filters packages = new Filters();
58  
59      /**
60       * Sets the processAll flag on the internal model packages instance
61       * of this model.
62       *
63       * @param processAllPackages whether or not all packages should be processed by default.
64       */
65      public void setProcessAllPackages(final boolean processAllPackages)
66      {
67          packages.setApplyAll(processAllPackages);
68      }
69  
70      /**
71       * Stores the information about what packages should/shouldn't be processed.
72       *
73       * @return Returns the packages.
74       */
75      public Filters getPackages()
76      {
77          return this.packages;
78      }
79  
80      /**
81       * Sets the model packages for this model.  This indicates what
82       * packages should and should not be processed from this model.
83       *
84       * @param packages the packages to process.
85       */
86      public void setPackages(final Filters packages)
87      {
88          this.packages = packages;
89      }
90  
91      /**
92       * Stores the information about what constraints should and shouldn't
93       * be enforced.
94       */
95      private Filters constraints = new Filters();
96  
97      /**
98       * Sets the applyAll flag on the internal filters instance
99       * of this model.
100      *
101      * @param enforceAllConstraints whether or not all constraints should be enforced by default.
102      */
103     public void setEnforceAllConstraints(final boolean enforceAllConstraints)
104     {
105         this.constraints.setApplyAll(enforceAllConstraints);
106     }
107 
108     /**
109      * Stores the information about what constraints should/shouldn't be enforced.
110      *
111      * @return Returns the constraints instance.
112      */
113     public Filters getConstraints()
114     {
115         return this.constraints;
116     }
117 
118     /**
119      * Sets the constraints for this model.  This indicates what
120      * constraints should and should not be processed from this model.
121      *
122      * @param constraints the packages to process.
123      */
124     public void setConstraints(final Filters constraints)
125     {
126         this.constraints = constraints;
127     }
128 
129     /**
130      * The URL to the model.
131      */
132     private List<URL> uris = new ArrayList<URL>();
133 
134     /**
135      * Caches the urisAsStrings value (so we don't need
136      * to do the conversion more than once).
137      */
138     private String[] urisAsStrings = null;
139 
140     /**
141      * All URIs that make up the model.
142      *
143      * @return Returns the uri.
144      */
145     public String[] getUris()
146     {
147         if (this.urisAsStrings == null)
148         {
149             final int uriNumber = uris.size();
150             this.urisAsStrings = new String[uriNumber];
151             for (int ctr = 0; ctr < uriNumber; ctr++)
152             {
153                 urisAsStrings[ctr] = uris.get(ctr).toString();
154             }
155         }
156         return this.urisAsStrings;
157     }
158 
159     /**
160      * Adds the location as a URI to one of the model files.
161      *
162      * @param uri the URI to the model.
163      */
164     public void addUri(final String uri)
165     {
166         try
167         {
168             final URL url = ResourceUtils.toURL(uri);
169             if (url == null)
170             {
171                 throw new ConfigurationException("Model could not be loaded from invalid path --> '" + uri + '\'');
172             }
173             try
174             {
175                 // - Get around the fact the URL won't be released until the JVM
176                 //   has been terminated, when using the 'jar' uri protocol.
177                 url.openConnection().setDefaultUseCaches(false);
178             }
179             catch (final IOException exception)
180             {
181                 // - ignore the exception
182             }
183             this.uris.add(url);
184         }
185         catch (final Throwable throwable)
186         {
187             throw new ConfigurationException(throwable);
188         }
189     }
190 
191     /**
192      * Stores the transformations for this Configuration instance.
193      */
194     private final Collection<Transformation> transformations = new ArrayList<Transformation>();
195 
196     /**
197      * Adds a transformation to this configuration instance.
198      *
199      * @param transformation the transformation instance to add.
200      */
201     public void addTransformation(final Transformation transformation)
202     {
203         this.transformations.add(transformation);
204     }
205 
206     /**
207      * Gets the transformations belonging to this configuration.
208      *
209      * @return the array of {@link Transformation} instances.
210      */
211     public Transformation[] getTransformations()
212     {
213         return this.transformations.toArray(new Transformation[this.transformations.size()]);
214     }
215 
216     /**
217      * The locations in which to search for module.
218      */
219     private final Collection<Location> moduleSearchLocations = new ArrayList<Location>();
220 
221     /**
222      * Adds a module search location (these are the locations
223      * in which a search for module is performed).
224      *
225      * @param location a location path.
226      * @see #addModuleSearchLocation(String)
227      */
228     public void addModuleSearchLocation(final Location location)
229     {
230         this.moduleSearchLocations.add(location);
231     }
232 
233     /**
234      * Adds a module search location path (a location
235      * without a pattern defined).
236      *
237      * @param path a location path.
238      * @see #addModuleSearchLocation(Location)
239      */
240     public void addModuleSearchLocation(final String path)
241     {
242         if (path != null)
243         {
244             final Location location = new Location();
245             location.setPath(path);
246             this.moduleSearchLocations.add(location);
247         }
248     }
249 
250     /**
251      * The type of model (i.e. uml-1.4, uml-2.0, etc).
252      */
253     private String type;
254 
255     /**
256      * Gets the type of the model (i.e. the type of metamodel this
257      * model is based upon).
258      *
259      * @return Returns the type.
260      */
261     public String getType()
262     {
263         return this.type;
264     }
265 
266     /**
267      * Sets the type of model (i.e. the type of metamodel this model
268      * is based upon).
269      *
270      * @param type The type to set.
271      */
272     public void setType(final String type)
273     {
274         this.type = type;
275     }
276 
277     /**
278      * Gets the module search locations for this model instance.
279      *
280      * @return the module search locations.
281      * @see #getModuleSearchLocationPaths()
282      */
283     public Location[] getModuleSearchLocations()
284     {
285         return this.moduleSearchLocations.toArray(new Location[this.moduleSearchLocations.size()]);
286     }
287 
288     /**
289      * Stores the path for each module search location in this configuration.
290      */
291     private String[] moduleSearchLocationPaths = null;
292 
293     /**
294      * Gets all found module search location paths for this model instance.
295      *
296      * @return the module search location paths.
297      * @see #getModuleSearchLocations()
298      */
299     public String[] getModuleSearchLocationPaths()
300     {
301         if (this.moduleSearchLocationPaths == null)
302         {
303             final Collection<String> paths = new ArrayList<String>();
304             for (final Location location : this.moduleSearchLocations)
305             {
306                 final URL[] resources = location.getResources();
307                 final int resourceNumber = resources.length;
308                 for (int ctr = 0; ctr < resourceNumber; ctr++)
309                 {
310                     paths.add(resources[ctr].toString());
311                 }
312                 paths.add(location.getPath());
313             }
314             this.moduleSearchLocationPaths = paths.toArray(new String[paths.size()]);
315         }
316         return this.moduleSearchLocationPaths;
317     }
318 
319     /**
320      * Stores all resources including all resources found within the module search locations
321      * as well as a resource for the {@link #getUris()}.
322      */
323     private URL[] moduleSearchLocationResources = null;
324 
325     /**
326      * Gets the accumulation of all files found when combining the contents
327      * of all module search location paths and their patterns by which they
328      * are filtered as well as the model URI.
329      *
330      * @return all module search location files.
331      */
332     public URL[] getModuleSearchLocationResources()
333     {
334         if (this.moduleSearchLocationResources == null)
335         {
336             final Collection<URL> allResources = new ArrayList<URL>();
337             final Location[] locations = this.getModuleSearchLocations();
338             for (final Location location : locations)
339             {
340                 final URL[] resources = location.getResources();
341                 allResources.addAll(Arrays.asList(resources));
342             }
343             this.moduleSearchLocationResources = allResources.toArray(new URL[allResources.size()]);
344         }
345         return this.moduleSearchLocationResources;
346     }
347 
348     /**
349      * Gets the time of the latest modified uri of the model as a <code>long</code>.
350      * If it can not be determined <code>0</code> is returned.
351      *
352      * @return the time this model was last modified
353      */
354     public long getLastModified()
355     {
356         long lastModifiedTime = 0;
357         for (final URL url : uris)
358         {
359             final long modifiedTime = ResourceUtils.getLastModifiedTime(url);
360             if (modifiedTime > lastModifiedTime)
361             {
362                 lastModifiedTime = modifiedTime;
363             }
364         }
365         return lastModifiedTime;
366     }
367 
368     /**
369      * @see Object#toString()
370      */
371     public String toString()
372     {
373         String toString = super.toString();
374         final String key = this.getKey();
375         if (StringUtils.isNotBlank(key))
376         {
377             toString = key;
378         }
379         return toString;
380     }
381 
382     /**
383      * Stores the last modified times for each model at the time
384      * {@link #isChanged()} is called.
385      */
386     private static final Map<String, Map<String, Long>> modelModifiedTimes = new HashMap<String, Map<String, Long>>();
387 
388     /**
389      * The unique key that identifies this model.
390      */
391     private String key = null;
392 
393     /**
394      * Creates the unique key that identifies this model
395      * (its made up of a list of all the URIs for this model
396      * concatenated).
397      *
398      * @return the unique key
399      */
400     private String getKey()
401     {
402         if (StringUtils.isBlank(this.key))
403         {
404             final StringBuilder buffer = new StringBuilder();
405             for (final Iterator<URL> iterator = this.uris.iterator(); iterator.hasNext();)
406             {
407                 final URL uri = iterator.next();
408                 buffer.append(uri.getFile());
409                 if (iterator.hasNext())
410                 {
411                     buffer.append(", ");
412                 }
413             }
414             this.key = buffer.toString();
415         }
416         return this.key;
417     }
418 
419     /**
420      * The repository to which this model belongs.
421      */
422     private Repository repository;
423 
424     /**
425      * Gets the repository to which this model belongs.
426      *
427      * @return the repository to which this model belongs.
428      */
429     public Repository getRepository()
430     {
431         return this.repository;
432     }
433 
434     /**
435      * Sets the repository to which this model belongs.
436      *
437      * @param repository the repository configuration to which this model belongs.
438      */
439     void setRepository(final Repository repository)
440     {
441         this.repository = repository;
442     }
443 
444     /**
445      * Indicates whether or not the given <code>model</code>
446      * has changed since the previous call to this method.
447      *
448      * @return true/false
449      */
450     public boolean isChanged()
451     {
452         boolean changed = this.getUris().length > 0;
453         if (changed)
454         {
455             final String modelKey = this.getKey();
456             Map<String, Long> lastModifiedTimes = modelModifiedTimes.get(modelKey);
457 
458             // - load up the last modified times (from the model and all its modules)
459             //   if they haven't been loaded yet
460             if (lastModifiedTimes != null)
461             {
462                 final long modelLastModified = lastModifiedTimes.get(modelKey);
463                 changed = this.getLastModified() > modelLastModified;
464                 if (!changed)
465                 {
466                     // - check to see if any of the modules have changed if the model hasn't changed
467                     final URL[] resources = this.getModuleSearchLocationResources();
468                     for (final URL resource : resources)
469                     {
470                         final Long lastModified = lastModifiedTimes.get(resource.getFile());
471                         if (lastModified != null)
472                         {
473                             // - when we find the first modified module, break out
474                             if (ResourceUtils.getLastModifiedTime(resource) > lastModified)
475                             {
476                                 changed = true;
477                                 break;
478                             }
479                         }
480                     }
481                 }
482             }
483 
484             // - if our model (or modules) have changed re-load the last modified times
485             if (changed)
486             {
487                 this.loadLastModifiedTimes();
488             }
489         }
490         return changed;
491     }
492 
493     /**
494      * Loads (or re-loads) the last modified times from the
495      * {@link #getUris()} and the modules found on the module search path.
496      */
497     private void loadLastModifiedTimes()
498     {
499         final String modelKey = this.getKey();
500         Map<String, Long> lastModifiedTimes = modelModifiedTimes.get(modelKey);
501         if (lastModifiedTimes == null)
502         {
503             lastModifiedTimes = new HashMap<String, Long>();
504         }
505         else
506         {
507             lastModifiedTimes.clear();
508         }
509         final URL[] resources = this.getModuleSearchLocationResources();
510         for (final URL resource : resources)
511         {
512             lastModifiedTimes.put(
513                 resource.getFile(),
514                     ResourceUtils.getLastModifiedTime(resource));
515         }
516 
517         // - add the model key last so it overwrites any invalid ones
518         //   we might have picked up from adding the module search location files.
519         lastModifiedTimes.put(
520                 modelKey,
521                 this.getLastModified());
522 
523         modelModifiedTimes.put(
524             modelKey,
525             lastModifiedTimes);
526     }
527 
528     /**
529      * Clears out the current last modified times.
530      */
531     static void clearLastModifiedTimes()
532     {
533         modelModifiedTimes.clear();
534     }
535 }