IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Collection et Stream Java Discussion :

ResourceBundle.getString sans MissingResourceException


Sujet :

Collection et Stream Java

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 22
    Par défaut ResourceBundle.getString sans MissingResourceException
    Bonjour à tous,

    Je travaille actuellement sur une interface graphique qui utilise donc une instance de ResourceBundle pour l'internationalisation. Or comme vous le savez sûrement déjà, la méthode getString(String key) lance l'exception MissingResourceException lorsque la clé est introuvable. Jusque là, je n'avais aucun problème, parce que toutes les parties internationalisables étaient clairement définies, et identifiables par une clé.

    Jusqu'à maintenant.

    Une partie de l'application est générée en fonction de la configuration. Cette partie comprend un graphique qui se trouve dans un onglet d'un JTabbedPane. Il y a donc le nom de l'onglet, et la légende du graphe qui doivent être internationalisé. Chacune des clés des ressources est donc calculée et peut varier en fonction de la configuration. Il peut donc arriver que la configuration amène donc le programme à chercher des clés qui n'existent pas. Les contraintes sont les suivantes :
    • Aucune exception ne doit arriver dans l'EDT, cela empêche l'affichage de l'écran qui contient également des parties prédéfinies
    • Du fait de la structure du code, les recherches de texte internationalisé sont assez dispersées dans le code, et non facilement factorisable.
    • J'aimerai toucher le moins possible à l'existant, avec une solution propre, càd utiliser au maximum les API Java standard.


    Mon but premier est donc que l'instance du ResourceBundle renvoie la clé elle-même lorsque getString() est appelée si la ressource n'existe pas. En fait, pour être clair, je voudrais que la classe ResourceBundle se comporte comme le fameux gettext.

    L'héritage est impossible, les méthodes getObject et getString sont déclarées final.
    L'encapsulation est également écartée pour la même raison, et parce que je tiens à conserver l'utilisation de la classe ResourceBundle. Tout le code l'utilise actuellement.

    Pensez-vous qu'il y a une solution ? En avez-vous une ? Ou bien faudra-t-il que je me contente d'ouvrir une RFE sur le bugtracker de Sun ?

  2. #2
    Modérateur
    Avatar de dinobogan
    Homme Profil pro
    ingénieur
    Inscrit en
    Juin 2007
    Messages
    4 073
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations professionnelles :
    Activité : ingénieur
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 4 073
    Par défaut
    D'après tes contraintes, la seule solution que je vois est de créer un fichier de ressources de base (sans extensions) avec la totalité des mots clé, chacun ayant pour valeur le mot clé lui-même.
    Tu vas devoir faire une recherche dans tous le projet pour connaître la liste exhaustive des mots clés utilisés, tu n'as pas vraiment le choix.
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java
    Que la force de la puissance soit avec le courage de ta sagesse.

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 22
    Par défaut
    Ah ça par contre, ça va être assez difficile

    J'ai oublié de préciser clairement que les clés des parties dynamiques de l'interface sont générées en partie avec la configuration, qui peut changer.
    Adaptée à ma situation, ta solution reviendrait à générer un tableau contents pour en faire un ListResourceBundle, et le rajouter ou le déclarer comme bundle racine. Pas forcément évident, ni le plus élégant, mais cela permettrait de garder le ResourceBundle comme classe.

    Bon en gros, merci de m'avoir mis sur une piste

  4. #4
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par hermes1983 Voir le message
    Ah ça par contre, ça va être assez difficile
    Pas forcément : tu n'est pas obligé d'utiliser un fichier *.properties

    Combien as-tu de fichier *.properties ? As-tu un fichier de base (sans notion de langue) ?

    a++

  5. #5
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 22
    Par défaut
    J'ai effectivement un fichier de base (message.properties) et un fichier par locale utilisée (message_fr_FR.properties par exemple, bien que ce ne soit pas le seul). Par contre, je n'ai actuellement qu'un seul fichier pour toutes les chaînes de l'application.

    Par contre, qu'est-ce que tu entends par "tu n'es pas obligé d'utiliser un properties" ? Une classe ou un ListResourceBundle ?

    Par ailleurs, la solution du bundle racine est également difficile. setParent est une méthode protected, et on ne peut pas rajouter de pairs clé-valeur à un ResourceBundle déjà instancié.

    Question annexe : Y a-t-il une raison pour qu'une classe soit aussi verrouillée ? La sécurité ?

  6. #6
    Membre Expert
    Avatar de professeur shadoko
    Homme Profil pro
    retraité nostalgique Java SE
    Inscrit en
    Juillet 2006
    Messages
    1 257
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 76
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : retraité nostalgique Java SE

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 257
    Par défaut
    Citation Envoyé par hermes1983 Voir le message
    J'ai effectivement un fichier de base (message.properties) et un fichier par locale utilisée (message_fr_FR.properties par exemple, bien que ce ne soit pas le seul). Par contre, je n'ai actuellement qu'un seul fichier pour toutes les chaînes de l'application.
    une première chose à changer: avoir une clef différente par "partie" de l'application (package + clef)

    Par ailleurs, la solution du bundle racine est également difficile. setParent est une méthode protected, et on ne peut pas rajouter de pairs clé-valeur à un ResourceBundle déjà instancié.
    j'ai pas essayé mais un hack qui me viendrait à l'esprit est d'utiliser ResourceBundle.Control pour avoir une partie des dictionnaires
    qui est redirigé dynamiquement vers une ressource en mémoire.
    (mais j'ai pas bien compris tes contraintes: je pensais qu'une méthode statique qui fait le tri et passe au resourceBundle OU à un code ad hoc suffirait)

  7. #7
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par hermes1983 Voir le message
    J'ai effectivement un fichier de base (message.properties) et un fichier par locale utilisée (message_fr_FR.properties par exemple, bien que ce ne soit pas le seul).
    Hum donc il va falloir rusé un peu (je comptais utiliser le fichier de base ).


    Citation Envoyé par hermes1983 Voir le message
    Question annexe : Y a-t-il une raison pour qu'une classe soit aussi verrouillée ? La sécurité ?
    L'objectif est de simplifier la création des bundles. En clair tu n'a qu'à implémenter handleGetObject() qui consiste uniquement à récupérer une valeur selon sa clef. La méthode getString() s'occupant de tout le travail autour (appel du bundle parent si la clef est inexistante, etc...).


    Citation Envoyé par hermes1983 Voir le message
    Par contre, qu'est-ce que tu entends par "tu n'es pas obligé d'utiliser un properties" ? Une classe ou un ListResourceBundle ?
    Oui tu peux utiliser ta propose classe. En fait si tu places une classe étendant ResourceBundle avec le même nom de base que tes fichiers properties, elle sera utilisée de préférence.

    En clair dans ton cas si tu crée une classe nommé "message" dans le même package que tes fichiers *.properties, c'est cette dernière qui sera utilisé comme ResourceBundle.

    Par exemple avec une classe "message" (sans majuscule c'est important) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class message extends ResourceBundle {
    	@Override
    	public Enumeration<String> getKeys() {
    		// On retourne une enumeration vide :
    		return new Enumeration<String>() {
    			public boolean hasMoreElements() {
    				return false;
    			}
    			public String nextElement() {
    				throw new NoSuchElementException();
    			}
    		};
    	}
     
    	@Override
    	protected Object handleGetObject(String key) {
    		// On retourne directement la clef :
    		return key;
    	}
    }
    Tu renvois directement les clef comme valeur. Comme le bundle "message" est utilisé en dernier cela marche très bien ! Lorsque tu fais getBundle("message") avec la langue "fr_FR", tu vas donc charger dans l'ordre :
    • message_fr_FR.properties
    • message_fr.properties
    • message.class (en priorité sur message.properties)



    Le seul problème dans ton cas c'est qu'il va être pris en priorité et que ton fichier "message.properties" ne sera plus utilisé


    La solution serait de le charger par toi même, ce qui donnerait ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public class message extends ResourceBundle {
     
    	private PropertyResourceBundle properties;
     
    	public message() throws IOException {
    		// On charge le fichier *.properties du même nom, s'il existe :
    		InputStream in = getClass().getResourceAsStream(getClass().getSimpleName()+".properties");
    		if (in!=null) {
    			try {
    				this.properties = new PropertyResourceBundle(in);
    			} finally {
    				in.close();
    			}
    		} else {
    			this.properties = null;
    		}
    	}
     
    	@Override
    	public Enumeration<String> getKeys() {
    		if (this.properties==null) {
    			// On retourne seulement les clefs du fichier properties :
    			return this.properties.getKeys();
    		}
    		// Sinon on retourne une enumeration vide :
    		return new Enumeration<String>() {
    			public boolean hasMoreElements() {
    				return false;
    			}
    			public String nextElement() {
    				throw new NoSuchElementException();
    			}
    		};
    	}
     
    	@Override
    	protected Object handleGetObject(String key) {
    		Object value = null;
    		if (this.properties!=null) {
    			value = this.properties.handleGetObject(key);
    		}
    		return value!=null ? value : key;
    	}
     
    }

    Et là tu devrais avoir le comportement souhaité (le bundle parent ne renvoi jamais 'null').

    a++

  8. #8
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par professeur shadoko Voir le message
    j'ai pas essayé mais un hack qui me viendrait à l'esprit est d'utiliser ResourceBundle.Control pour avoir une partie des dictionnaires
    qui est redirigé dynamiquement vers une ressource en mémoire.
    C'est surement possible également avec les Control, mais j'ai peur que ce soit un peu plus complexe à mettre en place...

    a++

  9. #9
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 22
    Par défaut
    Citation Envoyé par professeur shadoko Voir le message
    j'ai pas essayé mais un hack qui me viendrait à l'esprit est d'utiliser ResourceBundle.Control pour avoir une partie des dictionnaires
    qui est redirigé dynamiquement vers une ressource en mémoire.
    (mais j'ai pas bien compris tes contraintes: je pensais qu'une méthode statique qui fait le tri et passe au resourceBundle OU à un code ad hoc suffirait)
    ResourceBundle.Control permet de contrôler l'instanciation des ResourceBundle. Je l'utilise déjà, c'est très pratique pour charger un fichier depuis le disque. Il permet donc juste d'instancier le ResourceBundle que je veux. Simplement, cette classe n'existe pas.
    Tu mentionnes deux solutions, et ça me donne l'occasion d'expliquer pourquoi je ne les aime pas :
    • La méthode statique. Du genre GetString() dans une classe à part, qui fait appel au ResourceBundle désiré (passé en paramètre ou récupéré autrement, ce n'est pas le problème), qui délègue l'appel à getString en l'enrobant dans un bloc try. Cela nécessite de changer tous les appels à getString, partout dans le code (et il y en a).
    • Le code ad hoc. Du genre enrober chaque appel à getString dans un bloc try, ou bien de faire un bloc if en utilisant containKeys ? La lourdeur du code me rebute déjà...


    Je pensais que quelque chose de simple était disponible, mais visiblement, ça n'est pas le cas. Pourtant, quand on jette un œil au code de la classe ResourceBundle, ça paraît évident :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
        public final String getString(String key) {
            return (String) getObject(key);
        }
     
        public final Object getObject(String key) {
            Object obj = handleGetObject(key);
            if (obj == null) {
                if (parent != null) {
                    obj = parent.getObject(key);
                }
                if (obj == null)
                    throw new MissingResourceException("Can't find resource for bundle "
                                                       +this.getClass().getName()
                                                       +", key "+key,
                                                       this.getClass().getName(),
                                                       key);
            }
            return obj;
        }
    • Faire le bloc try directement dans getString
    • Changer le dernier bloc if avec la condition obj == null dans getObject


    Ça me déçoit un peu, parce que GNU Gettext ont pensé à tous ces développeurs qui font parfois des erreurs d'internationalisation sans pour autant planter toute l'application

  10. #10
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 22
    Par défaut
    Bon classique, le temps que je réponde, il y avait pleins de réponse

    adiGuba, j'ai l'impression que ton bout de code m'a effectivement inspiré. J'avais abandonné l'idée de déléguer handleGetObject parce qu'il est qualifié comme protected dans ResourceBundle, mais dans les deux implémentations standard, il est exposé en public (ce qui au passage, est un peu bancal).
    Je pense que je vais donc adopter une solution qui s'appuie sur ton bout de code. Je reviens poster ça ici dès que j'ai quelque chose qui me satisfait, si aucune autre idée n'a pris la place.

    Déjà, merci pour votre aide précieuse

  11. #11
    Membre Expert
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    1 252
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2004
    Messages : 1 252
    Par défaut
    Il y a plus simple et plus propre, non ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class GetTextResourceBundle extends ResourceBundle {
    	private final ResourceBundle delegate;
    	public GetTextResourceBundle (ResourceBundle delegate) {
    		this.delegate = delegate;
    	}
     
    	@Override
    	protected Object handleGetObject(String key) {
    		try {
    			return delegate.getObject(key);
    		} catch (MissingResourceException e) {
    			return key;
    		}
    	}
     
    	public void setParent (ResourceBundle bundle) {this.delegate.setParent(bundle);}
    	public ResourceBundle getParent () {return this.delegate.getParent();}
     
    	public void setLocale (Locale locale) {this.delegate.setLocale(locale);}
    	public Locale getLocale () {return this.delegate.getLocale();}
     
    	// etc. pour les autres méthodes
    }
    Et l'appel à cette classe :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public static getBundle(String baseName) {
    	ResourceBundle delegate = ResourceBundle.getBundle(baseName);
    	return new GetTextResourceBundle(delegate);
    }
    Et voilà : il n'y a qu'à changer les accès à ResourceBundle.getBundle(...) par MaClasse.getBundle(...)

  12. #12
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 22
    Par défaut
    Bon, le bilan de cette journée est plutôt bon finalement. J'ai donc deux solutions à ce problème. Aucune n'est complètement triviale, mais elles correspondent aux contraintes que je m'étais fixées
    J'ai donc deux classes qui héritent de ResourceBundle, et qui renvoie la clé elle-même lorsqu'il n'y a pas d'objet associé. Ces deux classes encapsulent une autre instance de ResourceBundle, qui a été créée par les bibliothèques standard de Java. Dans tous les cas, la chaîne des bundles est respectée, c'est-à-dire que ces deux classes de ResourceBundle ne renvoie la clé que si l'instance est à la racine de la chaîne de bundles.

    La première délègue handleGetObject à handleGetObject. Ce n'est pas simple, parce que j'ai voulu gérer tous les cas. Dans le cas d'un PropertyResourceBundle ou d'un ListResourceBundle, c'est facile, mais dans le cas général, j'ai utilisé la réflexivité pour débloquer la méthode. Je sais, c'est sale

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    public class NoEmptyKeyResourceBundle extends ResourceBundle {
     
        private enum Type {
            PROPERTY,
            LIST,
            CLASS
        }
     
        private ResourceBundle bundle;
     
        private Type type = Type.CLASS;
     
        private Method handleGetObjectMethod;
     
        public NoEmptyKeyResourceBundle(ResourceBundle originalBundle) {
            if (originalBundle == null) {
                throw new NullPointerException("Original bundle can't be null");
            }
            this.bundle = originalBundle;
            if (this.bundle instanceof PropertyResourceBundle) {
                this.type = Type.PROPERTY;
            } else if (this.bundle instanceof ListResourceBundle) {
                this.type = Type.LIST;
            } else {
                try {
                    // Use reflection to have the method
                    this.handleGetObjectMethod =
                            this.bundle.getClass().getDeclaredMethod("handleGetObject",new Class[]{String.class});
                    // Remove the private flag... Not really kind...
                    this.handleGetObjectMethod.setAccessible(true);
                } catch (NoSuchMethodException ex) {
                } catch (SecurityException ex) {
                }
            }
        }
     
        @Override
        protected Object handleGetObject(String key) {
            Object value = null;
            switch (this.type) {
                case PROPERTY:
                    value = ((PropertyResourceBundle)this.bundle).handleGetObject(key);
                    break;
                case LIST:
                    value = ((ListResourceBundle)this.bundle).handleGetObject(key);
                    break;
                case CLASS:
                    try {
                        value = this.handleGetObjectMethod.invoke(this.bundle, key);
                    } catch (IllegalAccessException ex) {
                    } catch (IllegalArgumentException ex) {
                    } catch (InvocationTargetException ex) {
                    }
                    break;
            }
     
            return value == null && this.parent == null ? key : value;
        }
     
        @Override
        public Enumeration<String> getKeys() {
            return this.bundle.getKeys();
        }
    }
    La deuxième, sur l'idée de dingoth est plus simple, mais nécessite le passage dans un bloc try à chaque fois. Bon, entre un bloc try et un switch, la différence est faible, d'autant plus que ça évite la réflexivité dans le cas général, ce qui est toujours bénéfique. Plus je la vois, plus je la trouve convaincante.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class GetTextResourceBundle extends ResourceBundle {
    	private final ResourceBundle delegate;
    	public GetTextResourceBundle (ResourceBundle delegate) {
            if (delegate == null) {
                throw new NullPointerException("Delegate bundle can't be null");
            }
    		this.delegate = delegate;
    	}
     
    	@Override
    	protected Object handleGetObject(String key) {
    		try {
    			return this.delegate.getObject(key);
    		} catch (MissingResourceException e) {
                // Returns the key only if we are the root bundle
    			return this.parent == null ? key : null;
    		}
    	}
     
        @Override
        public Enumeration<String> getKeys() {
            return this.delegate.getKeys();
        }
    }
    Deux remarques importantes :
    • Le NullPointerException dans le constructeur est utile, surtout quand on utilise ces classes dans un ResourceBundle.Control, qui gère tout seul les exceptions
    • C'est le test this.parent == null qui permet de ne pas renvoyer la clé, même si on n'a pas trouvé d'objet associé. Cela permet au ResourceBundle d'aller taper dans le bundle parent. Ceci n'est peut-être pas utile dans la deuxième classe, car la recherche dans le parent est déjà faite quand on arrive dans l'exception (normalement).


    Merci à vous, je pense que le tag Résolu va faire son apparition

  13. #13
    Membre Expert
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    1 252
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2004
    Messages : 1 252
    Par défaut
    Je viens d'y penser, mais plutôt que d'attraper une MRE, tu peux checker si keySet().contains(key);

    Le code deviendrait donc :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    	@Override
    	protected Object handleGetObject(String key) {
    		if (delegate.keySet().contains(key)) {
    			return delegate.getObject(key);
    		} else {
    			return key;
    		}
    	}

  14. #14
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 22
    Par défaut
    À première vue, je dirais que c'est une mauvaise idée si tu veux garder des performances comparable… Même en mettant en cache le Set de clés. Faire un appel à contains puis à getObject, c'est faire une recherche en double, donc doubler le temps d'exécution. Bien que ce soit évidemment plus propre

  15. #15
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    au delà de ça, je pense que keyset ne descend par dans la hierarchie, ca ne marche donc que si le delegate n'as pas lui meme un delegate

  16. #16
    Membre Expert
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    1 252
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2004
    Messages : 1 252
    Par défaut
    Le keySet() renvoie la liste de toutes les clés connues dans ce RB, plus dans tous ses parents. Au delegate de bien gérer cette contrainte. Dans ce cas-ci, si tout est étendu correctement, cela ne posera pas de souci.

  17. #17
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par hermes1983 Voir le message
    À première vue, je dirais que c'est une mauvaise idée si tu veux garder des performances comparable… Même en mettant en cache le Set de clés. Faire un appel à contains puis à getObject, c'est faire une recherche en double, donc doubler le temps d'exécution. Bien que ce soit évidemment plus propre
    En fait cela dépend :
    • Tu perds en performance lorsque tu as des clef valides, car tu auras une double-vérification "inutile".
    • Tu gagnes en performance lorsque tu as des clef invalides, car la génération de l'exception est assez couteuse (surement plus que la double vérification).




    Sinon avec les Control il y a moyen de faire encore plus simple !

    D'abord il faut un ResourceBundle qui renvoi toujours la valeur de la clef. Ceci devrait faire l'affaire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class NoKeyFoundResourceBundle extends ResourceBundle {
     
    	@Override
    	public Enumeration<String> getKeys() {
    		// On retourne une enumeration vide :
    		return Collections.emptyEnumeration();
    	}
     
    	@Override
    	protected Object handleGetObject(String key) {
    		// On retourne directement la clef :
    		return key;
    	}
    }
    Ensuite, dans notre Control, il faut redéfinir la méthode getCandidateLocales(). Cette méthode renvoi normalement les la liste des Locales à charger selon la locale du bundle, dans leur ordre de priorité. Par exemple pour "fr_FR" cela renvoi ceci : [ "fr_FR", "fr", "" ].
    Cela correspond également à l'ordre de recherche dans les bundle. On va donc rajouter à la fin une fausse-locale qui servira donc de bundle parent, et qui sera donc exécuter à la fin...


    Ensuite il suffit de modifier newBundle() afin de gérer cette locale.
    Ce qui donnerait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class NoKeyFoundControl extends ResourceBundle.Control {
     
    	// Une Locale "bidon" qui servira de "marqueur" pour le bundle parent.
    	private final Locale NO_KEY_FOUND = new Locale("no-key-found");
     
    	@Override
    	public List<Locale> getCandidateLocales(String baseName, Locale locale) {
    		// On utilise la méthode parente qui effectuera les traitements par défaut :
    		List<Locale> list = super.getCandidateLocales(baseName, locale);
    		// Et on y rajoute notre "locale" :
    		list.add(NO_KEY_FOUND);
    		return list;
    	}
     
    	@Override
    	public ResourceBundle newBundle(String baseName, Locale locale,
    			String format, ClassLoader loader, boolean reload)
    			throws IllegalAccessException, InstantiationException, IOException {
    		// S'il s'agit de notre "locale" si particulière :
    		if (NO_KEY_FOUND.equals(locale)) {
    			// On crée une instance de notre bundle "vide" :
    			return new NoKeyFoundResourceBundle();
    		}
    		// Sinon on laisse le comportement par défaut :
    		return super.newBundle(baseName, locale, format, loader, reload);
    	}
    }
    Pas de double vérification, pas de remontée d'exception, mais simplement un bundle parent supplémentaire dans la hiérarchie

    a++

  18. #18
    Membre Expert
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    1 252
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2004
    Messages : 1 252
    Par défaut
    Même si ce n'est pas moi qui ai ouvert la discussion, merci pour cette classe intéressante de Java 6.


  19. #19
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 22
    Par défaut
    Citation Envoyé par adiGuba Voir le message
    En fait cela dépend :
    • Tu perds en performance lorsque tu as des clef valides, car tu auras une double-vérification "inutile".
    • Tu gagnes en performance lorsque tu as des clef invalides, car la génération de l'exception est assez couteuse (surement plus que la double vérification).
    Oui, effectivement, mais je pars du principe qu'on a quand même plus de clés valides qu'invalides. Ceci étant, ne pas oublier la règle qui impose de jamais trop optimiser... Par exemple, dans le cas des ressources textuelles, elles sont chargés au démarrage, j'en ai à peine une centaine, l'impact de performances ne sera complètement invisible pour l'utilisateur. À moins d'utiliser une très vieille machine


    Citation Envoyé par adiGuba Voir le message
    Ensuite, dans notre Control, il faut redéfinir la méthode getCandidateLocales(). Cette méthode renvoi normalement les la liste des Locales à charger selon la locale du bundle, dans leur ordre de priorité. Par exemple pour "fr_FR" cela renvoi ceci : [ "fr_FR", "fr", "" ].
    Cela correspond également à l'ordre de recherche dans les bundle. On va donc rajouter à la fin une fausse-locale qui servira donc de bundle parent, et qui sera donc exécuter à la fin...
    Que serions-nous sans toi ? Cette idée me semble de loin la plus élégante, et beaucoup plus propre que ce qu'on a pu trouver avant. Étant donné que j'utilise déjà un Control, c'est d'autant plus simple.
    Cela reste un vieux hack, mais quand même, c'est élégant. Il fallait penser à détourner la hiérarchie des locales à son avantage...

    Merci donc à toi

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. MissingResourceException : ResourceBundle
    Par sylverspoon dans le forum Langage
    Réponses: 11
    Dernier message: 10/12/2009, 15h35
  2. [WSAD] ResourceBundle et MissingResourceException
    Par petitelulu dans le forum Eclipse Java
    Réponses: 1
    Dernier message: 23/09/2004, 11h37
  3. MDI sans MFC, possible ?
    Par delire8 dans le forum MFC
    Réponses: 4
    Dernier message: 17/06/2002, 07h38
  4. [Kylix] Fiches sans bordure
    Par alex dans le forum EDI
    Réponses: 4
    Dernier message: 28/04/2002, 21h19

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo