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 }