On a des cas où une structure XML a évolué et où l’on aimerait pouvoir accéder aux contenus des différentes versions à travers une unique interface Java. Pour cela,
avc-binding-dom permet de déclarer des sous-interfaces et suit un polymorphisme.
Prenons un exemple, avec une interface Java candidate à récupérer des informations d’un fichier xib (Apple iOS) :
public interface Xib {
boolean hasUseAutoLayout();
}
Les deux versions du XML qu’on veut savoir traiter renseignent l’information « useAutoLayout » de deux façons différentes.
Ici avec Xcode 5.0.2 :
<document
type="com.apple.InterfaceBuilder3.CocoaTouch.XIB"
version="3.0"
toolsVersion="4514"
systemVersion="12F45"
targetRuntime="iOS.CocoaTouch"
propertyAccessControl="none"
useAutolayout="YES">
...
</document>
Ici avec Xcode 4.6.3 :
<archive
type="com.apple.InterfaceBuilder3.CocoaTouch.XIB"
version="8.00">
<data>
...
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<bool key="IBDocument.UseAutolayout">YES</bool>
<string key="IBCocoaTouchPluginVersion">2083</string>
</data>
</archive>
Écrivons des interfaces de binding pour ces deux versions :
@XPath("/document[@version = '3.0']")
public interface Xib3 extends Xib {
@Override
@XPath("@useAutolayout = 'YES'")
boolean hasUseAutoLayout();
}
et :
@XPath("/archive[@version = '8.00']")
public interface Xib8 extends Xib {
@Override
@XPath(
"data/bool[@key = 'IBDocument.UseAutolayout'] = 'YES'")
boolean hasUseAutoLayout();
}
Pour lire des documents XML de versions différentes, on utilise les sous-interfaces pour le
binding effectif, en ne déclarant pour l’accès aux données qu’une seule variable du type de la super-interface :
Xib xib;
xib = DomBinderUtils.xmlContentToJava(new File(
"MyViewController_iPhone_3_0.xib"), Xib3.class);
assertTrue(xib.hasUseAutoLayout());
xib = DomBinderUtils.xmlContentToJava(new File(
"MyViewController_iPhone_8_0.xib"), Xib8.class);
assertTrue(xib.hasUseAutoLayout());
Maintenant, ajoutons des méthodes pour lire des sous-structures contenues dans le fichier xib :
public interface Xib {
boolean hasUseAutoLayout();
IBUIView getIBUIViewByTag(int tag); // e.g. 1104
interface IBUIView {
String getFrame(); // e.g. "{{82, 77}, {43, 43}}"
String getUserLabel(); // e.g. "LButton 20"
}
}
Évidemment, les expressions XPath ne sont pas du tout les mêmes pour les deux structures XML.
Pour Xcode 5.0.2 :
@XPath("/document[@version = '3.0']")
public interface Xib3 extends Xib {
@Override
@XPath("@useAutolayout = 'YES'")
boolean hasUseAutoLayout();
@Override
@XPath("//*[@tag = $arg0]")
IBUIView3 getIBUIViewByTag(int tag);
interface IBUIView3 extends Xib.IBUIView {
@Override
@XPath("concat('{{', rect[@key = 'frame']/@x, ', '"
+ ", rect[@key = 'frame']/@y, '}, {'"
+ ", rect[@key = 'frame']/@width, ', '"
+ ", rect[@key = 'frame']/@height, '}}')")
String getFrame(); // e.g. "{{82, 77}, {43, 43}}"
@Override
@XPath("@userLabel")
String getUserLabel(); // e.g. "LButton 20"
}
}
Et pour Xcode 4.6.3 :
@XPath("/archive[@version = '8.00']")
public interface Xib8 extends Xib {
@Override
@XPath(
"data/bool[@key = 'IBDocument.UseAutolayout'] = 'YES'")
boolean hasUseAutoLayout();
@Override
@XPath("//object[int[@key = 'IBUITag'] = $arg0] ")
IBUIView8 getIBUIViewByTag(int tag);
interface IBUIView8 extends Xib.IBUIView {
@Override
@XPath("string[@key = 'NSFrame']")
String getFrame(); // e.g. "{{82, 77}, {43, 43}}"
@Override
@XPath("//object[@class = 'IBObjectRecord'"
+ " and reference[@key = 'object']/"
+ "@ref = $this/@id]/"
+ "string[@key = 'objectName']")
String getUserLabel(); // e.g. "LButton 20"
}
}
Quel que soit le
binding initial de l’objet « xib », on pourra valider le code suivant
grâce au polymorphisme :
final IBUIView view = xib.getIBUIViewByTag(1104);
assertEquals("{{130, 100}, {128, 128}}", view.getFrame());
assertEquals("Bouton OK", view.getUserLabel());
Passons maintenant à de l’héritage qui ajoute des fonctionnalités :

Dans notre exemple, les interfaces IBUIButton dérivent des interfaces IBUIView, en leur ajoutant la méthode « getNormalTitle() »,
dont l’annotation @XPath dépend encore une fois de la version du document XML.
avc-binding-dom permet alors d’écrire ceci :
final IBUIView view = xib.getIBUIViewByTag(1104);
final IBUIButton button = BinderUtils.rebind(
view, IBUIButton8.class);
assertEquals("OK", button.getNormalTitle());
Eh oui, les relations d’héritage dans notre diagramme ne permettent pas de remonter à IBUIButton8 à partir
de « Xib8.getIBUIViewByTag(int): IBUIView8 ».
Il faut faire une sorte de transtypage grâce à la méthode rebind().
Noter que d’autres possibilités de transtypage
existent, par exemple la méthode self(Class<?>)
quand l’interface de
binding dérive explicitement de Binding<Node>, mais
rebind() est une solution qui fonctionne dans tous les cas.
Pour que le transtypage fonctionne de façon transparente
avec des interfaces génériques, c’est-à-dire si on voulait
pouvoir écrire « rebind(view, IBUIButton.class) »
au lieu de « rebind(view, IBUIButton
8.class) »,
il faudrait plutôt que
« Xib8.getIBUIViewByTag(int) » renvoie directement un IBUI
Button8
et non un IBUIView8.
Le diagramme devient alors :
Et le code :
final IBUIView view = xib.getIBUIViewByTag(1104);
// ni rebind(), ni IBUIButton8, ni IBUIButton3
final IBUIButton button = (IBUIButton) view;
assertEquals("OK", button.getNormalTitle());
Voilà
:-)
Edit :
pour le dernier exemple, je montre qu’on peut utiliser un
cast,
plus simple qu’un rebind().
Note : les bouts de code et les exemples de cet article sont inclus dans les tests unitaires de
avc-binding-dom.