/* * 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 com.github.rodionmoiseev.c10n; import com.github.rodionmoiseev.c10n.share.utils.Preconditions; /** * @author rodion */ public final class C10NFilters { /** * <p>Create an Enum-to-methods mapping filter to ease Enum translation using C10N. * * <h3>Basic Usage</h3> * <p>Consider the following Enum type with 3 values: * <pre>{@code * enum Status{ * Open, Closed, Pending * } * } * </pre> * * <p>In order to localise each of the values, first create a c10n interface with a method for * each of the Status values * <pre>{@code * @C10NMessages * public interface StatusMsg { * @En("open") * @Ja("未着手") * String open(); * * @En("closed! beer time!") * @Ja("完了") * String closed(); * * @En("pending ...") * @Ja("進行中") * String pending(); * } * } * </pre> * * Then, in your c10n configuration add an enum-filter binding for the Status type * * <pre>{@code * void configure(){ * bindFilter(C10NFilters.enumMapping(Status.class, StatusMsg.class), Status.class); * } * } * </pre> * * <p>Now, every time c10n encounters Status as a method argument in a c10n-interface type, * the it will be replaced with the appropriate localised version of the Enum value: * * <pre>{@code * @C10NMessages * public interface Messages{ * @En("Localised status is: {0}") * @Ja("状態は{0}") * String showStatus(Status status); * } * } * </pre> * * <p>Invoking <code>showStatus(Status.Closed)</code> will render * as <code>"Localised status is: closed! beer time!"</code>. * * <h2>Restricting Filter Application</h2> * <p>You can restrict filter application only to method arguments annotated with a given * annotation, or one of the given annotations from a list, by using <code>annotatedWith(Class)</code> method * when binding. For example: * <pre>{@code * void configure(){ * bindFilter(new IntFormattingFilter(), int.class) * .annotatedWith(Precise.class); * } * } * </pre> * * <p>The above declaration will make sure int arguments are only passed through the * <code>IntFormattingFilter</code> whenever the method argument is marked with the <code>@Precise</code> * annotation. Other int arguments will not have the filter applied. * * <h2>Method Mapping Rules</h2> * <p>Enum values are mapped to c10n-interface methods in the following order: * <ol> * <li>Method name matches "<Enum class name>_<Enum value name>". e.g <code>status_open()</code></li> * <li>Method name matches "<Enum value name>" e.g. <code>closed()</code></li> * </ol> * * <p><i>Note:</i> method mapping is case-insensitive. * <p><i>Note:</i> mapped methods cannot take any arguments. Methods with arguments will be excluded from mapping. * <p><i>Warning:</i> if mapping for one or more values is not found, a runtime exception will be thrown. * * @param enumClass Enum type to create mapping for * @param c10nMappedInterface a c10n-interface containing mapped methods * @param <E> Enum type * @return a non-cached provider of enum mapping filter */ public static <E extends Enum<?>> C10NFilterProvider<E> enumMapping(Class<E> enumClass, Class<?> c10nMappedInterface) { return new EnumMappingFilterProvider<E>(enumClass, c10nMappedInterface); } /** * <p>Filter provider that always returns the specified instance * * @param filter filter instance to return from the generated provider(not-null) * @param <T> Filter argument type * @return instance of filter provider (never-null) */ public static <T> C10NFilterProvider<T> staticFilterProvider(C10NFilter<T> filter) { Preconditions.assertNotNull(filter, "filter"); return new StaticC10NFilterProvider<T>(filter); } /** * <p>Decorates the specified filter provider with a simple static cache. * Only the first call will result in an execution of {@link com.github.rodionmoiseev.c10n.C10NFilterProvider#get()} method. * The following calls will always return a cached instance of the first call. * * @param filterProvider filter provider to decorate with caching (not-null) * @param <T> Filter argument type * @return instance of a filter provider decorated with simple static cache (never-null) */ public static <T> C10NFilterProvider<T> cachedFilterProvider(C10NFilterProvider<T> filterProvider) { Preconditions.assertNotNull(filterProvider, "filterProvider"); return new CachedC10NFilterProvider<T>(filterProvider); } private static final class StaticC10NFilterProvider<T> implements C10NFilterProvider<T> { private final C10NFilter<T> filter; private StaticC10NFilterProvider(C10NFilter<T> filter) { this.filter = filter; } @Override public C10NFilter<T> get() { return filter; } } private static final class CachedC10NFilterProvider<T> implements C10NFilterProvider<T> { private final C10NFilterProvider<T> base; private C10NFilter<T> thunk = null; private CachedC10NFilterProvider(C10NFilterProvider<T> base) { this.base = base; } @Override public C10NFilter<T> get() { if (null == thunk) { thunk = base.get(); } return thunk; } } /** * @author rodion */ private static final class EnumMappingFilterProvider<E extends Enum<?>> implements C10NFilterProvider<E> { private final Class<E> enumClass; private final Class<?> c10nMappedInterface; EnumMappingFilterProvider(Class<E> enumClass, Class<?> c10nMappedInterface) { this.enumClass = enumClass; this.c10nMappedInterface = c10nMappedInterface; } @Override public C10NFilter<E> get() { return new EnumMappingFilter<E>(enumClass, c10nMappedInterface); } } }