blob: f404f24d06f56ddd4fe7d1a2ce19983c8985e894 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.i18n.rebind;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
import com.google.gwt.i18n.rebind.AnnotationsResource.AnnotationsError;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Creates resources.
*/
public abstract class ResourceFactory {
static class SimplePathTree extends AbstractPathTree {
String path;
SimplePathTree(String path) {
this.path = path;
}
@Override
public AbstractPathTree getChild(int i) {
throw new UnsupportedOperationException(
"Simple paths have no children, therefore cannot get child: " + i);
}
@Override
public String getPath() {
return path;
}
@Override
public int numChildren() {
return 0;
}
}
private abstract static class AbstractPathTree {
abstract AbstractPathTree getChild(int i);
JClassType getJClassType(JClassType clazz) {
return clazz;
}
abstract String getPath();
abstract int numChildren();
}
private static class ClassPathTree extends AbstractPathTree {
Class<?> javaInterface;
ClassPathTree(Class<?> javaInterface) {
this.javaInterface = javaInterface;
}
@Override
AbstractPathTree getChild(int i) {
// we expect to do this at most once, so no caching is used.
return new ClassPathTree(javaInterface.getInterfaces()[i]);
}
@Override
String getPath() {
return javaInterface.getName();
}
@Override
int numChildren() {
return javaInterface.getInterfaces().length;
}
}
private static class JClassTypePathTree extends AbstractPathTree {
JClassType javaInterface;
JClassTypePathTree(JClassType javaInterface) {
this.javaInterface = javaInterface;
}
@Override
AbstractPathTree getChild(int i) {
// we expect to do this at most once, so no caching is used.
return new JClassTypePathTree(javaInterface.getImplementedInterfaces()[i]);
}
@Override
JClassType getJClassType(JClassType clazz) {
return javaInterface;
}
/**
* Path is equivalent to javaInterface.getQualifiedName() except for inner
* classes.
*
* @see com.google.gwt.i18n.rebind.ResourceFactory.AbstractPathTree#getPath()
*/
@Override
String getPath() {
String name = getResourceName(javaInterface);
String packageName = javaInterface.getPackage().getName();
return packageName + "." + name;
}
@Override
int numChildren() {
return javaInterface.getImplementedInterfaces().length;
}
}
/**
* Represents default locale.
*/
public static final String DEFAULT_TOKEN = "default";
public static final char LOCALE_SEPARATOR = '_';
private static Map<String, ResourceList> cache = new HashMap<String, ResourceList>();
private static List<ResourceFactory> loaders = new ArrayList<ResourceFactory>();
static {
loaders.add(new LocalizedPropertiesResource.Factory());
}
/**
* Clears the resource cache.
*/
public static void clearCache() {
cache.clear();
}
public static ResourceList getBundle(Class<?> clazz, String locale, boolean isConstants) {
return getBundle(TreeLogger.NULL, clazz, locale, isConstants);
}
public static ResourceList getBundle(String path, String locale, boolean isConstants) {
return getBundle(TreeLogger.NULL, path, locale, isConstants);
}
/**
* Gets the resource associated with the given interface.
*
* @param javaInterface interface
* @param locale locale name
* @return the resource
*/
public static ResourceList getBundle(TreeLogger logger, Class<?> javaInterface,
String locale, boolean isConstants) {
if (javaInterface.isInterface() == false) {
throw new IllegalArgumentException(javaInterface
+ " should be an interface.");
}
ClassPathTree path = new ClassPathTree(javaInterface);
return getBundleAux(logger, path, null, locale, true, isConstants);
}
/**
* Gets the resource associated with the given interface.
*
* @param javaInterface interface
* @param locale locale name
* @return the resource
*/
public static ResourceList getBundle(TreeLogger logger, JClassType javaInterface,
String locale, boolean isConstants) {
return getBundleAux(logger, new JClassTypePathTree(javaInterface), javaInterface, locale,
true, isConstants);
}
/**
* Gets the resource associated with the given path.
*
* @param path the path
* @param locale locale name
* @return the resource
*/
public static ResourceList getBundle(TreeLogger logger, String path, String locale,
boolean isConstants) {
return getBundleAux(logger, new SimplePathTree(path), null, locale, true, isConstants);
}
/**
* Given a locale name, derives the parent's locale name. For example, if
* the locale name is "en_US", the parent locale name would be "en". If the
* locale name is that of a top level locale (i.e. no '_' characters, such
* as "fr"), then the the parent locale name is that of the default locale.
* If the locale name is null, the empty string, or is already that of the
* default locale, then null is returned.
*
* @param localeName the locale name
* @return the parent's locale name
*/
public static String getParentLocaleName(String localeName) {
if (localeName == null ||
localeName.length() == 0 ||
localeName.equals(DEFAULT_TOKEN)) {
return null;
}
int pos = localeName.lastIndexOf(LOCALE_SEPARATOR);
if (pos != -1) {
return localeName.substring(0, pos);
}
return DEFAULT_TOKEN;
}
public static String getResourceName(JClassType targetClass) {
String name = targetClass.getName();
if (targetClass.isMemberType()) {
name = name.replace('.', '$');
}
return name;
}
private static void addAlternativeParents(TreeLogger logger,
ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale,
boolean useAlternativeParents, boolean isConstants,
ResourceList resources, Set<String> seenPaths) {
if (tree != null) {
for (int i = 0; i < tree.numChildren(); i++) {
ResourceFactory.AbstractPathTree child = tree.getChild(i);
addResources(logger, child, child.getJClassType(clazz),
locale, useAlternativeParents, isConstants, resources, seenPaths);
}
}
}
private static void addPrimaryParent(TreeLogger logger,
ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale,
boolean isConstants, ResourceList resources, Set<String> seenPaths) {
// If we are not in the default case, calculate parent
if (!DEFAULT_TOKEN.equals(locale)) {
addResources(logger, tree, clazz, getParentLocaleName(locale),
false, isConstants, resources, seenPaths);
}
}
private static void addResources(TreeLogger logger,
ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale,
boolean useAlternateParents, boolean isConstants,
ResourceList resources, Set<String> seenPaths) {
String targetPath = tree.getPath();
String localizedPath = targetPath;
if (!DEFAULT_TOKEN.equals(locale)) {
localizedPath = targetPath + LOCALE_SEPARATOR + locale;
}
if (seenPaths.contains(localizedPath)) {
return;
}
seenPaths.add(localizedPath);
ClassLoader loader = AbstractResource.class.getClassLoader();
Map<String, JClassType> matchingClasses = null;
if (clazz != null) {
try {
matchingClasses = LocalizableLinkageCreator.findDerivedClasses(logger,
clazz);
/*
* In this case, we specifically want to be able to look at the interface
* instead of just implementations.
*/
matchingClasses.put(ResourceFactory.DEFAULT_TOKEN, clazz);
} catch (UnableToCompleteException e) {
// ignore error, fall through
}
}
if (matchingClasses == null) {
// empty map
matchingClasses = new HashMap<String, JClassType>();
}
if (locale == null || locale.length() == 0) {
// This should never happen, since the only legitimate user of this
// method traces back to AbstractLocalizableImplCreator. The locale
// that is passed in from AbstractLocalizableImplCreator is produced
// by the I18N property provider, which guarantees that the locale
// will not be of zero length or null. However, we add this check
// in here in the event that a future user of ResourceFactory does
// not obey this constraint.
locale = DEFAULT_TOKEN;
}
// Check for file-based resources.
String partialPath = localizedPath.replace('.', '/');
for (int i = 0; i < loaders.size(); i++) {
ResourceFactory element = loaders.get(i);
String ext = "." + element.getExt();
String path = partialPath + ext;
InputStream m = loader.getResourceAsStream(path);
if (m == null && partialPath.contains("$")) {
// Also look for A_B for inner classes, as $ in path names
// can cause issues for some build tools.
path = partialPath.replace('$', '_') + ext;
m = loader.getResourceAsStream(path);
}
if (m != null) {
AbstractResource found = element.load(m);
found.setPath(path);
resources.add(found);
}
}
// Check for annotations
JClassType currentClass = matchingClasses.get(locale);
if (currentClass != null) {
AnnotationsResource resource;
try {
resource = new AnnotationsResource(logger, currentClass, locale,
isConstants);
if (resource.notEmpty()) {
resource.setPath(currentClass.getQualifiedSourceName());
resources.add(resource);
}
} catch (AnnotationsError e) {
logger.log(TreeLogger.ERROR, e.getMessage(), e);
}
}
// Add our parent, if any
addPrimaryParent(logger, tree, clazz, locale, isConstants, resources,
seenPaths);
// Add our alternate parents
if (useAlternateParents) {
addAlternativeParents(logger, tree, clazz, locale, useAlternateParents,
isConstants, resources, seenPaths);
}
}
private static ResourceList getBundleAux(TreeLogger logger,
ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale,
boolean required, boolean isConstants) {
String cacheKey = tree.getPath() + "_" + locale;
if (cache.containsKey(cacheKey)) {
return cache.get(cacheKey);
}
Set<String> seenPaths = new HashSet<String>();
final ResourceList resources = new ResourceList();
addResources(logger, tree, clazz, locale, true, isConstants, resources,
seenPaths);
String className = tree.getPath();
if (clazz != null) {
className = clazz.getQualifiedSourceName();
}
TreeLogger branch = logger.branch(TreeLogger.SPAM, "Resource search order for "
+ className + ", locale " + locale);
for (AbstractResource resource : resources) {
branch.log(TreeLogger.SPAM, resource.toString());
}
cache.put(cacheKey, resources);
return resources;
}
abstract String getExt();
abstract AbstractResource load(InputStream m);
}