====== Problèmes avec les fonctions de restrictions ======
L'idée est de définir un moyen permettant à partir d'un feature model **Source** de restreindre les choix possibles dans un FM **Target**. Nous avons plusieurs exemples de usecases où les fonctions de restrictions doivent agir différemment.
Je présente quelque usecases typique et ensuite les pistes déjà explorées / pourquoi ça ne fonctionne pas.
===== Usecases =====
==== Exemple 1 : Restriction sur les TypeInfo avec config finale ====
**Source : **
source = FM(Root: TypeInfo Product; TypeInfo: Picture; Product: Foo; Picture <-> Foo;)
**Target :**
target = FM(Root: TypeInfo Product; TypeInfo: (Picture|Calendar); Product: (X|Y); Picture <-> X; Calendar <-> Y;)
**Résultat attendu :**
result = FM(Root: TypeInfo Product; TypeInfo: Picture; Product: X; Picture <-> X;)
{{ :source-renderer.png?direct&500 |}}
==== Exemple 2 : Restriction sur les TypeInfo avec config partielle ====
**Source : **
source = FM(Root: TypeInfo Product; TypeInfo: (Picture|Calendar); Product: (Foo|Bar); Picture <-> Foo; Calendar <-> Bar;)
**Target :**
target = FM(Root: TypeInfo Product; TypeInfo: (Picture|Calendar|RSS|Sthg); Product: (X|Y|Z|T); Picture <-> X; Calendar <-> Y; RSS <-> Z; Sthg <-> T;)
**Résultat attendu :**
result = FM(Root: TypeInfo Product; TypeInfo: (Picture|Calendar); Product: (X|Y); Picture <-> X; Calendar <-> Y;)
{{ :source-renderer2.png?direct&700 |}}
==== Exemple 3 : Restriction sur les Zones ====
**Source : **
source = FM(Root: Product Element; Product: MainZone ScrollingBar; Element: Foo;)
**Target :**
target = FM(Root: Zone Product; Zone: MainZone [ScrollingBar]; Product: (EPU|CA); ScrollingBar <-> EPU;)
**Résultat attendu :**
result = FM(Root: Zone Product; Zone: MainZone ScrollingBar; Product: EPU; ScrollingBar <-> EPU;)
{{ :zone-layout.png?direct&600 |}}
===== Pistes envisagées =====
Un retour sur les différentes pistes envisagées avec quelques explications sur la raison pour laquelle elles ne suffisent pas. Comme on peut le voir dans les usecases, il existe toujours une partie commune entre les FM Source et Target, je me reférerais à cette partie en parlant de "la partie commune".
==== Piste 1 : Slice + intersection + aggregation ====
La première piste sur laquelle nous étions partis consistait à extraire la partie commune des FM Source et Target, à effectuer une opération d'intersection dessus, puis à réinjecter cette intersection dans le FM Target.
On a écrit avec Mathieu un script Familiar pour réaliser cette opération :
s1 = { source.TypeInfo } ++ source.TypeInfo.*
s2 = { target.TypeInfo } ++ target.TypeInfo.*
s1X = slice source including s1
s2X = slice target including s2
common = merge intersection {s1X s2X}
removeFeature target.TypeInfo
insert common into target.Root with MAND
cleanup target
Ce script semble très bien fonctionner (testé avec le UseCase 1) :
fml> target
target: (FEATURE_MODEL) Root: TypeInfo Product ;
TypeInfo: Picture ;
Product: X ;
(Calendar <-> false);
Sauf qu'en réalité :
fml> root target
res2: (FEATURE) Calendar
La représentation interne du FM est complètement fausse parce qu'on mélangé des opération sémantiques (merge, slice) avec des opérations syntaxiques : removeFeature, insert.
==== Piste 2 : Configuration + selection + asFM ====
La première piste ne fonctionnant pas, on a décidé de partir sur quelque chose de plus simple : piloter les restrictions par des opérations de sélection. On récupère les features de la partie commune et on les sélectionne dans le FM target.
Exemple de script :
s1 = source.TypeInfo.*
c = configuration target
s2 = setEmpty
foreach (f in s1) do setAdd s2 (name f) end
foreach (f in s2) do select f in c end
target = asFM c
Cette fois dans le Usecase 1 le script fonctionnera très bien. Par contre, le Usecase 2 plante directement : le script va tenter de sélectionner les 2 types d'infos qui s'excluent mutuellement au sein de la même configuration…
==== Piste 3 : Configuration + deselection + asFM ====
Pour éviter les problèmes posés par le script de sélection, on essaie de faire le contraire : de déselectionner pour ne garder que ce qui est valable. On récupère les features des parties communes et ensuite on déselectionne celles qui sont absentes de Source dans Target.
Pour se faire on utilise le script suivant :
sRestr = source.TypeInfo.*
sGlobal = target.TypeInfo.*
c = configuration target
sToDeselect = setEmpty
// make a diff between element of sGlobal and sRestr and put additionnal element of sGlobal in the set sToDeselect
foreach (f in sGlobal) do t = "" foreach (f2 in sRestr) do if ((name f) eq (name f2)) then t = (name f) end end if (t eq "") then setAdd sToDeselect (name f) end end
foreach (f in sToDeselect) do deselect f in c end
target = asFM c
La première boucle se contente de faire un parcours des deux sets de features et pour repérer quelles sont les features absentes du premier set : si elles sont absente alors on devra les déselectionner donc on les met dans un set particulier.
EDIT: en fait après relecture du script je peux obtenir exactement la même boucle avec setDiff sRestr sGlobal mais je ne connaissais pas l'opérateur à l'époque ;)
Le script fonctionne désormais pour les UseCase 1 et 2, mais pas pour le dernier : en effet, il n'y a plus de sélection donc rien qui force la feature ScrollingBar à passer de "Optionnel" à "Mandatory" et donc aucun impact sur le choix du produit final…
==== Piste 4 : Slice + double diff ====
Constatant que piloter les restrictions par des sélections / déselections ne semble pas fonctionner correctement, on tente de repartir sur de la logique.
L'idée était cette fois la suivante :
1. Réaliser un slice sur la partie commune dans Target et Source (comme pour la Piste 1)
2. Faire un diff entre les deux
3. Recréer la hiérarchie du diff jusqu'au root (comment ?)
4. Réaliser un diff du FM obtenu avec le FM target
En gros la dernière partie aurait pu se résumer par ce genre de script :
a = FM(Root: TI Product; TI: (Calendar|Picture|RSS); Product: (X|Y|Z); Calendar <-> X; Picture <-> Y; RSS <-> Z;)
b = FM(Root: TI; TI: RSS;)
merge diff {a b}
Sauf que le merge est sémantique or il n'y a aucune configuration commune entre les deux… Le résultat correspond à a dans le script au dessus.
===== Synthèse =====
Synthèse est sûrement un bien grand mot, mais juste pour résumer 2 ou 3 choses :
1. On ne peut pas utiliser les opérateurs sémantiques de Familiar uniquement. Pour la simple et bonne raison que nos produits sont inclus dans nos configurations et donc qu'on ne peut pas directement raisonner sur nos configurations : elles seront toujours dissemblables.
2. On ne peut pas uniquement raisonner sur des configurations : on a besoin de restreindre des FM à partir d'autres FMs qui ne sont PAS des configurations.
3. On ne peut pas actuellement pas raisonner sur des morceaux de FM de peur de perdre des contraintes cross-tree.
En rédigeant la dernière piste je viens de m'interroger sur l'utilisation possible de setDiff en correspondance avec l'opérateur slice. Cela pourrait fonctionner pour les Usecase 1 et 2, mais ça ne fonctionnerait pas pour le 3 le diff ne voit pas la différence entre la feature mandatory et la feature optionnelle.
A mon avis, il faudrait pouvoir utiliser un opérateur qui fasse une opération d'intersection purement syntaxique, en conservant les contraintes internes et qui ensuite puisse faire du ménage en reprenant la nouvelle sémantique du FM et en supprimant ce qui n'est plus utile.
Notre problème principal vient du fait que l'on souhaite changer la formule du FM par nos fonctions de restrictions…