Skip to content

Commit

Permalink
C10NTranslations refactoring (refs #12)
Browse files Browse the repository at this point in the history
* You can now get the evaluated translation value for each locale (`getValue()`)
* All declared annotations with their values are also accessible (`getAnnotations()`)
* Translation evaluation is powered by the `DummyInstanceProvider` responsible for providing parameter instances for parameterized interface methods

* Test suite rewrite
  • Loading branch information
rodionmoiseev committed Dec 25, 2012
1 parent 7a9af7c commit 46df654
Show file tree
Hide file tree
Showing 18 changed files with 775 additions and 97 deletions.
13 changes: 11 additions & 2 deletions tools/src/main/java/c10n/tools/inspector/C10NTranslations.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,23 @@
* @author rodion
*/
public final class C10NTranslations {
private String value = null;
private final Set<ResourceBundle> bundles = Sets.newHashSet();
private final Set<Class<? extends Annotation>> annotations = Sets.newHashSet();
private final Set<Annotation> annotations = Sets.newHashSet();

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

public Set<ResourceBundle> getBundles() {
return bundles;
}

public Set<Class<? extends Annotation>> getAnnotations() {
public Set<Annotation> getAnnotations() {
return annotations;
}

Expand Down
83 changes: 67 additions & 16 deletions tools/src/main/java/c10n/tools/inspector/DefaultC10NInspector.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

package c10n.tools.inspector;

import c10n.C10N;
import c10n.C10NMessages;
import c10n.C10NMsgFactory;
import c10n.ConfiguredC10NModule;
import c10n.share.utils.C10NBundleKey;
import c10n.share.utils.ReflectionUtils;
Expand All @@ -28,6 +30,7 @@
import com.google.common.collect.Maps;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

Expand All @@ -37,62 +40,102 @@
class DefaultC10NInspector implements C10NInspector {
private final C10NInterfaceSearch c10NInterfaceSearch;
private final ConfiguredC10NModule configuredC10NModule;
private final DummyInstanceProvider dummyInstanceProvider;
private final Set<Locale> localesToCheck;

DefaultC10NInspector(C10NInterfaceSearch c10NInterfaceSearch,
ConfiguredC10NModule configuredC10NModule,
DummyInstanceProvider dummyInstanceProvider,
Set<Locale> localesToCheck) {
this.c10NInterfaceSearch = c10NInterfaceSearch;
this.configuredC10NModule = configuredC10NModule;
this.dummyInstanceProvider = dummyInstanceProvider;
this.localesToCheck = localesToCheck;
}

@Override
public List<C10NUnit> inspect(String... packagePrefixes) {
Map<C10NBundleKey, C10NUnit> c10NUnitsByKey = Maps.newHashMap();
List<C10NUnit> res = Lists.newArrayList();

C10NMsgFactory c10NMsgFactory = C10N.createMsgFactory(configuredC10NModule);

Set<Class<?>> c10nInterfaces = c10NInterfaceSearch.find(C10NMessages.class, packagePrefixes);
for (Class<?> c10nInterface : c10nInterfaces) {
Set<Map.Entry<Class<? extends Annotation>, Set<Locale>>> annotationEntries =
configuredC10NModule.getAnnotationBindings(c10nInterface).entrySet();

List<C10NBundleKey> keysForInterface = Lists.newArrayList();

List<C10NUnit> unitsForInterface = Lists.newArrayList();

for (Method method : c10nInterface.getDeclaredMethods()) {
String keyAnnotationValue = ReflectionUtils.getKeyAnnotationValue(method);
String bundleKey = ReflectionUtils.getC10NKey(configuredC10NModule.getKeyPrefix(), method);
boolean isCustom = ReflectionUtils.getKeyAnnotationBasedKey(method) != null;
C10NBundleKey key = new C10NBundleKey(isCustom, bundleKey, keyAnnotationValue);

C10NUnit unit = new C10NUnit(c10nInterface, method, key, localesToCheck);
for (Map.Entry<Class<? extends Annotation>, Set<Locale>> entry : annotationEntries) {
Class<? extends Annotation> annotation = entry.getKey();
Class<? extends Annotation> annotationClass = entry.getKey();
for (Locale locale : entry.getValue()) {
C10NUnit unit = addC10NUnit(c10NUnitsByKey, c10nInterface, method, key);
C10NTranslations trs = addTranslations(unit, locale);
if (method.getAnnotation(annotation) != null) {
trs.getAnnotations().add(annotation);
if (localesToCheck.contains(locale)) {
String translatedValue = extractTranslatedValue(c10NMsgFactory,
c10nInterface,
method,
locale);
C10NTranslations trs = addTranslations(unit, locale, translatedValue);
Annotation annotation = method.getAnnotation(annotationClass);
if (null != annotation) {
trs.getAnnotations().add(annotation);
}
}
}
}

keysForInterface.add(key);
unitsForInterface.add(unit);
}

for (Locale locale : localesToCheck) {
List<ResourceBundle> bundles = configuredC10NModule.getBundleBindings(c10nInterface, locale);
for (C10NBundleKey key : keysForInterface) {
C10NUnit unit = c10NUnitsByKey.get(key);
C10NTranslations trs = addTranslations(unit, locale);
for (C10NUnit unit : unitsForInterface) {
String translatedValue = extractTranslatedValue(c10NMsgFactory,
c10nInterface,
unit.getDeclaringMethod(),
locale);
C10NTranslations trs = addTranslations(unit, locale, translatedValue);
for (ResourceBundle bundle : bundles) {
if (bundle.containsKey(key.getKey())) {
if (bundle.containsKey(unit.getKey().getKey())) {
trs.getBundles().add(bundle);
}
}
}
}

res.addAll(unitsForInterface);
}

return Lists.newArrayList(c10NUnitsByKey.values());
return res;
}

private String extractTranslatedValue(C10NMsgFactory c10NMsgFactory,
Class<?> c10nInterface,
Method method,
Locale locale) {
try {
Class<?>[] paramTypes = method.getParameterTypes();
Object[] args = new Object[paramTypes.length];
for (int i = 0; i < args.length; i++) {
args[i] = dummyInstanceProvider.getInstance(c10nInterface, method, paramTypes[i], i);
if (null == args[i]) {
throw new C10NInspectorException("Cannot create dummy instance for" +
"type: " + paramTypes[i].getName());
}
}
Object v = method.invoke(c10NMsgFactory.get(c10nInterface, locale), args);
if (null != v) {
return v.toString();
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
return null;
}

private C10NUnit addC10NUnit(Map<C10NBundleKey, C10NUnit> unitByKey,
Expand All @@ -107,12 +150,20 @@ private C10NUnit addC10NUnit(Map<C10NBundleKey, C10NUnit> unitByKey,
return unit;
}

private C10NTranslations addTranslations(C10NUnit unit, Locale locale) {
private C10NTranslations addTranslations(C10NUnit unit, Locale locale, String value) {
C10NTranslations translations = unit.getTranslations().get(locale);
if (null == translations) {
translations = new C10NTranslations();
unit.getTranslations().put(locale, translations);
}
translations.setValue(value);
return translations;
}


private static final class C10NInspectorException extends Exception {
C10NInspectorException(String message) {
super(message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 c10n.tools.inspector;

import java.lang.reflect.Method;

/**
* <p>Default dummy instance provide that can generate dummy values for
* for all primitive types and strings.</p>
*
* @author rodion
*/
class DefaultDummyInstanceProvider implements DummyInstanceProvider {
@Override
public Object getInstance(Class<?> c10nInterface, Method method, Class<?> paramType, int paramIndex) {
//Replace String and Object type args with their respective index
if (paramType.isAssignableFrom(String.class) ||
paramType.isAssignableFrom(CharSequence.class)) {
return String.format("{%d}", paramIndex);
} else if (paramType.equals(byte.class)) {
return (byte) paramIndex;
} else if (paramType.equals(short.class)) {
return (short) paramIndex;
} else if (paramType.equals(int.class)) {
return paramIndex;
} else if (paramType.equals(long.class)) {
return (long) paramIndex;
} else if (paramType.equals(float.class)) {
return (float) paramIndex;
} else if (paramType.equals(double.class)) {
return (double) paramIndex;
} else if (paramType.equals(boolean.class)) {
return false;
} else if (paramType.equals(char.class)) {
return Character.forDigit(paramIndex, 10);
} else {
//give up
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 c10n.tools.inspector;

import java.lang.reflect.Method;

/**
* @author rodion
*/
public interface DummyInstanceProvider {
/**
* <p>Generate a dummy parameter instance for parameterized c10n methods.</p>
* <p>Consider the interface below:
* <code><pre>
* public interface Messages{
* &#64;En("Message {0}, {1}")
* String msg(String arg1, int arg2);
* }
* </pre></code>
* <p/>
* During inspection, translated value for the <code>msg(String,int)</code>
* method will be evaluated by invoking it for each locale. Since invokation
* requires a list of instaces of <code>String</code> and <code>int</code>,
* dummy instances will have to be generated at runtime.
* </p>
* <p>This method must make sure it always returns an instance of type
* specified by the <code>paramType</code> argument, or null if it cannot
* be correctly generated. If the generated instance is of other type, or null,
* the value for {@link c10n.tools.inspector.C10NTranslations#getValue()} after
* inspection will also be <code>null</code>.</p>
*
* @param c10nInterface c10n interface declaring the corresponding method (not null)
* @param method c10n method for which the translation is being provided (not null)
* @param paramType type of the parameter for which to generate the dummy instance (not null)
* @param paramIndex position of the parameter in the argument list
* @return instance of given parameter type (<code>paramType</code>) or <code>null</code>
* if instance cannot be generated.
*/
Object getInstance(Class<?> c10nInterface, Method method, Class<?> paramType, int paramIndex);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,15 @@
* @author rodion
*/
public class InspectorModule {
public static DummyInstanceProvider defaultDummyInstanceProvider() {
return new DefaultDummyInstanceProvider();
}

public static C10NInspector defaultInspector(ConfiguredC10NModule configuredC10NModule,
Set<Locale> localesToCheck) {
return new DefaultC10NInspector(SearchModule.reflectionsSearch(), configuredC10NModule, localesToCheck);
return new DefaultC10NInspector(SearchModule.reflectionsSearch(),
configuredC10NModule,
defaultDummyInstanceProvider(),
localesToCheck);
}
}
Loading

0 comments on commit 46df654

Please sign in to comment.