Test coverage report for MetaModelElement.java - www.sdmetrics.com

/*
 * SDMetrics Open Core for UML design measurement
 * Copyright (c) Juergen Wuest
 * To contact the author, see <http://www.sdmetrics.com/Contact.html>.
 * 
 * This file is part of the SDMetrics Open Core.
 * 
 * SDMetrics Open Core is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
    
 * SDMetrics Open Core is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with SDMetrics Open Core.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package com.sdmetrics.model;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Represents an element type of the SDMetrics metamodel.
 * <p>
 * An element type has a name, a parent element type, and a list of attributes.
 * Attributes can contain data or references to model elements, and may be
 * single-valued or multi-valued.
 */
public class MetaModelElement {
	/** Name of the attribute with the XMI ID of a model element. */
	static final String ID = "id";
	/** Name of the attribute with the name of a model element. */
	static final String NAME = "name";
	/** Name of the attribute with the owner of a model element. */
	static final String CONTEXT = "context";

	/** Name of this element type. */
	private final String typeName;
	/**
	 * The attributes of this element type. Allows lookup of the attributes by
	 * their name.
	 */
	private final Map<String, MetaModelElementAttribute> attributes = 
			new LinkedHashMap<>(4);
	/** The parent type of this element type. */
	private MetaModelElement parent;
	/** Name of the extension reference attribute, if any. */
	private String extensionReference;

	/** Stores the definition of a metamodel element attribute. */
	static class MetaModelElementAttribute {
		/** Name of the attribute. */
		String attrName;
		/** Indicates if this attribute references other model elements. */
		boolean isReference;
		/** Indicates if this is a multi-valued attribute. */
		boolean isSet;
		/** Description text of the attribute. */
		String description;
		/** Index of the attribute, for efficient array storage. */
		int index;

		/**
		 * Constructor.
		 * @param name Name of the attribute
		 * @param isRef Indicates if the attribute references other model
		 *        elements
		 * @param isSet Indicates if this is a multi-valued attribute
		 * @param index Index of the attribute
		 */
		MetaModelElementAttribute(String name, boolean isRef, boolean isSet,
				int index) {
			this.attrName = name;
			this.isReference = isRef;
			this.isSet = isSet;
			this.description = "";
			this.index = index;
		}
	}

	/**
	 * Creates a element type.
	 * 
	 * @param name Name of the type.
	 * @param parent The parent of the type.
	 */
	MetaModelElement(String name, MetaModelElement parent) {
		this.typeName = name;
		if (parent != null) {
			// inherit all of the parent's attributes
			this.parent = parent;
			for (MetaModelElementAttribute parentAttribute : parent.attributes
					.values()) {
				attributes.put(parentAttribute.attrName, parentAttribute);
			}
			this.extensionReference = parent.extensionReference;
		}
	}

	/**
	 * Gets the name of this element type.
	 * 
	 * @return Name of the element type.
	 */
	public String getName() {
		return typeName;
	}

	/**
	 * Gets the parent of this element type.
	 * 
	 * @return Parent of the metamodel element, <code>null</code> if this is the
	 *         metamodel base element type.
	 * @since 2.3
	 */
	public MetaModelElement getParent() {
		return parent;
	}

	/**
	 * Checks if this type specializes another type.
	 * 
	 * @param base Base type against which to check
	 * @return <code>true</code> if the <code>base</code> is the same type as
	 *         this type, or a direct or indirect parent of this type.
	 * @since 2.3
	 */
	public boolean specializes(MetaModelElement base) {
		MetaModelElement type = this;
		while (type != null) {
			if (type == base) {
				return true;
			}
			type = type.getParent();
		}
		return false;
	}

	/**
	 * Gets the attribute names of the metamodel element. This includes
	 * inherited attributes. The collection maintains the attributes in the
	 * order in which they were defined in the metamodel definition file;
	 * inherited elements are listed first.
	 * 
	 * @return Collection of attribute names
	 */
	public Collection<String> getAttributeNames() {
		return attributes.keySet();
	}

