/* * Copyright (c) 2009-present the original author or authors. * * 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.planet57.gshell.util.i18n; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import com.planet57.gossip.Log; import com.planet57.gshell.util.i18n.MessageBundle.DefaultMessage; import com.planet57.gshell.util.i18n.MessageBundle.Key; import org.slf4j.Logger; import com.google.common.annotations.VisibleForTesting; import javax.annotation.Nullable; // adapted from https://github.com/sonatype/goodies/blob/master/i18n/src/main/java/org/sonatype/goodies/i18n/I18N.java /** * I18n strings access. * * @since 3.0 * @see MessageBundle */ public class I18N { private static final Logger log = Log.getLogger(I18N.class); @VisibleForTesting static final String MISSING_MESSAGE_FORMAT = "ERROR_MISSING_MESSAGE[%s]"; //NON-NLS private I18N() { // empty } /** * Returns a {@link MessageSource} for the given types. */ public static MessageSource of(final Class... types) { checkNotNull(types); checkArgument(types.length > 0); return new ResourceBundleMessageSource().add(false, types); } /** * Returns a proxy to the given {@link MessageBundle} type. */ @SuppressWarnings({"unchecked"}) public static <T extends MessageBundle> T create(final Class<T> type) { checkNotNull(type); return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new Handler(type)); } /** * Proxy invocation handler to convert method calls into message lookup/format. */ private static class Handler implements InvocationHandler { private final Class<? extends MessageBundle> type; private final MessageSource messages; public Handler(final Class<? extends MessageBundle> type) { this.type = checkNotNull(type); this.messages = I18N.of(type); } public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } else if (method.getReturnType() != String.class) { throw new Error("Illegal MessageBundle method: " + method); } String key = getKey(method); String format = getFormat(key); if (format == null) { DefaultMessage defaultMessage = method.getAnnotation(DefaultMessage.class); if (defaultMessage != null) { format = defaultMessage.value(); } } if (format == null) { log.warn("Missing message for: {}, key: {}", type, key); return String.format(MISSING_MESSAGE_FORMAT, key); } if (args != null) { return String.format(format, args); } return format; } @Nullable private String getFormat(final String key) { try { return messages.getMessage(key); } catch (ResourceNotFoundException e) { log.trace("Missing resource for: {}, key: {}", type, key); return null; } } private String getKey(final Method method) { Key key = method.getAnnotation(Key.class); if (key != null) { return key.value(); } return method.getName(); } } }