digraph structs {
	node [shape=record,style=filled,color=red3];
@

#include "CommonTools-C++.cws"

function composeGraphVizLikeString(sText) {
	set sText = sText.replaceString(' ', "\\ ");
	set sText = sText.replaceString('<', "\\<");
	set sText = sText.replaceString('>', "\\>");
	return sText;
}

function composeGraphVizIdentifier(sText) {
	set sText = sText.replaceString('\\', '_');
	set sText = sText.replaceString('<', '_');
	set sText = sText.replaceString('>', '_');
	set sText = sText.replaceString(',', '_');
	set sText = sText.replaceString(':', '_');
	set sText = sText.replaceString('&', '_');
	set sText = sText.replaceString('*', '_');
	return sText;
}

function validate(myClass : node, theProperty : node) {
	local bSuccess;
	foreach i in this.descriptions {
		if myClass == i {
			set bSuccess = true;
			merge theProperty = i;
		} else if i.filter {
			executeString(myClass, "insert this.validate = " + i.filter + ";");
			if myClass.validate {
				set bSuccess = true;
				merge theProperty = i;
			}
		}
	}
	return bSuccess;
}

function validateAssociation(theOrigin : node, theDestination : node, theProperty : node) {
	local bSuccess;
	if this.associations.empty() return true;
	foreach i in this.associations {
		executeString(theOrigin, "insert this.validate = " + i.origin_filter + ";");
		if theOrigin.validate {
			executeString(theDestination, "insert this.validate = " + i.destination_filter + ";");
			if theDestination.validate {
				set bSuccess = true;
				merge theProperty = i;
			}
		}
	}
	return bSuccess;
}

function elementType(myType : node, myElementType : reference) {
	if existVariable(myType.template_parameters) {
		switch(myType) {
			case "vector":
			case "set":
			case "map":
				ref myElementType = myType.template_parameters#back;
				if this.typedefs.findElement(myElementType) && !existVariable(myElementType.nested_names) {
					ref myElementType = this.typedefs[myElementType];
				}
				return true;
			default:
				return false;
		}
	} else if this.typedefs.findElement(myType) && !existVariable(myType.nested_names) {
		return this.typedefs[myType].elementType(myElementType);
	}
}

function isObject(myType : node) {
	if myType.is_function return false;
	if this.typedefs.findElement(myType) && !existVariable(myType.nested_names) {
		return this.typedefs[myType].isObject();
	}
	local a = myType.charAt(0);
	if (a >= 'A') && (a <= 'Z') {
		local sType = myType;
		if !myType.template_parameters.empty() {
			set sType += '<';
			foreach i in myType.template_parameters {
				if !first(i) set sType += ',';
				set sType += getCppTargetType(i);
			}
			set sType += '>';
		}
		return sType;
	}
	local myElementType;
	if myType.elementType(myElementType) return myElementType.isObject();
	return false;
}

function getObject(myType : node, myClass : reference) {
	if myType.is_function return false;
	if this.typedefs.findElement(myType) && !existVariable(myType.nested_names) {
		return this.typedefs[myType].getObject(myClass);
	}
	if this.classes.findElement(myType) {
		local sType = myType;
		ref myClass = this.classes[myType];
		return true;
	}
	local myElementType;
	if myType.elementType(myElementType) return myElementType.getObject(myClass);
	return false;
}

function isArray(myType : node) {
	if (myType == "vector") && existVariable(myType.template_parameters) return true;
	if !myType.suffix.empty() && myType.suffix#front == "array" return true;
	if this.typedefs.findElement(myType) && !existVariable(myType.nested_names) return this.typedefs[myType].isArray();
}

function isAggregate(myType : node) {
	if myType.isObject() {
		if myType.suffix.empty() || myType.suffix#front != "pointer" {
			if this.typedefs.findElement(myType) && !existVariable(myType.nested_names) return this.typedefs[myType].isAggregate();
			return true;
		}
	}
	return false;
}

function isOptional(myType : node) {
	if myType.isObject() {
		if myType.suffix.empty() || myType.suffix#front != "pointer" {
			if this.typedefs.findElement(myType) && !existVariable(myType.nested_names) return this.typedefs[myType].isOptional();
			return false;
		}
		return true;
	}
	return false;
}

function serializeType(myType : node) {
	if myType.isArray() {
		local myElt;
		myType.elementType(myElt);
		return serializeType(myElt) + "[]";
	}
	return myType;
}

local displayed;
local toDisplay;

