001package org.andromda.core.configuration; 002 003import java.io.IOException; 004import java.io.Serializable; 005import java.net.URL; 006import java.util.ArrayList; 007import java.util.Arrays; 008import java.util.Collection; 009import java.util.HashMap; 010import java.util.Iterator; 011import java.util.List; 012import java.util.Map; 013import org.andromda.core.common.ResourceUtils; 014import org.apache.commons.lang.StringUtils; 015 016/** 017 * Stores the model information for each model that AndroMDA will process. 018 * 019 * @author Chad Brandon 020 */ 021public class Model 022 implements Serializable 023{ 024 private static final long serialVersionUID = 34L; 025 026 /** 027 * Stores whether or not a last modified check 028 * should be performed. 029 */ 030 private boolean lastModifiedCheck = false; 031 032 /** 033 * Whether or not to perform a last modified check on the model. 034 * 035 * @return Returns the lastModifiedCheck. 036 */ 037 public boolean isLastModifiedCheck() 038 { 039 return lastModifiedCheck; 040 } 041 042 /** 043 * Sets whether or not to perform a last modified check when processing the model. If 044 * <code>true</code> the model will be checked for a timestamp before processing occurs. 045 * 046 * @param lastModifiedCheck true/false 047 */ 048 public void setLastModifiedCheck(final boolean lastModifiedCheck) 049 { 050 this.lastModifiedCheck = lastModifiedCheck; 051 } 052 053 /** 054 * Stores the information about what packages should and shouldn't 055 * be processed. 056 */ 057 private Filters packages = new Filters(); 058 059 /** 060 * Sets the processAll flag on the internal model packages instance 061 * of this model. 062 * 063 * @param processAllPackages whether or not all packages should be processed by default. 064 */ 065 public void setProcessAllPackages(final boolean processAllPackages) 066 { 067 packages.setApplyAll(processAllPackages); 068 } 069 070 /** 071 * Stores the information about what packages should/shouldn't be processed. 072 * 073 * @return Returns the packages. 074 */ 075 public Filters getPackages() 076 { 077 return this.packages; 078 } 079 080 /** 081 * Sets the model packages for this model. This indicates what 082 * packages should and should not be processed from this model. 083 * 084 * @param packages the packages to process. 085 */ 086 public void setPackages(final Filters packages) 087 { 088 this.packages = packages; 089 } 090 091 /** 092 * Stores the information about what constraints should and shouldn't 093 * be enforced. 094 */ 095 private Filters constraints = new Filters(); 096 097 /** 098 * Sets the applyAll flag on the internal filters instance 099 * 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}