Model.java
package org.andromda.core.configuration;
import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.andromda.core.common.ResourceUtils;
import org.apache.commons.lang.StringUtils;
/**
* Stores the model information for each model that AndroMDA will process.
*
* @author Chad Brandon
*/
public class Model
implements Serializable
{
private static final long serialVersionUID = 34L;
/**
* Stores whether or not a last modified check
* should be performed.
*/
private boolean lastModifiedCheck = false;
/**
* Whether or not to perform a last modified check on the model.
*
* @return Returns the lastModifiedCheck.
*/
public boolean isLastModifiedCheck()
{
return lastModifiedCheck;
}
/**
* Sets whether or not to perform a last modified check when processing the model. If
* <code>true</code> the model will be checked for a timestamp before processing occurs.
*
* @param lastModifiedCheck true/false
*/
public void setLastModifiedCheck(final boolean lastModifiedCheck)
{
this.lastModifiedCheck = lastModifiedCheck;
}
/**
* Stores the information about what packages should and shouldn't
* be processed.
*/
private Filters packages = new Filters();
/**
* Sets the processAll flag on the internal model packages instance
* of this model.
*
* @param processAllPackages whether or not all packages should be processed by default.
*/
public void setProcessAllPackages(final boolean processAllPackages)
{
packages.setApplyAll(processAllPackages);
}
/**
* Stores the information about what packages should/shouldn't be processed.
*
* @return Returns the packages.
*/
public Filters getPackages()
{
return this.packages;
}
/**
* Sets the model packages for this model. This indicates what
* packages should and should not be processed from this model.
*
* @param packages the packages to process.
*/
public void setPackages(final Filters packages)
{
this.packages = packages;
}
/**
* Stores the information about what constraints should and shouldn't
* be enforced.
*/
private Filters constraints = new Filters();
/**
* Sets the applyAll flag on the internal filters instance
* of this model.
*
* @param enforceAllConstraints whether or not all constraints should be enforced by default.
*/
public void setEnforceAllConstraints(final boolean enforceAllConstraints)
{
this.constraints.setApplyAll(enforceAllConstraints);
}
/**
* Stores the information about what constraints should/shouldn't be enforced.
*
* @return Returns the constraints instance.
*/
public Filters getConstraints()
{
return this.constraints;
}
/**
* Sets the constraints for this model. This indicates what
* constraints should and should not be processed from this model.
*
* @param constraints the packages to process.
*/
public void setConstraints(final Filters constraints)
{
this.constraints = constraints;
}
/**
* The URL to the model.
*/
private List<URL> uris = new ArrayList<URL>();
/**
* Caches the urisAsStrings value (so we don't need
* to do the conversion more than once).
*/
private String[] urisAsStrings = null;
/**
* All URIs that make up the model.
*
* @return Returns the uri.
*/
public String[] getUris()
{
if (this.urisAsStrings == null)
{
final int uriNumber = uris.size();
this.urisAsStrings = new String[uriNumber];
for (int ctr = 0; ctr < uriNumber; ctr++)
{
urisAsStrings[ctr] = uris.get(ctr).toString();
}
}
return this.urisAsStrings;
}
/**
* Adds the location as a URI to one of the model files.
*
* @param uri the URI to the model.
*/
public void addUri(final String uri)
{
try
{
final URL url = ResourceUtils.toURL(uri);
if (url == null)
{
throw new ConfigurationException("Model could not be loaded from invalid path --> '" + uri + '\'');
}
try
{
// - Get around the fact the URL won't be released until the JVM
// has been terminated, when using the 'jar' uri protocol.
url.openConnection().setDefaultUseCaches(false);
}
catch (final IOException exception)
{
// - ignore the exception
}
this.uris.add(url);
}
catch (final Throwable throwable)
{
throw new ConfigurationException(throwable);
}
}
/**
* Stores the transformations for this Configuration instance.
*/
private final Collection<Transformation> transformations = new ArrayList<Transformation>();
/**
* Adds a transformation to this configuration instance.
*
* @param transformation the transformation instance to add.
*/
public void addTransformation(final Transformation transformation)
{
this.transformations.add(transformation);
}
/**
* Gets the transformations belonging to this configuration.
*
* @return the array of {@link Transformation} instances.
*/
public Transformation[] getTransformations()
{
return this.transformations.toArray(new Transformation[this.transformations.size()]);
}
/**
* The locations in which to search for module.
*/
private final Collection<Location> moduleSearchLocations = new ArrayList<Location>();
/**
* Adds a module search location (these are the locations
* in which a search for module is performed).
*
* @param location a location path.
* @see #addModuleSearchLocation(String)
*/
public void addModuleSearchLocation(final Location location)
{
this.moduleSearchLocations.add(location);
}
/**
* Adds a module search location path (a location
* without a pattern defined).
*
* @param path a location path.
* @see #addModuleSearchLocation(Location)
*/
public void addModuleSearchLocation(final String path)
{
if (path != null)
{
final Location location = new Location();
location.setPath(path);
this.moduleSearchLocations.add(location);
}
}
/**
* The type of model (i.e. uml-1.4, uml-2.0, etc).
*/
private String type;
/**
* Gets the type of the model (i.e. the type of metamodel this
* model is based upon).
*
* @return Returns the type.
*/
public String getType()
{
return this.type;
}
/**
* Sets the type of model (i.e. the type of metamodel this model
* is based upon).
*
* @param type The type to set.
*/
public void setType(final String type)
{
this.type = type;
}
/**
* Gets the module search locations for this model instance.
*
* @return the module search locations.
* @see #getModuleSearchLocationPaths()
*/
public Location[] getModuleSearchLocations()
{
return this.moduleSearchLocations.toArray(new Location[this.moduleSearchLocations.size()]);
}
/**
* Stores the path for each module search location in this configuration.
*/
private String[] moduleSearchLocationPaths = null;
/**
* Gets all found module search location paths for this model instance.
*
* @return the module search location paths.
* @see #getModuleSearchLocations()
*/
public String[] getModuleSearchLocationPaths()
{
if (this.moduleSearchLocationPaths == null)
{
final Collection<String> paths = new ArrayList<String>();
for (final Location location : this.moduleSearchLocations)
{
final URL[] resources = location.getResources();
final int resourceNumber = resources.length;
for (int ctr = 0; ctr < resourceNumber; ctr++)
{
paths.add(resources[ctr].toString());
}
paths.add(location.getPath());
}
this.moduleSearchLocationPaths = paths.toArray(new String[paths.size()]);
}
return this.moduleSearchLocationPaths;
}
/**
* Stores all resources including all resources found within the module search locations
* as well as a resource for the {@link #getUris()}.
*/
private URL[] moduleSearchLocationResources = null;
/**
* Gets the accumulation of all files found when combining the contents
* of all module search location paths and their patterns by which they
* are filtered as well as the model URI.
*
* @return all module search location files.
*/
public URL[] getModuleSearchLocationResources()
{
if (this.moduleSearchLocationResources == null)
{
final Collection<URL> allResources = new ArrayList<URL>();
final Location[] locations = this.getModuleSearchLocations();
for (final Location location : locations)
{
final URL[] resources = location.getResources();
allResources.addAll(Arrays.asList(resources));
}
this.moduleSearchLocationResources = allResources.toArray(new URL[allResources.size()]);
}
return this.moduleSearchLocationResources;
}
/**
* Gets the time of the latest modified uri of the model as a <code>long</code>.
* If it can not be determined <code>0</code> is returned.
*
* @return the time this model was last modified
*/
public long getLastModified()
{
long lastModifiedTime = 0;
for (final URL url : uris)
{
final long modifiedTime = ResourceUtils.getLastModifiedTime(url);
if (modifiedTime > lastModifiedTime)
{
lastModifiedTime = modifiedTime;
}
}
return lastModifiedTime;
}
/**
* @see Object#toString()
*/
public String toString()
{
String toString = super.toString();
final String key = this.getKey();
if (StringUtils.isNotBlank(key))
{
toString = key;
}
return toString;
}
/**
* Stores the last modified times for each model at the time
* {@link #isChanged()} is called.
*/
private static final Map<String, Map<String, Long>> modelModifiedTimes = new HashMap<String, Map<String, Long>>();
/**
* The unique key that identifies this model.
*/
private String key = null;
/**
* Creates the unique key that identifies this model
* (its made up of a list of all the URIs for this model
* concatenated).
*
* @return the unique key
*/
private String getKey()
{
if (StringUtils.isBlank(this.key))
{
final StringBuilder buffer = new StringBuilder();
for (final Iterator<URL> iterator = this.uris.iterator(); iterator.hasNext();)
{
final URL uri = iterator.next();
buffer.append(uri.getFile());
if (iterator.hasNext())
{
buffer.append(", ");
}
}
this.key = buffer.toString();
}
return this.key;
}
/**
* The repository to which this model belongs.
*/
private Repository repository;
/**
* Gets the repository to which this model belongs.
*
* @return the repository to which this model belongs.
*/
public Repository getRepository()
{
return this.repository;
}
/**
* Sets the repository to which this model belongs.
*
* @param repository the repository configuration to which this model belongs.
*/
void setRepository(final Repository repository)
{
this.repository = repository;
}
/**
* Indicates whether or not the given <code>model</code>
* has changed since the previous call to this method.
*
* @return true/false
*/
public boolean isChanged()
{
boolean changed = this.getUris().length > 0;
if (changed)
{
final String modelKey = this.getKey();
Map<String, Long> lastModifiedTimes = modelModifiedTimes.get(modelKey);
// - load up the last modified times (from the model and all its modules)
// if they haven't been loaded yet
if (lastModifiedTimes != null)
{
final long modelLastModified = lastModifiedTimes.get(modelKey);
changed = this.getLastModified() > modelLastModified;
if (!changed)
{
// - check to see if any of the modules have changed if the model hasn't changed
final URL[] resources = this.getModuleSearchLocationResources();
for (final URL resource : resources)
{
final Long lastModified = lastModifiedTimes.get(resource.getFile());
if (lastModified != null)
{
// - when we find the first modified module, break out
if (ResourceUtils.getLastModifiedTime(resource) > lastModified)
{
changed = true;
break;
}
}
}
}
}
// - if our model (or modules) have changed re-load the last modified times
if (changed)
{
this.loadLastModifiedTimes();
}
}
return changed;
}
/**
* Loads (or re-loads) the last modified times from the
* {@link #getUris()} and the modules found on the module search path.
*/
private void loadLastModifiedTimes()
{
final String modelKey = this.getKey();
Map<String, Long> lastModifiedTimes = modelModifiedTimes.get(modelKey);
if (lastModifiedTimes == null)
{
lastModifiedTimes = new HashMap<String, Long>();
}
else
{
lastModifiedTimes.clear();
}
final URL[] resources = this.getModuleSearchLocationResources();
for (final URL resource : resources)
{
lastModifiedTimes.put(
resource.getFile(),
ResourceUtils.getLastModifiedTime(resource));
}
// - add the model key last so it overwrites any invalid ones
// we might have picked up from adding the module search location files.
lastModifiedTimes.put(
modelKey,
this.getLastModified());
modelModifiedTimes.put(
modelKey,
lastModifiedTimes);
}
/**
* Clears out the current last modified times.
*/
static void clearLastModifiedTimes()
{
modelModifiedTimes.clear();
}
}