package ru.hflabs.rcd.accessor;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import ru.hflabs.rcd.exception.constraint.document.NotUniqueFieldsException;
import ru.hflabs.rcd.model.*;
import ru.hflabs.rcd.model.document.Dictionary;
import ru.hflabs.rcd.model.document.Field;
import ru.hflabs.rcd.model.document.Group;
import ru.hflabs.rcd.model.document.MetaField;
import ru.hflabs.rcd.model.path.FieldNamedPath;
import ru.hflabs.rcd.model.path.MetaFieldNamedPath;
import ru.hflabs.rcd.model.rule.RecodeRule;
import ru.hflabs.rcd.model.rule.RecodeRuleSet;
import ru.hflabs.rcd.model.rule.Rule;
import ru.hflabs.util.core.FormatUtil;
import ru.hflabs.util.spring.Assert;
import java.util.Collection;
import java.util.List;
import static ru.hflabs.rcd.model.ModelUtils.*;
/**
* Класс <class>Accessors</class> реализует вспомогательные методы для установки/доступа объектов, основанных на иерархии интерфейсов
*
* @see Function
* @see ModelUtils
*/
public abstract class Accessors {
private static final FieldAccessor IDENTITY = new IdentityFieldAccessor();
/** Сервис установки исходного значения в правило перекодирования */
public static final RuleFieldAccessor<FieldNamedPath, Field, RecodeRule> FROM_RULE_INJECTOR = new FromRuleFieldAccessorTemplate<FieldNamedPath, Field, RecodeRule>() {
@Override
public RecodeRule inject(RecodeRule target, Field value) {
return target.injectFrom(value).injectFromNamedPath(createFieldNamedPath(value));
}
};
/** Сервис установки целевого значения в правило перекодирования */
public static final RuleFieldAccessor<FieldNamedPath, Field, RecodeRule> TO_RULE_INJECTOR = new ToRuleFieldAccessorTemplate<FieldNamedPath, Field, RecodeRule>() {
@Override
public RecodeRule inject(RecodeRule target, Field value) {
return target.injectTo(value).injectToNamedPath(createFieldNamedPath(value));
}
};
/** Сервис установки исходного значения в набор правил перекодирования */
public static final RuleFieldAccessor<MetaFieldNamedPath, MetaField, RecodeRuleSet> FROM_SET_INJECTOR = new FromRuleFieldAccessorTemplate<MetaFieldNamedPath, MetaField, RecodeRuleSet>() {
@Override
public RecodeRuleSet inject(RecodeRuleSet target, MetaField value) {
return target.injectFrom(value).injectFromNamedPath(ModelUtils.createMetaFieldNamedPath(value));
}
};
/** Сервис установки целевого значения в набор правил перекодирования */
public static final RuleFieldAccessor<MetaFieldNamedPath, MetaField, RecodeRuleSet> TO_SET_INJECTOR = new ToRuleFieldAccessorTemplate<MetaFieldNamedPath, MetaField, RecodeRuleSet>() {
@Override
public RecodeRuleSet inject(RecodeRuleSet target, MetaField value) {
return target.injectTo(value).injectToNamedPath(ModelUtils.createMetaFieldNamedPath(value));
}
};
/** Сервис установки и доступа к полю перекодирования по умолчанию для набора правил */
public static final RelativeFieldAccessor<RecodeRuleSet, Field> DEFAULT_SET_INJECTOR = new RelativeFieldAccessorTemplate<RecodeRuleSet, Field>() {
@Override
public RecodeRuleSet inject(RecodeRuleSet target, Field value) {
RecodeRuleSet result = super.inject(target, value);
result.setDefaultPath(createFieldNamedPath(value));
return result;
}
};
/** Сервис установки и доступа к группе справочника через справочник */
public static final RelativeFieldAccessor<Dictionary, Group> GROUP_TO_DICTIONARY_INJECTOR = new RelativeFieldAccessorTemplate<>();
/** Сервис установки и доступа к справочнику через МЕТА-поле */
public static final RelativeFieldAccessor<MetaField, Dictionary> DICTIONARY_TO_META_FIELD_INJECTOR = new RelativeFieldAccessorTemplate<>();
/** Сервис установки и доступа к значению поля через МЕТА-поле */
public static final RelativeFieldAccessor<Field, MetaField> META_FIELD_TO_FIELD_INJECTOR = new RelativeFieldAccessorTemplate<>();
/** Сервис установки и доступа к группе справочников через МЕТА-поле */
public static final FieldAccessor<Group, MetaField> GROUP_TO_META_FIELD_INJECTOR = compose(DICTIONARY_TO_META_FIELD_INJECTOR, GROUP_TO_DICTIONARY_INJECTOR);
protected Accessors() {
// embedded constructor
}
/**
* Формирует и возвращает комплексный сервис преобразования сущностей по следующему принципу доступа:
* <p/>
* <i>A</i> -> <i>B</i> -> <i>C</i>
*
* @param b2c сервис преобразования <i>B</i> в <i>C</i>
* @param a2b сервис преобразования <i>A</i> в <i>B</i>
* @return Возвращает комплексный сервис преобразования сущностей
*/
public static <A, B, C> FieldAccessor<A, C> compose(final FieldAccessor<B, C> b2c, final FieldAccessor<A, B> a2b) {
return new FieldAccessor<A, C>() {
private final FieldAccessor<B, C> b_to_c = b2c;
private final FieldAccessor<A, B> a_to_b = a2b;
private final Function<C, A> a_to_c = Functions.compose(a_to_b, b_to_c);
@Override
public C inject(C target, A value) {
B intermediate = b_to_c.apply(target);
intermediate = a_to_b.inject(intermediate, value);
return b_to_c.inject(target, intermediate);
}
@Override
public A apply(C input) {
return a_to_c.apply(input);
}
};
}
/**
* Возвращает самозамыкающийся сервис преобразования
*
* @see com.google.common.base.Functions#identity()
*/
@SuppressWarnings("unchecked")
public static <T> FieldAccessor<T, T> identity() {
return (FieldAccessor<T, T>) IDENTITY;
}
/**
* Устанавливает идентификатор и возвращает сущность
*
* @param target целевая сущность
* @param value устанавливаемый идентификатор
* @return Возвращает сущность или <code>NULL</code>, если сущность не задана
* @see Identifying#setId(String)
*/
public static <T extends Identifying> T injectId(T target, String value) {
if (target != null) {
target.injectId(value);
}
return target;
}
/**
* Устанавливает название и возвращает сущность
*
* @param target целевая сущность
* @param value устанавливаемое название
* @return Возвращает сущность или <code>NULL</code>, если сущность не задана
* @see Named#setName(String)
*/
public static <T extends Named> T injectName(T target, String value) {
if (target != null) {
target.setName(value);
}
return target;
}
/**
* Устанавливает название, предварительно удалив из него лидирующие пробелы, и возвращает сущность
*
* @param target целевая сущность
* @param value устанавливаемое название
* @return Возвращает сущность или <code>NULL</code>, если сущность не задана
* @see #injectName(Named, String)
* @see String#trim()
*/
public static <T extends Named> T injectTrimmedName(T target, String value) {
return injectName(target, FormatUtil.parseString(value));
}
/**
* Устанавливает описание и возвращает сущность
*
* @param target целевая сущность
* @param value устанавливаемое описание
* @return Возвращает сущность или <code>NULL</code>, если сущность не задана
* @see Descriptioned#setDescription(String)
*/
public static <T extends Descriptioned> T injectDescription(T target, String value) {
if (target != null) {
target.setDescription(value);
}
return target;
}
/**
* Устанавливает описание, предварительно удалив из него лидирующие пробелы, и возвращает сущность
*
* @param target целевая сущность
* @param value устанавливаемое описание
* @return Возвращает сущность или <code>NULL</code>, если сущность не задана
* @see #injectDescription(Descriptioned, String)
* @see String#trim()
*/
public static <T extends Descriptioned> T injectTrimmedDescription(T target, String value) {
return injectDescription(target, FormatUtil.parseString(value));
}
/**
* Устанавливает связь между связанными объектами
*
* @param parent родитель
* @param descendant потомок
* @return Возвращает модифицированного потомка
*/
public static <R extends Identifying & OneToMany<T>, T extends Identifying & ManyToOne<R>> T linkRelative(R parent, T descendant) {
if (descendant != null) {
descendant.setRelative(parent);
}
return descendant;
}
/**
* Устанавливает связь между связанными объектами
*
* @param parent родитель
* @param descendants коллекция потомоков
* @return Возвращает модифицированного родителя
*/
public static <R extends Identifying & OneToMany<T>, T extends Identifying & ManyToOne<R>> R linkDescendants(R parent, Collection<T> descendants) {
if (descendants != null) {
List<T> targetDescendant = Lists.newArrayListWithExpectedSize(descendants.size());
for (T descendant : descendants) {
targetDescendant.add(linkRelative(parent, descendant));
}
parent.setDescendants(targetDescendant);
}
return parent;
}
/**
* Устанавливает связь между связанными объектами
*
* @param parent родитель
* @param descendants коллекция потомоков
* @return Возвращает коллекцию модифицированных потомков
*/
public static <R extends Identifying & OneToMany<T>, T extends Identifying & ManyToOne<R>> Collection<T> linkDescendants(Collection<T> descendants, R parent) {
return linkDescendants(parent, descendants).getDescendants();
}
/**
* Выполняет добавление значения поля к уже существующей коллекции в МЕТА-поле
*
* @param metaField целевое МЕТА-поле
* @param field добавляемое значение поля
*/
public static Field linkFieldToMetaField(MetaField metaField, Field field) {
field = linkRelative(metaField, field);
Collection<Field> fields = metaField.getDescendants();
if (fields == null) {
fields = metaField.isFlagEstablished(MetaField.FLAG_UNIQUE) ?
Sets.<Field>newLinkedHashSet() :
Lists.<Field>newLinkedList();
}
Assert.isTrue(
fields.add(field),
String.format("Meta field '%s' marked as unique, but has duplicate value '%s'", metaField.getName(), field.getValue()),
NotUniqueFieldsException.class
);
metaField.setDescendants(fields);
return field;
}
/**
* Выполняет копировавание сущности
*
* @param target целевая сущность
* @return Возвращает копию сущности
* @see Copyable#copy()
*/
public static <T extends Copyable> T shallowClone(T target) {
return target.copy();
}
/**
* Класс <class>IdentityFieldAccessor</class> реализует самозамыкающийся сервис доступа
*/
private static final class IdentityFieldAccessor implements FieldAccessor<Object, Object> {
@Override
public Object inject(Object target, Object value) {
return value;
}
@Override
public Object apply(Object input) {
return input;
}
}
/**
* Класс <class>RelativeFieldAccessor</class> реализует сервис доступа и установки связанных сущностей
*/
private static class RelativeFieldAccessorTemplate<R extends Identifying & ManyToOne<E>, E extends Identifying> implements RelativeFieldAccessor<R, E> {
@Override
public R inject(R target, E value) {
Assert.notNull(target, "Source relative object must not be NULL");
target.setRelative(value);
return target;
}
@Override
public E apply(R input) {
return input != null ? input.getRelative() : null;
}
@Override
public String applyRelativeId(R relative) {
return RELATIVE_ID_FUNCTION.apply(relative);
}
}
/**
* Класс <class>RuleFieldAccessorTemplate</class> реализует шаблон для доступа/установки полей для правила
*/
private abstract static class RuleFieldAccessorTemplate<NP extends MetaFieldNamedPath, T extends Essence, R extends Rule<NP, T, R>> implements RuleFieldAccessor<NP, T, R> {
/** Функция доступа к связанному идентификатору */
private final Function<Rule<?, ?, ?>, String> fieldIdFunction;
/** Функция доступа к именованному пути */
private final Function<R, NP> namedPathFunction;
protected RuleFieldAccessorTemplate(Function<Rule<?, ?, ?>, String> fieldIdFunction) {
this.fieldIdFunction = fieldIdFunction;
this.namedPathFunction = new Function<R, NP>() {
@Override
public NP apply(R input) {
return applyNamedPath(input);
}
};
}
@Override
public String applyRelativeId(Rule<?, ?, ?> rule) {
return fieldIdFunction.apply(rule);
}
@Override
public Function<R, NP> getNamedPathFunction() {
return namedPathFunction;
}
}
/**
* Класс <class>FromRuleFieldAccessorTemplate</class> реализует шаблон доступа/установки полей источника для правила
*/
private abstract static class FromRuleFieldAccessorTemplate<NP extends MetaFieldNamedPath, T extends Essence, R extends Rule<NP, T, R>> extends RuleFieldAccessorTemplate<NP, T, R> {
protected FromRuleFieldAccessorTemplate() {
super(FROM_RULE_FIELD_ID);
}
@Override
public NP applyNamedPath(R rule) {
return rule.getFromNamedPath();
}
@Override
public T apply(R input) {
return input.getFrom();
}
}
/**
* Класс <class>ToRuleFieldAccessorTemplate</class> реализует шаблон доступа/установки полей назначения для правила
*/
private abstract static class ToRuleFieldAccessorTemplate<NP extends MetaFieldNamedPath, T extends Essence, R extends Rule<NP, T, R>> extends RuleFieldAccessorTemplate<NP, T, R> {
protected ToRuleFieldAccessorTemplate() {
super(TO_RULE_FIELD_ID);
}
@Override
public NP applyNamedPath(R rule) {
return rule.getToNamedPath();
}
@Override
public T apply(R input) {
return input.getTo();
}
}
}