001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.i18n.client;
017:
018: import com.google.gwt.core.client.JavaScriptObject;
019:
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Map;
025: import java.util.MissingResourceException;
026: import java.util.Set;
027:
028: /**
029: * Provides dynamic string lookup of key/value string pairs defined in a
030: * module's host HTML page. Each unique instance of <code>Dictionary</code> is
031: * bound to a named JavaScript object that resides in the global namespace of
032: * the host page's window object. The bound JavaScript object is used directly
033: * as an associative array.
034: *
035: * <p>
036: * For example, suppose you define the following JavaScript object in your host
037: * page:
038: *
039: * {@gwt.include com/google/gwt/examples/i18n/ThemeDictionaryExample.js}
040: *
041: * You can then use a <code>Dictionary</code> to access the key/value pairs
042: * above:
043: *
044: * {@example com.google.gwt.examples.i18n.ThemeDictionaryExample#useThemeDictionary()}
045: * </p>
046: *
047: * <p>
048: * Unlike the family of interfaces that extend
049: * {@link com.google.gwt.i18n.client.Localizable} which support static
050: * internationalization, the <code>Dictionary</code> class is fully dynamic.
051: * As a result, a variety of error conditions (particularly those involving key
052: * mismatches) cannot be caught until runtime. Similarly, the GWT compiler is
053: * unable discard unused dictionary values since the structure cannot be
054: * statically analyzed.
055: * </p>
056: *
057: * <h3>A Caveat Regarding Locale</h3>
058: * The module's host page completely determines the mappings defined for each
059: * dictionary without regard to the <code>locale</code> client property. Thus,
060: * <code>Dictionary</code> is the most flexible of the internationalization
061: * types and may provide the simplest form of integration with existing
062: * localization systems which were not specifically designed to use GWT's
063: * <code>locale</code> client property.
064: *
065: * <p>
066: * See {@link com.google.gwt.i18n.client.Localizable} for background on the
067: * <code>locale</code> client property.
068: * </p>
069: *
070: * <h3>Required Module</h3>
071: * Modules that use this interface should inherit
072: * <code>com.google.gwt.i18n.I18N</code>.
073: *
074: * {@gwt.include com/google/gwt/examples/i18n/InheritsExample.gwt.xml}
075: */
076: public final class Dictionary {
077:
078: private static Map<String, Dictionary> cache = new HashMap<String, Dictionary>();
079: private static final int MAX_KEYS_TO_SHOW = 20;
080:
081: /**
082: * Returns the <code>Dictionary</code> object associated with the given
083: * name.
084: *
085: * @param name
086: * @return specified dictionary
087: * @throws MissingResourceException
088: */
089: public static Dictionary getDictionary(String name) {
090: Dictionary target = cache.get(name);
091: if (target == null) {
092: target = new Dictionary(name);
093: cache.put(name, target);
094: }
095: return target;
096: }
097:
098: static void resourceErrorBadType(String name) {
099: throw new MissingResourceException(
100: "'"
101: + name
102: + "' is not a JavaScript object and cannot be used as a Dictionary",
103: null, name);
104: }
105:
106: private JavaScriptObject dict;
107:
108: private String label;
109:
110: /**
111: * Constructor for <code>Dictionary</code>.
112: *
113: * @param name name of linked JavaScript Object
114: */
115: private Dictionary(String name) {
116: if (name == null || "".equals(name)) {
117: throw new IllegalArgumentException(
118: "Cannot create a Dictionary with a null or empty name");
119: }
120: this .label = "Dictionary " + name;
121: attach(name);
122: if (dict == null) {
123: throw new MissingResourceException(
124: "Cannot find JavaScript object with the name '"
125: + name + "'", name, null);
126: }
127: }
128:
129: /**
130: * Get the value associated with the given Dictionary key.
131: *
132: * We have to call Object.hasOwnProperty to verify that the value is
133: * defined on this object, rather than a superclass, since normal Object
134: * properties are also visible on this object.
135: *
136: * @param key to lookup
137: * @return the value
138: * @throws MissingResourceException if the value is not found
139: */
140: public native String get(String key) /*-{
141: var value = this.@com.google.gwt.i18n.client.Dictionary::dict[key];
142: if (value == null || !Object.prototype.hasOwnProperty.call(
143: this.@com.google.gwt.i18n.client.Dictionary::dict, key))
144: {
145: this.@com.google.gwt.i18n.client.Dictionary::resourceError(Ljava/lang/String;)(key);
146: }
147: return String(value);
148: }-*/;
149:
150: /**
151: * The set of keys associated with this dictionary.
152: *
153: * @return the Dictionary set
154: */
155: public Set<String> keySet() {
156: HashSet<String> s = new HashSet<String>();
157: addKeys(s);
158: return s;
159: }
160:
161: @Override
162: public String toString() {
163: return label;
164: }
165:
166: /**
167: * Collection of values associated with this dictionary.
168: *
169: * @return the values
170: */
171: public Collection<String> values() {
172: ArrayList<String> s = new ArrayList<String>();
173: addValues(s);
174: return s;
175: }
176:
177: void resourceError(String key) {
178: Collection<String> s = this .keySet();
179: String error = "Cannot find '" + key + "' in " + this ;
180: if (s.size() < MAX_KEYS_TO_SHOW) {
181: error += "\n keys found: " + s;
182: }
183: throw new MissingResourceException(error, this .toString(), key);
184: }
185:
186: private native void addKeys(HashSet<String> s) /*-{
187: for (x in this.@com.google.gwt.i18n.client.Dictionary::dict) {
188: s.@java.util.HashSet::add(Ljava/lang/Object;)(x);
189: }
190: }-*/;
191:
192: private native void addValues(ArrayList<String> s) /*-{
193: for (x in this.@com.google.gwt.i18n.client.Dictionary::dict) {
194: var value = this.@com.google.gwt.i18n.client.Dictionary::get(Ljava/lang/String;)(x);
195: s.@java.util.ArrayList::add(Ljava/lang/Object;)(value);
196: }
197: }-*/;
198:
199: private native void attach(String name)/*-{
200: try {
201: if (typeof($wnd[name]) != "object") {
202: @com.google.gwt.i18n.client.Dictionary::resourceErrorBadType(Ljava/lang/String;)(name);
203: }
204: this.@com.google.gwt.i18n.client.Dictionary::dict = $wnd[name];
205: } catch(e) {
206: @com.google.gwt.i18n.client.Dictionary::resourceErrorBadType(Ljava/lang/String;)(name);
207: }
208: }-*/;
209: }
|