function generateClassBox(theClass : node, displayed : node, toDisplay : node) {
	local theProperty;
	local sClassIdentifier;
	if validate(theClass, theProperty) {
		local sClassName = theClass;
		if !theClass.template_parameters_specializations.empty() {
			set sClassName += "\\<";
			foreach j in theClass.template_parameters_specializations {
				if !first(j) set sClassName += ',';
				set sClassName += composeGraphVizLikeString(getCppTargetType(j));
			}
			set sClassName += "\\>";
		}
		set sClassIdentifier = sClassName.composeGraphVizIdentifier();
		insert displayed[sClassIdentifier];
		if !theClass.template_parameters.empty() {
			set sClassName += "\\<";
			foreach j in theClass.template_parameters {
				if !first(j) set sClassName += ',';
				set sClassName += key(j);
			}
			set sClassName += "\\>";
		}
		@	@sClassIdentifier@ [shape=record,label="{@sClassName@|{@
		local bAtLeastOne = false;
		if theProperty.attributes {
			foreach j in theClass.attributes {
				if !j.type.isObject()  {
					if !bAtLeastOne {
						bAtLeastOne = true;
					} else {
						@\n@
					}
					@@j@\ :\ @composeGraphVizLikeString(serializeType(j.type))@@
				}
			}
		}
		if !bAtLeastOne {
			@\n@
		}
		@}@
		if theProperty.methods {
			set bAtLeastOne = false;
			foreach j in theClass.methods {
				if !bAtLeastOne {
					bAtLeastOne = true;
					@|{@
				} else {
					@\n@
				}
				local sReturnType = composeGraphVizLikeString(j.return_type);
				if j.is_abstract {
					@[a] @
				} else if j.modifiers.findElement("virtual") {
					@[v] @
				}
				@@composeGraphVizLikeString(key(j))@@
				if sReturnType {
					@\ : \ @sReturnType@@
				}
			}
			if bAtLeastOne {
				@\n}@
			}
		}
		@}",fillcolor="#FCFBA6",];
@
	}
	foreach i in theClass.class_specializations if sClassIdentifier {
		local sSpecializedIdentifier = generateClassBox(i, displayed, toDisplay);
		if sSpecializedIdentifier {
			@	@sSpecializedIdentifier@ -> @sClassIdentifier@ [arrowhead=normal,arrowsize=1.0,style=dashed];
@
		}
	}
	return sClassIdentifier;
}

foreach i in this.classes {
	generateClassBox(i, displayed, toDisplay);
}
@
@
newFloatingLocation("blackboxes");
@
@
local tRelationShipCounter;
foreach i in this.classes {
	local theProperty;
	if validate(i, theProperty) {
		if theProperty.inheritance {
			foreach j in i.inheritance {
				if !displayed.findElement(j) insert toDisplay[j] = composeGraphVizLikeString(getCppTargetType(j));
				@	@i@ -> @j@ [arrowhead=empty,arrowsize=2.0];
@
			}
		}
		if theProperty.associations {
			foreach j in i.attributes {
				local sObjectName = j.type.isObject();
				if sObjectName {
					local theDestination;
					j.type.getObject(theDestination);
					local theAssociationProperty;
					if validateAssociation(i, theDestination, theAssociationProperty) {
						set sObjectName = composeGraphVizLikeString(sObjectName);
						local sRoleType = sObjectName.composeGraphVizIdentifier();
						if !theAssociationProperty.no_duplicate {
							increment(tRelationShipCounter[sRoleType]);
							set sRoleType += '_' + tRelationShipCounter[sRoleType];
						}
						if !displayed.findElement(sRoleType) insert toDisplay[sRoleType] = sObjectName;
						@	@i@ -> @sRoleType@ [@
						if j.type.isAggregate() {
							@arrowtail=diamond,@
						}
						local sRole = j;
						if j.type.isArray() {
							if j.type.isOptional {
								set sRole = "[0..*] " + sRole;
							} else {
								set sRole = "[1..*] " + sRole;
							}
						} else {
							if j.type.isOptional() {
								set sRole = "[0..1] " + sRole;
							} else {
								set sRole = "[1] " + sRole;
							}
						}
						@arrowhead=normal,arrowsize=1.0,headlabel="@sRole@"];
@
					}
				}
			}
		}
	}
}

foreach i in toDisplay {
	if !displayed.findElement(key(i)) {
		insert displayed[key(i)];
		insertTextOnce(getFloatingLocation("blackboxes"), "\t" + key(i) + " [shape=record,label=\"{" + i + "}\",fillcolor=\"#FCFBA6\",];" + endl());
	}
}

@}
