/*
*
* * Copyright (c) 2016. David Sowerby
* *
* * 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 uk.q3c.krail.core.i18n;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.vaadin.data.Property;
import uk.q3c.krail.core.option.AnnotationOptionList;
import uk.q3c.krail.core.option.Option;
import uk.q3c.krail.core.persist.common.i18n.PatternDao;
import javax.annotation.Nonnull;
import java.lang.annotation.Annotation;
import java.util.*;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Default implementation for {@link PatternSourceProvider}
* <p>
* Created by David Sowerby on 01/08/15.
*/
public class DefaultPatternSourceProvider implements PatternSourceProvider {
private final Map<Class<? extends Annotation>, Provider<PatternDao>> sources;
private final Map<Class<? extends Annotation>, Provider<PatternDao>> targets;
private final Option option;
private final Map<Class<? extends I18NKey>, LinkedHashSet<Class<? extends Annotation>>> sourceOrderByBundle;
private final ImmutableSet<Class<? extends Annotation>> sourceOrderDefault;
@Inject
public DefaultPatternSourceProvider(@PatternSources Map<Class<? extends Annotation>, Provider<PatternDao>> sources, @PatternTargets Map<Class<? extends
Annotation>, Provider<PatternDao>> targets, Option option, @PatternSourceOrderByBundle Map<Class<?
extends I18NKey>, LinkedHashSet<Class<? extends Annotation>>> sourceOrderByBundle, @PatternSourceOrderDefault Set<Class<? extends Annotation>>
sourceOrderDefault) {
this.sources = sources;
this.targets = targets;
this.option = option;
this.sourceOrderByBundle = sourceOrderByBundle;
this.sourceOrderDefault = ImmutableSet.copyOf(sourceOrderDefault);
}
@Override
@Nonnull
public Optional<PatternDao> sourceFor(@Nonnull Class<? extends Annotation> sourceAnnotation) {
Provider<PatternDao> provider = sources.get(sourceAnnotation);
return (provider == null) ? Optional.empty() : Optional.of(provider.get());
}
@Override
@Nonnull
public Optional<PatternDao> targetFor(@Nonnull Class<? extends Annotation> targetAnnotation) {
Provider<PatternDao> provider = targets.get(targetAnnotation);
return (provider == null) ? Optional.empty() : Optional.of(provider.get());
}
/**
* {@inheritDoc}
*/
@Nonnull
@Override
public Option getOption() {
return option;
}
/**
* {@inheritDoc}
*/
@Override
public void optionValueChanged(Property.ValueChangeEvent event) {
//do nothing options used as required
}
/**
* Returns the order in which sources are processed. The first non-null of the following is used:
* <ol>
* <li>the order returned by{@link #getSourceOrderFromOption(String)} (a value from {@link Option}</li>
* <li>the order returned by {@link #getSourceOrderDefaultFromOption()} (a value from {@link Option}</li>
* <li>{@link #sourceOrderByBundle}, which is defined by {@link I18NModule#sourcesOrderByBundle}</li>
* <li>{@link #sourceOrderDefault}, which is defined by {@link I18NModule#sourcesDefaultOrder} </li>
* <li>the keys from {@link #sources} - note that the order for this will be unreliable if PatternDaos have
* been defined by multiple Guice modules</li>
* <p>
* <p>If the source order contains less elements than the number of sources, missing elements are added in the order declared in {@link #sources}<br>
* If the source order contains more elements than the number of sources, any elements not in {@link #sources} are removed and a warning logged
* </ol>
*
* @param key
* used to identify the bundle, from {@link I18NKey#bundleName()}
*
* @return a list containing the sources to be processed, in the order that they should be processed
*/
@Override
@Nonnull
public ImmutableSet<Class<? extends Annotation>> orderedSources(@Nonnull I18NKey key) {
checkNotNull(key);
AnnotationOptionList sourceOrder = getSourceOrderFromOption(key.bundleName());
if (!sourceOrder.isEmpty()) {
return verifySourceOrder(sourceOrder.getList());
}
sourceOrder = getSourceOrderDefaultFromOption();
if (!sourceOrder.isEmpty()) {
return verifySourceOrder(sourceOrder.getList());
}
LinkedHashSet<Class<? extends Annotation>> order = this.sourceOrderByBundle.get(key.getClass());
if (order != null) {
return verifySourceOrder(order);
}
if (!sourceOrderDefault.isEmpty()) {
return verifySourceOrder(sourceOrderDefault);
}
// just return as defined in sources
return ImmutableSet.copyOf(sources.keySet());
}
/**
* Checks that source order has the correct number of elements as described in javadoc for {@link #orderedSources}
*
* @param sourceOrder
* the order as retrieved from configuration
*
* @return the finals source order, adjusted if necessary, as described in javadoc for {@link #orderedSources}
*/
private ImmutableSet<Class<? extends Annotation>> verifySourceOrder(@Nonnull Collection<Class<? extends Annotation>> sourceOrder) {
// if all and only sources contained, just return
if (sourceOrder.size() == sources.size() && sourceOrder.containsAll(sources.keySet())) {
return ImmutableSet.copyOf(sourceOrder);
}
LinkedHashSet<Class<? extends Annotation>> newOrder = new LinkedHashSet<>();
// iterate source order and add to new order only if in sources
sourceOrder.forEach(a -> {
if (sources.containsKey(a)) {
newOrder.add(a);
}
});
// if sizes the same we must have all source elements
if ((newOrder.size()) == sources.size()) {
return ImmutableSet.copyOf(newOrder);
}
//we have missing sources, so add them to new order
sources.keySet()
.forEach(a -> {
if (!newOrder.contains(a)) {
newOrder.add(a);
}
});
return ImmutableSet.copyOf(newOrder);
}
@Nonnull
private AnnotationOptionList getSourceOrderFromOption(@Nonnull String bundleName) {
checkNotNull(bundleName);
return option.get(optionKeySourceOrder.qualifiedWith(bundleName));
}
@Nonnull
private AnnotationOptionList getSourceOrderDefaultFromOption() {
return option.get(optionKeySourceOrderDefault);
}
@Override
public AnnotationOptionList selectedTargets() {
AnnotationOptionList optionTargets = option.get(optionKeySelectedTargets);
//use a copy to iterate over, otherwise removing from iterated set
if (!optionTargets.isEmpty()) {
List<Class<? extends Annotation>> copyTargets = new ArrayList<>(optionTargets.getList());
optionTargets.getList()
.forEach(t -> {
if (!targets.containsKey(t)) {
copyTargets.remove(t);
}
});
return new AnnotationOptionList(copyTargets);
}
return new AnnotationOptionList(Lists.newArrayList(targets.keySet()));
}
}