All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.meeuw.i18n.regions.RegionService Maven / Gradle / Ivy

Go to download

Provides the service loader for Regions, and some utilities to deal with regions generally

There is a newer version: 2.1.0
Show newest version
package org.meeuw.i18n.regions;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javax.annotation.Priority;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.meeuw.i18n.regions.spi.RegionProvider;

/**
 * The singleton service providing information about the registered regions.
 * @author Michiel Meeuwissen
 * @since 0.1
 */
public class RegionService {

    private static final Logger logger = Logger.getLogger(RegionService.class.getName());

    private static final RegionService INSTANCE = new RegionService();

    private boolean inited = false;
    private List> providers;

    private RegionService() {

    }

    /**
     * The singleton of this class.
     */
    public static RegionService getInstance() {
        INSTANCE.initIfNeeded();
        return INSTANCE;
    }


     /**
      * Searches all available {@link RegionProvider}s for a region with given code and class.
      * @param code The (ISO) code
      * @param lenient Whether matching should be lenient or strict. Lenient matching may e.g. be case insensitive or matching on former codes
      * @param clazz The subclass or sub interface of {@link Region} which is searched
      * @param checker An extra predicate to which the region must match
      * @param  The type of the requested Region type
      *
     * @return an optional of region. Empty if not found.
     */
    @SuppressWarnings("unchecked")
    public  Optional getByCode(@NonNull String code, boolean lenient, @NonNull Class clazz, @NonNull Predicate checker) {
        for (RegionProvider provider : getProviders()) {
            if (provider.canProvide(clazz)) {
                try {
                    Optional byCode = provider.getByCode(code, lenient);
                    if (byCode.isPresent()) {
                        if (clazz.isInstance(byCode.get()) && checker.test(byCode.get())) {
                            return (Optional) byCode;
                        }
                    }
                } catch (Exception e) {
                    logger.warning(() -> "Exception from " + provider + " for code " + code);
                }
            }

        }
        return Optional.empty();
    }

    /**
     * A defaulting version of {@link #getByCode(String, boolean, Class, Predicate)}. The predicate is implicitly always true.
     */

    public   Optional getByCode(@NonNull String code, boolean lenient, @NonNull Class clazz) {
        return getByCode(code, lenient, clazz, (c) -> true);
    }

     /**
      * A defaulting version of {@link #getByCode(String, boolean, Class, Predicate)}. No predicate, lenient matching.
     */
    public   Optional getByCode(@NonNull String code, @NonNull Class clazz) {
        return getByCode(code, true, clazz);
    }


    /**
     *
     */
    public  Optional getByCode(@NonNull String s, boolean lenient) {
        return getByCode(s, lenient, Region.class);
    }
     /**
     * Searches all available {@link RegionProvider}s for a region with given code and class. This is a defaulting
      * version of {@link #getByCode(String, Class)}, where the second argument is {@link Region}, and hence it will search Regions of all types.
     * @return an optional of region. Empty if not found.
     */
    public  Optional getByCode(String s) {
        return getByCode(s, true);
    }

    /**
     * Returns a stream of all known instances of Region. Filtered by class.
     *
     * You could also use {@link #values()} and then simply {@link Stream#filter(Predicate)}, but the advantage of this method is that {@link RegionProvider}'s which say they cannot {@link RegionProvider#canProvide(Class)} the given class are not even consulted.
     *
     * @param clazz The class (a subclass or interface of {@link Region}
     * @param 
     */
    public   Stream values(Class clazz) {
        Spliterator spliterator = new Spliterator() {
            private final Iterator> iterator = getProviders().iterator();
            private Spliterator values;

            @Override
            public boolean tryAdvance(Consumer action) {
                while (values == null || !values.tryAdvance(action)) {
                    if (iterator.hasNext()) {
                        RegionProvider rp = iterator.next();
                        if (rp.canProvide(clazz)) {
                            values = (Spliterator) rp.values().spliterator();
                        }
                    } else {
                        return false;
                    }
                }
                return true;
            }

            @Override
            public Spliterator trySplit() {
                return null;
            }

            @Override
            public long estimateSize() {
                return Long.MAX_VALUE;
            }

            @Override
            public int characteristics() {
                return IMMUTABLE;
            }
        };
        return StreamSupport.stream(spliterator, false).filter(clazz::isInstance);
    }


    /**
     * @return A stream of all known {@link Region} instances of one of the given {@link Region.Type}.
     */
    public Stream values(Region.Type... types) {
        Set set = new HashSet<>(Arrays.asList(types));
        return values().filter(r -> set.contains(r.getType()));
    }

    /**
     * @return A stream of all known {@link Region} instances.
     */
    public Stream values() {
        return values(Region.class);
    }

    /**
     * @return The providers currently registered
     */
    public List> getProviders() {
        return Collections.unmodifiableList(providers);
    }

    /**
     */
    public > Optional getProvider(Class clazz) {
        return (Optional) providers.stream().filter(clazz::isInstance).findFirst();
    }



    private void initIfNeeded() {
        if (! inited) {
            boolean init = false;
            synchronized(this) {
                if (! inited) {
                    final ServiceLoader loader = ServiceLoader.load(RegionProvider.class);
                    List> list = new ArrayList<>();
                    loader.iterator().forEachRemaining(list::add);
                    list.sort(priorityComparator());
                    providers = Collections.unmodifiableList(list);
                    inited = true;
                    init = true;
                }
            }
            if (init) {
                // run in separate thread to avoid dead locks
                new Thread(() ->
                    logger.log(Level.FINE, "RegionService has {0}", providers)
                ).start();
            }
        }

    }


    private static  Comparator priorityComparator() {
        return  (o1, o2) -> {
            try {
                final Priority p1 = o1 == null ? null : o1.getClass().getAnnotation(Priority.class);
                final int v1 = p1 != null ? p1.value() : 100;
                final Priority p2 = o2 == null ? null : o2.getClass().getAnnotation(Priority.class);
                final int v2 = p2 != null ? p2.value() : 100;
                return v1 - v2;
            } catch (NoClassDefFoundError ncdfe) {
                logger.log(Level.INFO, "{0}:{1} region services {2} {3} are unordered", new Object[]{ncdfe.getClass(), ncdfe.getMessage(), o1, o2});
                String sn1 = o1 == null ? null : o1.getClass().getSimpleName();
                String sn2 = o2 == null ? null : o2.getClass().getSimpleName();
                return Objects.compare(sn1, sn2, Comparator.naturalOrder());
            }
        };

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy