blob: 1aee2cec481add841f31a10a63b417214e372b8d [file] [log] [blame]
// Copyright (c) 2000-2019 Ericsson Telecom AB Telecom AB //
// All rights reserved. This program and the accompanying materials are made available under the //
// terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at //
// https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html //
///////////////////////////////////////////////////////////////////////////////////////////////////////
function HelpTreeBuilder(p_flatHelp) {
"use strict";
var v_flatHelp = p_flatHelp;
this.getHelpTree = function(toSort) {
var help = {"sources": []};
for (var i = 0; i < v_flatHelp.sources.length; ++i) {
var source = v_flatHelp.sources[i].source;
var dataElements = v_flatHelp.sources[i].dataElements;
var tree = [];
help.sources.push({
"source": source,
"dataElements": tree
});
collectRemaining(dataElements, tree, false);
if (toSort === true) {
sortChildren(tree);
}
}
return help;
};
function sortChildren(dataElements) {
dataElements.sort(dataElementSort);
for (var i = 0; i < dataElements.length; ++i) {
traverseTree(dataElements[i], [dataElements[i]], function(path) {
var dataElement = path[path.length - 1];
if (dataElement.children != undefined && dataElement.children.length != 0) {
dataElement.children.sort(dataElementSort);
}
});
}
}
function dataElementSort(a, b) {
if (hasChildren(a)) {
if (hasChildren(b)) {
return dataElementNameCompare(a,b);
} else {
return -1;
}
} else if (hasChildren(b)) {
return 1;
} else {
return dataElementNameCompare(a,b);
}
}
function hasChildren(dataElement) {
return dataElement.children != undefined && dataElement.children.length != 0
}
function dataElementNameCompare(a, b) {
if (a.dataElement.name < b.dataElement.name) {
return -1;
} else if (a.dataElement.name > b.dataElement.name) {
return 1;
} else {
return 0;
}
}
function collectRemaining(dataElements, tree, force) {
var originalLength = dataElements.length;
var inserted = true;
while (inserted) {
var i = 0;
inserted = false;
while (i < dataElements.length) {
if (insert(dataElements[i], tree, force)) {
inserted = true;
dataElements.splice(i, 1);
} else {
++i;
}
}
}
// if we did not insert anything but should have
if (dataElements.length == originalLength && dataElements.length != 0) {
if (window["console"] != undefined && console.log != undefined) {
console.log("Failed to insert some elements into the help tree");
console.log(dataElements);
}
// insert the remaining to the root
for (var i = 0; i < dataElements.length; ++i) {
tree.push(dataElements[i]);
}
// if there are still some elements left, force insert them
} else if (dataElements.length != 0) {
// TODO: we could make this not recursive, but it is unlikely to cause problems
collectRemaining(dataElements, tree, true);
}
}
function insert(dataElement, tree, force) {
var references = getReferencedTypes(dataElement);
if (references.length == 0) {
// we can insert it into the root
tree.push(dataElement);
return true;
} else {
return insertIntoBestMatch(dataElement, references, tree, force);
}
}
function insertIntoBestMatch(dataElement, references, tree, force) {
// we give a score to every possible location and choose those that have a maximum score
var bestScore = 0;
var bestObjects = [];
var allReferencesFound = false;
for (var i = 0; i < tree.length; ++i) {
traverseTree(tree[i], [tree[i]], function(path) {
var scoreObj = getScore(path, mcopy(references));
scoreObj["bestMatch"] = path[path.length - 1];
var score = scoreObj.score;
if (score > bestScore) {
bestScore = score;
bestObjects = [scoreObj];
allReferencesFound = scoreObj.allReferencesFound
} else if (score == bestScore && allReferencesFound == scoreObj.allReferencesFound) {
bestObjects.push(scoreObj);
}
});
}
if (allReferencesFound) {
// we can insert the element, since we found all references
for (var i = 0; i < bestObjects.length; ++i) {
if (bestObjects[i].bestMatch.children != undefined) {
bestObjects[i].bestMatch.children.push(dataElement);
} else {
bestObjects[i].bestMatch.children = [dataElement];
}
}
return true;
} else if (force) {
// we must insert the missing references too, but it is possible that we cannot insert it this way either.
return insertMissingReferences(dataElement, bestObjects, tree);
} else {
// we could not insert the element
return false;
}
}
function findAllReferences(references, tree) {
for (var i = 0; i < tree.length && references.length > 0; ++i) {
traverseTree(tree[i], [tree[i]], function(path) {
var type = getTypeName(path[path.length - 1]);
var index = references.indexOf(type);
if (index != -1) {
references.splice(index, 1);
}
});
}
return references.length == 0;
}
function traverseTree(dateElement, path, process) {
process(path);
if (dateElement.children != undefined) {
var tree = dateElement.children;
for (var i = 0; i < tree.length; ++i) {
path.push(tree[i]);
traverseTree(tree[i], path, process);
path.pop();
}
}
}
function getScore(path, references) {
var ref = mcopy(references);
var score = 0;
var types = [];
for (var i = 0; i < path.length; ++i) {
var type = getTypeName(path[i]);
var referencesIndex = references.indexOf(type);
if (types.indexOf(type) == -1) {
types.push(type);
if (referencesIndex != -1) {
references.splice(referencesIndex, 1);
// if we find a reference, increase the score by 1
++score;
}
}
}
// the larger the depth, the larger the value we decrease the score with
score -= path.length / 20;
var allReferencesFound = references.length == 0;
if (allReferencesFound) {
// if we find all references the score will be large
score += 100;
}
return {
"score": score,
"allReferencesFound": allReferencesFound,
"missingReferences": references,
};
}
function insertMissingReferences(dataElement, bestMatches, tree) {
// the less elements we have to insert additionally, the higher the score
var maxScore = 0;
var maxObjects = [];
for (var i = 0; i < bestMatches.length; ++i) {
var bestMatch = bestMatches[i].bestMatch;
var missingReferences = bestMatches[i].missingReferences;
if (!findAllReferences(mcopy(missingReferences), tree)) {
// we could not find every reference in the whole tree, maybe later
continue;
}
var bestScore = 0;
var bestPath;
var stillMissing;
for (var i = 0; i < tree.length; ++i) {
traverseTree(tree[i], [tree[i]], function(path) {
var scoreObj = getScore(path, mcopy(missingReferences));
if (scoreObj.score > bestScore) {
bestScore = scoreObj.score;
bestPath = mcopy(path);
stillMissing = scoreObj.missingReferences;
}
});
}
if (bestScore > maxScore) {
maxScore = bestScore;
maxObjects = [{
"bestMatch": bestMatch,
"bestPath": bestPath,
"missingReferences": stillMissing
}];
} else if (bestScore == maxScore) {
maxObjects.push({
"bestMatch": bestMatch,
"bestPath": bestPath,
"missingReferences": stillMissing
});
}
}
if (maxObjects.length == 0) {
// we could not find any element in the tree that would allow us to insert the current element
return false;
}
for (var i = 0; i < maxObjects.length; ++i) {
// we insert the whole path to the missing reference to be sure
var newBestMatch = insertPath(maxObjects[i].bestMatch, maxObjects[i].bestPath);
if (maxObjects[i].missingReferences.length != 0) {
insertMissingReferences(dataElement, [maxObjects[i]], tree);
} else {
if (newBestMatch.children != undefined) {
newBestMatch.children.push(dataElement);
} else {
newBestMatch.children = [dataElement];
}
}
}
return true;
}
function insertPath(insertInto, path) {
for (var i = 0; i < path.length; ++i) {
var obj = shallowcopy(path[i]);
obj.children = undefined;
if (insertInto.children != undefined) {
insertInto.children.push(obj);
} else {
insertInto.children = [obj];
}
insertInto = obj;
}
return insertInto;
}
function getTypeName(p_dataElement) {
var dataElement = p_dataElement.dataElement;
if (dataElement.typeDescriptor != undefined && dataElement.typeDescriptor.typeName != undefined) {
return dataElement.typeDescriptor.typeName
} else {
return undefined;
}
}
function getReferencedTypes(p_dataElement) {
var dataElement = p_dataElement.dataElement;
var references = [];
if (dataElement.parameters != undefined) {
for (var i = 0; i < dataElement.parameters.length; ++i) {
if (dataElement.parameters[i].typeDescriptor != undefined && dataElement.parameters[i].typeDescriptor.reference != undefined && dataElement.parameters[i].typeDescriptor.reference.typeName != undefined) {
references.push(dataElement.parameters[i].typeDescriptor.reference.typeName);
}
}
}
return references;
}
}