/**
* Copyright (c) 2009-2011, The HATS Consortium. All rights reserved.
* This file is licensed under the terms of the Modified BSD License.
*/
package org.absmodels.abs.plugin.navigator;
import static abs.common.Constants.STDLIB_NAME;
import static org.absmodels.abs.plugin.util.UtilityFunctions.hasDecls;
import java.util.*;
import org.absmodels.abs.plugin.builder.AbsNature;
import org.absmodels.abs.plugin.util.InternalASTNode;
import abs.frontend.ast.Model;
import abs.frontend.ast.ModuleDecl;
/**
* A ModulePath represents the ABS module hierarchy with a specified modulePath as prefix
* @author cseise
*
*/
public class ModulePath {
/**
* The current prefix of this ModulePath instance
*/
private String modulePath;
/**
* AbsNature to retrieve the current {@link Model}. Additionally used for synchronization.
*/
final private AbsNature absNature;
/**
* Cached, synchronized list of all children of a ModulPath
*/
private Collection<Object> internalChildren;
/**
* Caching mechanism, mapping a root ModulePath to an ABSNature
*/
private static Map<AbsNature,ModulePath> rootPath = new HashMap<AbsNature,ModulePath>();
/**
* Creates a new ModulePath
*
* @param nature
* The AbsNature this ModulePath is bound to. If the given
* AbsNature is null, an IllegalArgumentException will be thrown.
* @param initialModulePath
* The prefix of this modulePath. Used for calculating its
* children. The modulePath can also be an empty String
* indicating that is ModulePath instance represents the module
* root
* @throws IllegalArgumentException if nature is null
*/
public ModulePath(AbsNature nature, String initialModulePath){
if (nature == null) {
throw new IllegalArgumentException("A Module Path can only be instantiated with an ABSNature != null!");
}
absNature = nature;
modulePath = initialModulePath;
//To avoid race conditions, internalChildren is synchronized
internalChildren = Collections.synchronizedCollection((List<Object>)new ArrayList<Object>());
}
/**
* @return The current module prefix of the ModulePath instance
*/
public String getModulePath() {
return modulePath;
}
/**
*
* @return The {@link AbsNature} stored in this ModulePath instance. The
* return value should never be null.
*/
public AbsNature getNature() {
return absNature;
}
/**
* Calculates the Children of the ModulePath.
*
* @return The children of this ModulePaths, which are both subordinate to
* ModulePaths and ModuleDecls. The list will be empty, if the
* ModulePath's nature is null.
*/
public List<Object> getChildModulePathsAndModuleDecls() {
Model model = absNature.getCompleteModel();
if (model != null) {
ArrayList<Object> paths = new ArrayList<Object>();
Set<ModulePath> names;
synchronized (absNature.modelLock) {
names = getModuleHierarchyForPrefix(modulePath);
}
paths.addAll(names);
// There was a change in the modulePath structure
if (internalChildren.isEmpty() || !internalChildren.containsAll(paths) || !paths.containsAll(internalChildren)) {
//Adapt internalChildren to model
addNewModulePaths(paths);
removeDeletedModulePaths(paths);
}
return addModulesWithoutHierarchy(internalChildren);
}
return Collections.emptyList();
}
/**
* Removes ModulePaths from the internalChildren Collection that are specified by paths
* @param paths
*/
private void removeDeletedModulePaths(ArrayList<Object> paths) {
ArrayList<ModulePath> removeModulePaths= new ArrayList<ModulePath>();
for (Object mp : internalChildren) {
if (!paths.contains(mp) && mp instanceof ModulePath) {
removeModulePaths.add((ModulePath) mp);
}
}
internalChildren.removeAll(removeModulePaths);
}
/**
* Removes ModulePaths from the internalChildren Collection that are specified by paths
* @param paths
*/
private void addNewModulePaths(ArrayList<Object> paths) {
ArrayList<Object> addModulePaths= new ArrayList<Object>();
for (Object path : paths) {
if (!internalChildren.contains(path)) {
addModulePaths.add(path);
}
}
internalChildren.addAll(addModulePaths);
}
/**
* Helper method for adding modules without any module hierarchy, that means
* their name does not contain '.'
*
* @param moduleHierarchy
* @return union of moduleHierarchy and the list of modules that do not contain '.'
*/
private List<Object> addModulesWithoutHierarchy(Collection<Object> moduleHierarchy) {
ArrayList<Object> ret = new ArrayList<Object>();
ret.addAll(moduleHierarchy);
synchronized (absNature.modelLock) {
ret.addAll(getModulesForPrefix());
}
return ret;
}
/**
* Calculates the <b>first</b> layer (prefix := "") of the module hierarchy
* and caches its results
*
* @param nature The ABSNature whose root hierarchy should be calculated
* @return The children of the modulePath with prefix = ""
* @throws IllegalArgumentException when the nature is null
*/
public static List<Object> getRootHierarchy(AbsNature nature){
ModulePath root;
if (rootPath.containsKey(nature)) {
root = rootPath.get(nature);
} else {
root = new ModulePath(nature, "");
rootPath.put(nature, root);
}
return root.getChildModulePathsAndModuleDecls();
}
/**
* Clears the cache maintained by getRootHierarchy
*/
public static void clearRootHierarchyCache() {
rootPath = new HashMap<AbsNature, ModulePath>();
}
/**
* Collects ModuleDecls with a given prefix. Only ModuleDecls with a name
* prefix.Name are returned, ModuleDecls with a name prefix.subPrefix.Name
* are ignored.
*
* prefix is the current module path of this instance.
*
* @return Only ModuleDecls with a name prefix.Name are returned,
* ModuleDecls with a name prefix.subPrefix.Name are ignored
*/
public Set<InternalASTNode<ModuleDecl>> getModulesForPrefix() {
LinkedHashSet<InternalASTNode<ModuleDecl>> names = new LinkedHashSet<InternalASTNode<ModuleDecl>>();
Model model = absNature.getCompleteModel();
if ("".equals(modulePath)) {
// Get the first category of module elements
for (ModuleDecl m : model.getModuleDecls()) {
String name = m.getName();
if (!name.equals(STDLIB_NAME)) {
if (name.indexOf('.') < 0 &&
!hasSubModule(name) &&
!name.equals(modulePath) &&
!names.contains(m)) { // FIXME: Review, can a ModuleDecl really be inside Set<nternalASTNode<..>>? GC_UNRELATED_TYPES
names.add(new InternalASTNode<ModuleDecl>(m, absNature));
}
}
}
return names;
} else {
for (ModuleDecl m : model.getModuleDecls()) {
String name = m.getName();
if (!name.equals(STDLIB_NAME)) {
String regex = NavigatorUtils.buildRegex(modulePath);
if ((name.matches(regex) && !existsSubLayer(name)) && !hasSubModule(name)) {
names.add(new InternalASTNode<ModuleDecl>(m, absNature));
}
}
}
return names;
}
}
private Set<ModulePath> getModuleHierarchyForPrefix(String prefix) {
HashSet<String> names = new HashSet<String>();
Model model = absNature.getCompleteModel();
if ("".equals(prefix)) {
// Get the first category of module elements
for (String name : getModuleNames(model)) {
if (!name.equals(STDLIB_NAME)){
if (name.indexOf('.') > -1) {
String subName = name.substring(0, name.indexOf('.'));
if (!names.contains(subName)){
names.add(subName);
}
}
}
}
HashSet<ModulePath> paths = new HashSet<ModulePath>();
for (String name : names){
paths.add(new ModulePath(absNature, name));
}
return paths;
} else {
for (String name : getModuleNames(model)) {
if (name.startsWith(prefix + '.')) {
String modWithoutPrefix = name.substring(prefix.length() + 1, name.length());
if (modWithoutPrefix.indexOf('.') > -1) {
String afterPrefix = modWithoutPrefix.substring(0, modWithoutPrefix.indexOf('.'));
names.add(prefix + "." + afterPrefix);
}
}
}
HashSet<ModulePath> paths = new HashSet<ModulePath>();
for (String name : names){
paths.add(new ModulePath(absNature, name));
}
return paths;
}
}
private boolean hasSubModule(String name){
for (String moduleName : getModuleNames(absNature.getCompleteModel())){
if (moduleName.startsWith(name+".")){
return true;
}
}
return false;
}
private boolean existsSubLayer(String name) {
return (getModuleHierarchyForPrefix(name).size() > 0);
}
/**
* Determines, whether the AbsNature's Model contains a ModuleDecl with
* exactly the ModulePath's name that has any kind of declarations
*
* @return True, if the Model contains a ModuleDecl with exactly the
* ModulePath's name having declarations <br/>
* False, else
*/
public boolean hasModuleWithDecls() {
synchronized (absNature.modelLock) {
for (ModuleDecl m : absNature.getCompleteModel().getModuleDecls()) {
if (m.getName().equals(modulePath) && hasDecls(m)) {
return true;
}
}
}
return false;
}
/**
* Determines, whether the ModulePaths AbsNature (respectively its
* associated Model) contains a ModuleDecl with exactly the ModulePath's
* name
*
* @return True, if the Model contains a ModuleDecl with exactly the
* ModulePath's <br/>
* False, else
*/
public boolean hasModule() {
synchronized (absNature.modelLock) {
for (ModuleDecl m : absNature.getCompleteModel().getModuleDecls()) {
if (m.getName().equals(modulePath)) {
return true;
}
}
}
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((absNature == null) ? 0 : absNature.hashCode());
result = prime * result + ((modulePath == null) ? 0 : modulePath.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof ModulePath))
return false;
ModulePath other = (ModulePath) obj;
if (absNature == null) {
if (other.absNature != null)
return false;
} else if (!absNature.equals(other.absNature))
return false;
if (modulePath == null) {
if (other.modulePath != null)
return false;
} else if (!modulePath.equals(other.modulePath))
return false;
return true;
}
/**
* @return The ModuleDecl with exactly the name of the ModulePath, or null
* if no such ModuleDecl exists
*/
public InternalASTNode<ModuleDecl> getModuleDecl() {
synchronized (absNature.modelLock) {
Model model = absNature.getCompleteModel();
for (ModuleDecl m : model.getModuleDecls()) {
if (m.getName().equals(modulePath)) {
return new InternalASTNode<ModuleDecl>(m,absNature);
}
}
}
return null;
}
@Override
public String toString(){
return this.modulePath;
}
/**
* @param Model model
* @return A String array with the names of all the model's ModuleDecls
*/
private String[] getModuleNames(Model model) {
ArrayList<String> list = new ArrayList<String>();
synchronized (absNature.modelLock) {
for (ModuleDecl m : model.getModuleDecls()) {
list.add(m.getName());
}
}
return list.toArray(new String[0]);
}
}