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.rebind.util;
017:
018: import com.google.gwt.core.ext.typeinfo.JClassType;
019:
020: import java.io.InputStream;
021: import java.util.ArrayList;
022: import java.util.HashMap;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.MissingResourceException;
026: import java.util.Set;
027:
028: /**
029: * Creates resources.
030: */
031: public abstract class ResourceFactory {
032: static class SimplePathTree extends AbstractPathTree {
033: String path;
034:
035: SimplePathTree(String path) {
036: this .path = path;
037: }
038:
039: @Override
040: public AbstractPathTree getChild(int i) {
041: throw new UnsupportedOperationException(
042: "Simple paths have no children, therefore cannot get child: "
043: + i);
044: }
045:
046: @Override
047: public String getPath() {
048: return path;
049: }
050:
051: @Override
052: public int numChildren() {
053: return 0;
054: }
055: }
056:
057: private abstract static class AbstractPathTree {
058: abstract AbstractPathTree getChild(int i);
059:
060: abstract String getPath();
061:
062: abstract int numChildren();
063: }
064:
065: private static class ClassPathTree extends AbstractPathTree {
066: Class<?> javaInterface;
067:
068: ClassPathTree(Class<?> javaInterface) {
069: this .javaInterface = javaInterface;
070: }
071:
072: @Override
073: AbstractPathTree getChild(int i) {
074: // we expect to do this at most once, so no caching is used.
075: return new ClassPathTree(javaInterface.getInterfaces()[i]);
076: }
077:
078: @Override
079: String getPath() {
080: return javaInterface.getName();
081: }
082:
083: @Override
084: int numChildren() {
085: return javaInterface.getInterfaces().length;
086: }
087: }
088:
089: private static class JClassTypePathTree extends AbstractPathTree {
090: JClassType javaInterface;
091:
092: JClassTypePathTree(JClassType javaInterface) {
093: this .javaInterface = javaInterface;
094: }
095:
096: @Override
097: AbstractPathTree getChild(int i) {
098: // we expect to do this at most once, so no caching is used.
099: return new JClassTypePathTree(javaInterface
100: .getImplementedInterfaces()[i]);
101: }
102:
103: /**
104: * Path is equivalent to javaInterface.getQualifiedName() except for inner
105: * classes.
106: *
107: * @see com.google.gwt.i18n.rebind.util.ResourceFactory.AbstractPathTree#getPath()
108: */
109: @Override
110: String getPath() {
111: String name = getResourceName(javaInterface);
112: String packageName = javaInterface.getPackage().getName();
113: return packageName + "." + name;
114: }
115:
116: @Override
117: int numChildren() {
118: return javaInterface.getImplementedInterfaces().length;
119: }
120: }
121:
122: /**
123: * Represents default locale.
124: */
125: public static final String DEFAULT_TOKEN = "default";
126: public static final char LOCALE_SEPARATOR = '_';
127:
128: public static final AbstractResource NOT_FOUND = new AbstractResource() {
129:
130: @Override
131: void addToKeySet(Set<String> s) {
132: throw new IllegalStateException("Not found resource");
133: }
134:
135: @Override
136: Object handleGetObject(String key) {
137: throw new IllegalStateException("Not found resource");
138: }
139: };
140:
141: private static Map<String, AbstractResource> cache = new HashMap<String, AbstractResource>();
142:
143: private static List<LocalizedPropertiesResource.Factory> loaders = new ArrayList<LocalizedPropertiesResource.Factory>();
144: static {
145: loaders.add(new LocalizedPropertiesResource.Factory());
146: }
147:
148: /**
149: * Clears the resource cache.
150: */
151: public static void clearCache() {
152: cache.clear();
153: }
154:
155: /**
156: * Gets the resource associated with the given interface.
157: *
158: * @param javaInterface interface
159: * @param locale locale name
160: * @return the resource
161: */
162: public static AbstractResource getBundle(Class<?> javaInterface,
163: String locale) {
164: if (javaInterface.isInterface() == false) {
165: throw new IllegalArgumentException(javaInterface
166: + " should be an interface.");
167: }
168: ClassPathTree path = new ClassPathTree(javaInterface);
169: return getBundleAux(path, locale, true);
170: }
171:
172: /**
173: * Gets the resource associated with the given interface.
174: *
175: * @param javaInterface interface
176: * @param locale locale name
177: * @return the resource
178: */
179: public static AbstractResource getBundle(JClassType javaInterface,
180: String locale) {
181: return getBundleAux(new JClassTypePathTree(javaInterface),
182: locale, true);
183: }
184:
185: /**
186: * Gets the resource associated with the given path.
187: *
188: * @param path the path
189: * @param locale locale name
190: * @return the resource
191: */
192: public static AbstractResource getBundle(String path, String locale) {
193: return getBundleAux(new SimplePathTree(path), locale, true);
194: }
195:
196: /**
197: * Given a locale name, derives the parent's locale name. For example, if
198: * the locale name is "en_US", the parent locale name would be "en". If the
199: * locale name is that of a top level locale (i.e. no '_' characters, such
200: * as "fr"), then the the parent locale name is that of the default locale.
201: * If the locale name is null, the empty string, or is already that of the
202: * default locale, then null is returned.
203: *
204: * @param localeName the locale name
205: * @return the parent's locale name
206: */
207: public static String getParentLocaleName(String localeName) {
208: if (localeName == null || localeName.length() == 0
209: || localeName.equals(DEFAULT_TOKEN)) {
210: return null;
211: }
212: int pos = localeName.lastIndexOf(LOCALE_SEPARATOR);
213: if (pos != -1) {
214: return localeName.substring(0, pos);
215: }
216: return DEFAULT_TOKEN;
217: }
218:
219: public static String getResourceName(JClassType targetClass) {
220: String name = targetClass.getName();
221: if (targetClass.isMemberType()) {
222: name = name.replace('.', '$');
223: }
224: return name;
225: }
226:
227: private static List<AbstractResource> findAlternativeParents(
228: ResourceFactory.AbstractPathTree tree, String locale) {
229: List<AbstractResource> altParents = null;
230: if (tree != null) {
231: altParents = new ArrayList<AbstractResource>();
232: for (int i = 0; i < tree.numChildren(); i++) {
233: ResourceFactory.AbstractPathTree child = tree
234: .getChild(i);
235: AbstractResource altParent = getBundleAux(child,
236: locale, false);
237: if (altParent != null) {
238: altParents.add(altParent);
239: }
240: }
241: }
242: return altParents;
243: }
244:
245: private static AbstractResource findPrimaryParent(
246: ResourceFactory.AbstractPathTree tree, String locale) {
247:
248: // If we are not in the default case, calculate parent
249: if (!DEFAULT_TOKEN.equals(locale)) {
250: return getBundleAux(tree, getParentLocaleName(locale),
251: false);
252: }
253: return null;
254: }
255:
256: private static AbstractResource getBundleAux(
257: ResourceFactory.AbstractPathTree tree, String locale,
258: boolean required) {
259: String targetPath = tree.getPath();
260: ClassLoader loader = AbstractResource.class.getClassLoader();
261:
262: if (locale == null || locale.length() == 0) {
263: // This should never happen, since the only legitimate user of this
264: // method traces back to AbstractLocalizableImplCreator. The locale
265: // that is passed in from AbstractLocalizableImplCreator is produced
266: // by the I18N property provider, which guarantees that the locale
267: // will not be of zero length or null. However, we add this check
268: // in here in the event that a future user of ResourceFactory does
269: // not obey this constraint.
270: locale = DEFAULT_TOKEN;
271: }
272:
273: // Calculate baseName
274: String localizedPath = targetPath;
275: if (!DEFAULT_TOKEN.equals(locale)) {
276: localizedPath = targetPath + LOCALE_SEPARATOR + locale;
277: }
278: AbstractResource result = cache.get(localizedPath);
279: if (result != null) {
280: if (result == NOT_FOUND) {
281: return null;
282: } else {
283: return result;
284: }
285: }
286: String partualPath = localizedPath.replace('.', '/');
287: AbstractResource parent = findPrimaryParent(tree, locale);
288: List<AbstractResource> altParents = findAlternativeParents(
289: tree, locale);
290:
291: AbstractResource found = null;
292: for (int i = 0; i < loaders.size(); i++) {
293: ResourceFactory element = loaders.get(i);
294: String path = partualPath + "." + element.getExt();
295: InputStream m = loader.getResourceAsStream(path);
296: if (m != null) {
297: found = element.load(m);
298: found.setPath(partualPath);
299: found.setPrimaryParent(parent);
300: found.setLocaleName(locale);
301: for (int j = 0; j < altParents.size(); j++) {
302: AbstractResource altParent = altParents.get(j);
303: found.addAlternativeParent(altParent);
304: }
305: found.checkKeys();
306: break;
307: }
308: }
309: if (found == null) {
310: if (parent != null) {
311: found = parent;
312: } else {
313: found = NOT_FOUND;
314: }
315: }
316:
317: cache.put(localizedPath, found);
318:
319: if (found == NOT_FOUND) {
320: if (required) {
321: throw new MissingResourceException(
322: "Could not find any resource associated with "
323: + tree.getPath(), null, null);
324: } else {
325: return null;
326: }
327: }
328:
329: // At this point, found cannot be equal to null or NOT_FOUND
330: return found;
331: }
332:
333: abstract String getExt();
334:
335: abstract AbstractResource load(InputStream m);
336: }
|