/*
* 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 static com.google.gwt.i18n.rebind.AnnotationUtil.getClassAnnotation;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.resource.ResourceOracle;
import com.google.gwt.dev.util.StringKey;
import com.google.gwt.dev.util.collect.IdentityHashSet;
import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale;
import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
import com.google.gwt.i18n.rebind.AnnotationsResource.AnnotationsError;
import com.google.gwt.i18n.shared.GwtLocale;
import com.google.gwt.i18n.shared.GwtLocaleFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
/**
* Creates resources.
*/
public abstract class ResourceFactory {
/**
* A key based on JClassType and GwtLocale.
*/
static class ClassLocale extends StringKey {
public ClassLocale(JClassType clazz, GwtLocale locale) {
super(clazz.getQualifiedSourceName() + "/" + locale.toString());
}
}
/**
* Separator between class name and locale in resource files. Should not
* appear in valid localizable class names.
*/
public static final char LOCALE_SEPARATOR = '_';
private static List<ResourceFactory> loaders = new ArrayList<ResourceFactory>();
/**
* Since multiple generators share the ResourceFactory, we tie the
* ResourceFactoryContext cache to the GeneratorContext.
*/
private static final WeakHashMap<GeneratorContext, ResourceFactoryContext>
resourceFactoryCtxHolder = new WeakHashMap<GeneratorContext, ResourceFactoryContext>();
static {
loaders.add(new LocalizedPropertiesResource.Factory());
}
public static synchronized ResourceList getBundle(TreeLogger logger,
JClassType topClass, GwtLocale bundleLocale, boolean isConstants,
GeneratorContext genCtx) {
List<GwtLocale> locales = bundleLocale.getCompleteSearchList();
List<JClassType> classes = new ArrayList<JClassType>();
Set<JClassType> seenClasses = new IdentityHashSet<JClassType>();
Map<ClassLocale, AnnotationsResource> annotations = new HashMap<ClassLocale, AnnotationsResource>();
GwtLocaleFactory factory = LocaleUtils.getLocaleFactory();
GwtLocale defaultLocale = factory.getDefault();
walkInheritanceTree(logger, topClass, factory, defaultLocale, classes,
annotations, seenClasses, isConstants);
// TODO(jat): handle explicit subinterface with other locales -- ie:
// public interface Foo_es_MX extends Foo { ... }
ResourceList allResources = new ResourceList();
ResourceFactoryContext localizableCtx = getResourceFactoryContext(genCtx);
for (GwtLocale locale : locales) {
for (JClassType clazz : classes) {
ClassLocale key = new ClassLocale(clazz, locale);
ResourceList resources;
resources = localizableCtx.getResourceList(key);
if (resources == null) {
resources = new ResourceList();
addFileResources(logger, clazz, locale, genCtx.getResourcesOracle(), resources);
AnnotationsResource annotationsResource = annotations.get(key);
if (annotationsResource != null) {
resources.add(annotationsResource);
}
localizableCtx.putResourceList(key, resources);
}
allResources.addAll(resources);
}
}
String className = topClass.getQualifiedSourceName();
TreeLogger branch = logger.branch(TreeLogger.SPAM,
"Resource search order for " + className + ", locale " + bundleLocale);
if (logger.isLoggable(TreeLogger.SPAM)) {
for (AbstractResource resource : allResources) {
branch.log(TreeLogger.SPAM, resource.toString());
}
}
return allResources;
}
public static String getResourceName(JClassType targetClass) {
String name = targetClass.getName();
if (targetClass.isMemberType()) {
name = name.replace('.', '$');
}
return name;
}
private static void addFileResources(TreeLogger logger, JClassType clazz, GwtLocale locale,
ResourceOracle resourceOracle, ResourceList resources) {
// TODO: handle classes in the default package?
String targetPath = clazz.getPackage().getName() + '.'
+ getResourceName(clazz);
String localizedPath = targetPath;
if (!locale.isDefault()) {
localizedPath = targetPath + LOCALE_SEPARATOR + locale.getAsString();
}
// 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;
Resource resource = resourceOracle.getResource(path);
if (resource == 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;
resource = resourceOracle.getResource(path);
}
if (resource != null) {
InputStream resourceStream = null;
try {
resourceStream = resource.openContents();
} catch (IOException ex) {
logger.log(TreeLogger.ERROR, "Error opening resource: " + resource.getLocation());
throw new RuntimeException(ex);
}
AbstractResource found = element.load(resourceStream, locale);
found.setPath(path);
resources.add(found);
}
}
}
private static synchronized ResourceFactoryContext getResourceFactoryContext(
GeneratorContext context) {
if (context instanceof CachedGeneratorContext) {
context = ((CachedGeneratorContext) context).getWrappedGeneratorContext();
}
ResourceFactoryContext resourceFactoryCtx = resourceFactoryCtxHolder.get(context);
if (resourceFactoryCtx == null) {
resourceFactoryCtx = new ResourceFactoryContext();
resourceFactoryCtxHolder.put(context, resourceFactoryCtx);
}
return resourceFactoryCtx;
}
private static void walkInheritanceTree(TreeLogger logger, JClassType clazz,
GwtLocaleFactory factory, GwtLocale defaultLocale,
List<JClassType> classes,
Map<ClassLocale, AnnotationsResource> annotations,
Set<JClassType> seenClasses, boolean isConstants) {
if (seenClasses.contains(clazz)) {
return;
}
seenClasses.add(clazz);
classes.add(clazz);
AnnotationsResource resource;
try {
resource = new AnnotationsResource(logger, clazz, defaultLocale,
isConstants);
if (resource.notEmpty()) {
resource.setPath(clazz.getQualifiedSourceName());
ClassLocale key = new ClassLocale(clazz, defaultLocale);
annotations.put(key, resource);
String defLocaleValue = null;
// If the class has an embedded locale in it, use that for the default
String className = clazz.getSimpleSourceName();
int underscore = className.indexOf('_');
if (underscore >= 0) {
defLocaleValue = className.substring(underscore + 1);
}
// If there is an annotation declaring the default locale, use that
DefaultLocale defLocaleAnnot = getClassAnnotation(clazz,
DefaultLocale.class);
if (defLocaleAnnot != null) {
defLocaleValue = defLocaleAnnot.value();
}
GwtLocale defLocale = LocaleUtils.getLocaleFactory().fromString(
defLocaleValue);
if (!defLocale.isDefault()) {
key = new ClassLocale(clazz, defLocale);
annotations.put(key, resource);
}
}
} catch (AnnotationsError e) {
logger.log(TreeLogger.ERROR, e.getMessage(), e);
}
if (clazz.getSuperclass() != null) {
walkInheritanceTree(logger, clazz.getSuperclass(), factory,
defaultLocale, classes, annotations, seenClasses, isConstants);
}
for (JClassType intf : clazz.getImplementedInterfaces()) {
walkInheritanceTree(logger, intf, factory, defaultLocale, classes,
annotations, seenClasses, isConstants);
}
}
abstract String getExt();
abstract AbstractResource load(InputStream m, GwtLocale locale);
}