	/**
	 * Tests if this element type has an attribute of a given name.
	 * 
	 * @param name Name of the candidate attribute
	 * @return <code>true</code> if this element type has an attribute of that
	 *         name.
	 */
	public boolean hasAttribute(String name) {
		return attributes.containsKey(name);
	}

	/**
	 * Tests if an attribute is a cross-reference attribute.
	 * 
	 * @param name Name of the attribute to test.
	 * @return <code>true</code> if the attribute is a cross-reference
	 *         attribute, <code>false</code> if it is a data attribute.
	 * @throws IllegalArgumentException Element type has no such attribute.
	 */
	public boolean isRefAttribute(String name) {
		return getAttribute(name).isReference;
	}

	/**
	 * Gets the name of the extension reference attribute.
	 * 
	 * @return Name of the extension reference attribute, or <code>null</code>
	 *         if none is defined.
	 * @since 2.3
	 */
	public String getExtensionReference() {
		return extensionReference;
	}

	/**
	 * Tests if an attribute is multi-valued.
	 * 
	 * @param name Name of the attribute to test.
	 * @return <code>true</code> if the attribute is multi-valued,
	 *         <code>false</code> if it only stores a single value.
	 * @throws IllegalArgumentException Element type has no such attribute.
	 */
	public boolean isSetAttribute(String name) {
		return getAttribute(name).isSet;
	}

	/**
	 * Gets the description of an attribute.
	 * 
	 * @param name Name of the attribute.
	 * @return Informal description of the attribute.
	 * @throws IllegalArgumentException Element type has no such attribute.
	 */
	public String getAttributeDescription(String name) {
		return getAttribute(name).description;
	}

	/**
	 * Adds an attribute to this element type.
	 * 
	 * @param attrName Name of the attribute.
	 * @param isRef <code>true</code> if this is to be a cross-reference
	 *        attribute, <code>false</code> if it is data-valued.
	 * @param isSet <code>true</code> if this is to be a multi-valued attribute,
	 *        <code>false</code> if it is single-valued.
	 */
	void addAttribute(String attrName, boolean isRef, boolean isSet) {
		MetaModelElementAttribute attrib = new MetaModelElementAttribute(
				attrName, isRef, isSet, attributes.size());
		attributes.put(attrName, attrib);
	}

	/**
	 * Gets the index of an attribute. Each attribute of an element type has a
	 * unique index. Attribute indices go from 0 to N-1, where N is the total
	 * number of attributes of this element type.
	 * 
	 * @param name Name of the attribute
	 * @return Index of the attribute in this element type
	 * @throws IllegalArgumentException Element type has no such attribute.
	 */
	int getAttributeIndex(String name) {
		return getAttribute(name).index;
	}

	/**
	 * Adds text to the description of an attribute.
	 * 
	 * @param name Name of the attribute.
	 * @param description Text to add to the attribute's description.
	 * @throws IllegalArgumentException Element type has no such attribute.
	 */
	void addAttributeDescription(String name, String description) {
		MetaModelElementAttribute attr = getAttribute(name);
		attr.description = attr.description + description;
	}

	/**
	 * Gets the attribute definition for an attribute.
	 * 
	 * @param name Name of the attribute
	 * @return The definition of the attribute
	 * @throws IllegalArgumentException Element type has no such attribute.
	 */
	private MetaModelElementAttribute getAttribute(String name) {
		MetaModelElementAttribute attr = attributes.get(name);
		if (attr == null) {
			throw new IllegalArgumentException("No attribute \"" + name
					+ "\" defined for elements of type \"" + typeName + "\".");
		}
		return attr;
	}

	/**
	 * Sets the name of the extension reference attribute.
	 * 
	 * @param retAttrName Name of the extension reference attribute
	 */
	void setExtensionReference(String retAttrName) {
		this.extensionReference = retAttrName;
	}
}