Automation + Expression WIP

This commit is contained in:
Patrick Haßel 2024-11-25 15:53:05 +01:00
parent 850d23f293
commit ded17239b6
65 changed files with 1619 additions and 1 deletions

View File

@ -0,0 +1,24 @@
import {Expression, expressionFromJson} from '../Expression/Expression';
import {validateBoolean, validateString} from '../common/validators';
export class Automation {
constructor(
readonly uuid: string,
readonly name: string,
readonly enabled: boolean,
readonly condition: Expression,
) {
//
}
static fromJson(json: any): Automation {
return new Automation(
validateString(json.uuid),
validateString(json.name),
validateBoolean(json.enabled),
expressionFromJson(json.condition),
);
}
}

View File

@ -0,0 +1,35 @@
import {validateString} from '../common/validators';
import {ExpressionLiteralBoolean} from './ExpressionLiteralBoolean';
import {ExpressionLiteralNumber} from './ExpressionLiteralNumber';
import {ExpressionProperty} from './ExpressionProperty';
import {ExpressionUnary} from './ExpressionUnary';
import {ExpressionNary} from './ExpressionNary';
export abstract class Expression {
protected constructor(
readonly _type_: string,
readonly uuid: string,
readonly name: string,
) {
//
}
}
export function expressionFromJson(json: any): Expression {
const _type_ = validateString(json._type_);
switch (_type_) {
case 'ExpressionLiteralBoolean':
return ExpressionLiteralBoolean.fromJson(json);
case 'ExpressionLiteralNumber':
return ExpressionLiteralNumber.fromJson(json);
case 'ExpressionProperty':
return ExpressionProperty.fromJson(json);
case 'ExpressionUnary':
return ExpressionUnary.fromJson(json);
case 'ExpressionNary':
return ExpressionNary.fromJson(json);
}
throw Error("expression type not implemented: " + _type_);
}

View File

@ -0,0 +1,14 @@
import {Expression} from "./Expression";
export abstract class ExpressionLiteral<T> extends Expression {
protected constructor(
_type_: string,
uuid: string,
name: string,
readonly literal: T | null,
) {
super(_type_, uuid, name);
}
}

View File

@ -0,0 +1,25 @@
import {validateBooleanOrNull, validateString} from "../common/validators";
import {ExpressionLiteral} from './ExpressionLiteral';
export class ExpressionLiteralBoolean extends ExpressionLiteral<boolean> {
constructor(
_type_: string,
uuid: string,
name: string,
readonly value: boolean | null,
) {
super(_type_, uuid, name, value);
}
static fromJson(json: any): ExpressionLiteralBoolean {
return new ExpressionLiteralBoolean(
validateString(json._type_),
validateString(json.uuid),
validateString(json.name),
validateBooleanOrNull(json.value),
);
}
}

View File

@ -0,0 +1,25 @@
import {validateNumberOrNull, validateString} from "../common/validators";
import {ExpressionLiteral} from './ExpressionLiteral';
export class ExpressionLiteralNumber extends ExpressionLiteral<number> {
constructor(
_type_: string,
uuid: string,
name: string,
readonly value: number | null,
) {
super(_type_, uuid, name, value);
}
static fromJson(json: any): ExpressionLiteralNumber {
return new ExpressionLiteralNumber(
validateString(json._type_),
validateString(json.uuid),
validateString(json.name),
validateNumberOrNull(json.value),
);
}
}

View File

@ -0,0 +1,29 @@
import {orNull, validateString} from "../common/validators";
import {Expression, expressionFromJson} from './Expression';
import {ExpressionNaryOperator} from './ExpressionNaryOperator';
export class ExpressionNary extends Expression {
constructor(
_type_: string,
uuid: string,
name: string,
readonly operator: ExpressionNaryOperator,
readonly expression0: Expression | null,
readonly expression1: Expression | null,
) {
super(_type_, uuid, name);
}
static fromJson(json: any): ExpressionNary {
return new ExpressionNary(
validateString(json._type_),
validateString(json.uuid),
validateString(json.name),
validateString(json.operator) as ExpressionNaryOperator,
orNull(json.expression0, j => expressionFromJson(j)),
orNull(json.expression1, j => expressionFromJson(j)),
);
}
}

View File

@ -0,0 +1,15 @@
export enum ExpressionNaryOperator {
OR = 'OR',
AND = 'AND',
XOR = 'XOR',
ADD = 'ADD',
SUB = 'SUB',
MUL = 'MUL',
DIV = 'DIV',
MOD = 'MOD',
POW = 'POW',
HYPOT = 'HYPOT',
ATAN2 = 'ATAN2',
MIN = 'MIN',
MAX = 'MAX',
}

View File

@ -0,0 +1,24 @@
import {validateString} from "../common/validators";
import {Expression} from './Expression';
export class ExpressionProperty extends Expression {
constructor(
_type_: string,
uuid: string,
name: string,
readonly propertyId: string,
) {
super(_type_, uuid, name);
}
static fromJson(json: any): ExpressionProperty {
return new ExpressionProperty(
validateString(json._type_),
validateString(json.uuid),
validateString(json.name),
validateString(json.propertyId),
);
}
}

View File

@ -0,0 +1,27 @@
import {orNull, validateString} from "../common/validators";
import {Expression, expressionFromJson} from './Expression';
import {ExpressionUnaryOperator} from './ExpressionUnaryOperator';
export class ExpressionUnary extends Expression {
constructor(
_type_: string,
uuid: string,
name: string,
readonly operator: ExpressionUnaryOperator,
readonly expression: Expression | null,
) {
super(_type_, uuid, name);
}
static fromJson(json: any): ExpressionUnary {
return new ExpressionUnary(
validateString(json._type_),
validateString(json.uuid),
validateString(json.name),
validateString(json.operator) as ExpressionUnaryOperator,
orNull(json.expression, j => expressionFromJson(j)),
);
}
}

View File

@ -0,0 +1,21 @@
export enum ExpressionUnaryOperator {
NOT = 'NOT',
NEG = 'NEG',
POS = 'POS',
FLOOR = 'FLOOR',
CEIL = 'CEIL',
ROUND = 'ROUND',
SQRT = 'SQRT',
ABS = 'ABS',
COS = 'COS',
SIN = 'SIN',
TAN = 'TAN',
ACOS = 'ACOS',
ASIN = 'ASIN',
ATAN = 'ATAN',
COSH = 'COSH',
SINH = 'SINH',
TANH = 'TANH',
EXP = 'EXP',
EXPM1 = 'EXPM1',
}

View File

@ -0,0 +1,22 @@
import {Injectable} from '@angular/core';
import {ExpressionLiteralBoolean} from './ExpressionLiteralBoolean';
import {ApiService} from '../common/api.service';
import {CrudService} from '../common/CrudService';
import {Next} from '../common/types';
@Injectable({
providedIn: 'root'
})
export class ExpressionLiteralBooleanService extends CrudService<ExpressionLiteralBoolean> {
constructor(
apiService: ApiService,
) {
super(apiService, ['ExpressionLiteralBoolean'], ExpressionLiteralBoolean.fromJson);
}
setValue(expression: ExpressionLiteralBoolean, value: boolean, next?: Next<ExpressionLiteralBoolean>) {
this.postSingle([expression.uuid, 'setValue'], value, next);
}
}

View File

@ -0,0 +1,22 @@
import {Injectable} from '@angular/core';
import {ExpressionLiteralNumber} from './ExpressionLiteralNumber';
import {ApiService} from '../common/api.service';
import {CrudService} from '../common/CrudService';
import {Next} from '../common/types';
@Injectable({
providedIn: 'root'
})
export class ExpressionLiteralNumberService extends CrudService<ExpressionLiteralNumber> {
constructor(
apiService: ApiService,
) {
super(apiService, ['ExpressionLiteralNumber'], ExpressionLiteralNumber.fromJson);
}
setValue(expression: ExpressionLiteralNumber, value: number, next?: Next<ExpressionLiteralNumber>) {
this.postSingle([expression.uuid, 'setValue'], value, next);
}
}

View File

@ -0,0 +1 @@
<input type="checkbox" [ngModel]="expression.value" (ngModelChange)="expressionService.setValue(expression, $event)">

View File

@ -0,0 +1,26 @@
import {Component, Input} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {ExpressionLiteralBoolean} from '../../../api/Expression/ExpressionLiteralBoolean';
import {ExpressionLiteralBooleanService} from '../../../api/Expression/expression-literal-boolean.service';
@Component({
selector: 'app-expression-literal-boolean',
standalone: true,
imports: [
FormsModule
],
templateUrl: './expression-literal-boolean.component.html',
styleUrl: './expression-literal-boolean.component.less'
})
export class ExpressionLiteralBooleanComponent {
@Input()
expression!: ExpressionLiteralBoolean;
constructor(
readonly expressionService: ExpressionLiteralBooleanService,
) {
//
}
}

View File

@ -0,0 +1 @@
<input type="number" [ngModel]="expression.value" (ngModelChange)="expressionService.setValue(expression, $event)">

View File

@ -0,0 +1,26 @@
import {Component, Input} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {ExpressionLiteralNumber} from '../../../api/Expression/ExpressionLiteralNumber';
import {ExpressionLiteralNumberService} from '../../../api/Expression/expression-literal-number.service';
@Component({
selector: 'app-expression-literal-number',
standalone: true,
imports: [
FormsModule
],
templateUrl: './expression-literal-number.component.html',
styleUrl: './expression-literal-number.component.less'
})
export class ExpressionLiteralNumberComponent {
@Input()
expression!: ExpressionLiteralNumber;
constructor(
readonly expressionService: ExpressionLiteralNumberService,
) {
//
}
}

View File

@ -0,0 +1,8 @@
<ng-container [ngSwitch]="expression._type_">
<ng-container *ngSwitchCase="'ExpressionLiteralDouble'">
<app-expression-literal-boolean [expression]="asExpressionLiteralBoolean()"></app-expression-literal-boolean>
</ng-container>
<ng-container *ngSwitchCase="'ExpressionLiteralDouble'">
<app-expression-literal-number [expression]="asExpressionLiteralNumber()"></app-expression-literal-number>
</ng-container>
</ng-container>

View File

@ -0,0 +1,49 @@
import {Component, Input} from '@angular/core';
import {Expression} from '../../api/Expression/Expression';
import {ExpressionLiteralBoolean} from '../../api/Expression/ExpressionLiteralBoolean';
import {NgSwitch, NgSwitchCase} from '@angular/common';
import {ExpressionLiteralNumber} from '../../api/Expression/ExpressionLiteralNumber';
import {ExpressionProperty} from '../../api/Expression/ExpressionProperty';
import {ExpressionUnary} from '../../api/Expression/ExpressionUnary';
import {ExpressionNary} from '../../api/Expression/ExpressionNary';
import {ExpressionLiteralBooleanComponent} from './expression-literal-boolean/expression-literal-boolean.component';
import {ExpressionLiteralNumberComponent} from './expression-literal-number/expression-literal-number.component';
@Component({
selector: 'app-expression',
standalone: true,
imports: [
NgSwitch,
NgSwitchCase,
ExpressionLiteralBooleanComponent,
ExpressionLiteralNumberComponent
],
templateUrl: './expression.component.html',
styleUrl: './expression.component.less'
})
export class ExpressionComponent {
@Input()
expression!: Expression;
asExpressionLiteralBoolean(): ExpressionLiteralBoolean {
return this.expression as ExpressionLiteralBoolean;
}
asExpressionLiteralNumber(): ExpressionLiteralNumber {
return this.expression as ExpressionLiteralNumber;
}
asExpressionProperty(): ExpressionProperty {
return this.expression as ExpressionProperty;
}
asExpressionUnary(): ExpressionUnary {
return this.expression as ExpressionUnary;
}
asExpressionNary(): ExpressionNary {
return this.expression as ExpressionNary;
}
}

View File

@ -0,0 +1,48 @@
package de.ph87.home.automation;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.Runtime;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import java.util.UUID;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class Automation {
@Id
@NonNull
private String uuid = UUID.randomUUID().toString();
@NonNull
@Column(nullable = false)
private String name;
@Column(nullable = false)
private boolean enabled = false;
@Nullable
@ManyToOne
private AbstractExpression condition;
public Automation(@NonNull final String name, final boolean enabled, @NonNull final AbstractExpression condition) {
this.name = name;
this.enabled = enabled;
this.condition = condition;
}
public boolean evaluateCondition(@NonNull final Runtime runtime) {
return AbstractExpression.mapOrElseNotNull(condition, c -> c.evaluate(runtime), AbstractExpression::convertToBoolean, false);
}
}

View File

@ -0,0 +1,22 @@
package de.ph87.home.automation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequiredArgsConstructor
@RequestMapping("Automation")
public class AutomationController {
private final AutomationService automationService;
@GetMapping("list")
public List<AutomationDto> list() {
return automationService.listDto();
}
}

View File

@ -0,0 +1,31 @@
package de.ph87.home.automation;
import de.ph87.home.expression.AbstractExpressionDto;
import jakarta.annotation.Nullable;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString
public class AutomationDto {
@NonNull
private final String uuid;
@NonNull
private final String name;
private final boolean enabled;
@Nullable
private final AbstractExpressionDto condition;
public AutomationDto(@NonNull final Automation automation, @Nullable final AbstractExpressionDto condition) {
this.uuid = automation.getUuid();
this.name = automation.getName();
this.enabled = automation.isEnabled();
this.condition = condition;
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.automation;
import org.springframework.data.repository.ListCrudRepository;
public interface AutomationRepository extends ListCrudRepository<Automation, String> {
}

View File

@ -0,0 +1,36 @@
package de.ph87.home.automation;
import de.ph87.home.expression.AbstractExpressionDto;
import de.ph87.home.expression.expression.ExpressionService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static de.ph87.home.expression.AbstractExpression.mapOrElse;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class AutomationService {
private final AutomationRepository automationRepository;
private final ExpressionService expressionService;
@NonNull
public List<AutomationDto> listDto() {
return automationRepository.findAll().stream().map(this::toDto).toList();
}
@NonNull
private AutomationDto toDto(@NonNull final Automation automation) {
final AbstractExpressionDto condition = mapOrElse(automation.getCondition(), expressionService::toDto, null);
return new AutomationDto(automation, condition);
}
}

View File

@ -1,6 +1,10 @@
package de.ph87.home.demo; package de.ph87.home.demo;
import de.ph87.home.automation.Automation;
import de.ph87.home.automation.AutomationRepository;
import de.ph87.home.device.DeviceService; import de.ph87.home.device.DeviceService;
import de.ph87.home.expression.expression.ExpressionRepository;
import de.ph87.home.expression.expression.literal.bool.ExpressionLiteralBoolean;
import de.ph87.home.knx.property.KnxPropertyService; import de.ph87.home.knx.property.KnxPropertyService;
import de.ph87.home.knx.property.KnxPropertyType; import de.ph87.home.knx.property.KnxPropertyType;
import de.ph87.home.shutter.ShutterService; import de.ph87.home.shutter.ShutterService;
@ -24,6 +28,10 @@ public class DemoService {
private final ShutterService shutterService; private final ShutterService shutterService;
private final ExpressionRepository expressionRepository;
private final AutomationRepository automationRepository;
@EventListener(ApplicationStartedEvent.class) @EventListener(ApplicationStartedEvent.class)
public void startup() { public void startup() {
knxPropertyService.create("eg_ambiente", KnxPropertyType.BOOLEAN, adr(849), adr(848)); knxPropertyService.create("eg_ambiente", KnxPropertyType.BOOLEAN, adr(849), adr(848));
@ -58,6 +66,9 @@ public class DemoService {
knxPropertyService.create("kueche_tuer", KnxPropertyType.DOUBLE, adr(2324), adr(2324)); knxPropertyService.create("kueche_tuer", KnxPropertyType.DOUBLE, adr(2324), adr(2324));
shutterService.create("Küche Tür", "kueche_tuer", "kueche_tuer"); shutterService.create("Küche Tür", "kueche_tuer", "kueche_tuer");
final ExpressionLiteralBoolean exp = expressionRepository.save(new ExpressionLiteralBoolean(true));
automationRepository.save(new Automation("test", true, exp));
} }
private static GroupAddress adr(final int rawGroupAddress) { private static GroupAddress adr(final int rawGroupAddress) {

View File

@ -0,0 +1,168 @@
package de.ph87.home.expression;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Function;
@Getter
@ToString
@NoArgsConstructor
@Entity(name = "expression")
@DiscriminatorColumn(name = "_type_")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class AbstractExpression {
@Id
@NonNull
private String uuid = UUID.randomUUID().toString();
@NonNull
@Column(nullable = false)
private String name = "";
public abstract Object evaluate(@NonNull final Runtime runtime);
@NonNull
public static <T, R> Object applyFallback(final Object v0, final Object v1, @NonNull final Function<Object, T> convert, @NonNull final T fallback, @NonNull final BiFunction<T, T, R> function) {
final T c0 = orElseNotNull(convert.apply(v0), fallback);
final T c1 = orElseNotNull(convert.apply(v1), fallback);
return function.apply(c0, c1);
}
@Nullable
public static <T, R> Object applyNullToNull(final Object value, @NonNull final Function<Object, T> convert, @NonNull final Function<T, R> function) {
final T converted = convert.apply(value);
if (converted == null) {
return null;
}
return function.apply(converted);
}
@Nullable
public static <T, R> Object applyNullToNull(final Object v0, final Object v1, @NonNull final Function<Object, T> convert, @NonNull final BiFunction<T, T, R> function) {
final T c0 = convert.apply(v0);
final T c1 = convert.apply(v1);
if (c0 == null || c1 == null) {
return null;
}
return function.apply(c0, c1);
}
@Nullable
public static Object evalOrNull(@NonNull final Runtime runtime, @Nullable final AbstractExpression expression) {
if (expression == null) {
return null;
}
return expression.evaluate(runtime);
}
@Nullable
public static Boolean convertToBoolean(@Nullable final Object value) {
if (value == null) {
return null;
}
if (value instanceof Boolean) {
return (boolean) value;
}
final Double number = convertToNumber(value);
if (number == null) {
return null;
}
return number > 0;
}
@Nullable
public static Double convertToNumber(@Nullable final Object value) {
if (value == null) {
return null;
}
if (value instanceof Number) {
return (double) value;
}
try {
return Double.parseDouble((String) value);
} catch (NumberFormatException e) {
return null;
}
}
@NonNull
public static <T> T orElseNotNull(@Nullable final T t, @NonNull final T orElse) {
if (t == null) {
return orElse;
}
return t;
}
@NonNull
public static <T, R> R mapOrElseNotNull(@Nullable final T t, @NonNull final Function<T, R> tr, @NonNull final R orElse) {
if (t == null) {
return orElse;
}
final R r = tr.apply(t);
if (r == null) {
return orElse;
}
return r;
}
@NonNull
public static <T, U, R> R mapOrElseNotNull(@Nullable final T t, @NonNull final Function<T, U> tu, @NonNull final Function<U, R> ur, @NonNull final R orElse) {
if (t == null) {
return orElse;
}
final U u = tu.apply(t);
if (u == null) {
return orElse;
}
final R r = ur.apply(u);
if (r == null) {
return orElse;
}
return r;
}
@Nullable
public static <T> T orElse(@Nullable final T t, @Nullable final T orElse) {
if (t == null) {
return orElse;
}
return t;
}
@Nullable
public static <T, R> R mapOrElse(@Nullable final T t, @NonNull final Function<T, R> tr, @Nullable final R orElse) {
if (t == null) {
return orElse;
}
final R r = tr.apply(t);
if (r == null) {
return orElse;
}
return r;
}
@Nullable
public static <T, U, R> R mapOrElse(@Nullable final T t, @NonNull final Function<T, U> tu, @NonNull final Function<U, R> ur, @Nullable final R orElse) {
if (t == null) {
return orElse;
}
final U u = tu.apply(t);
if (u == null) {
return orElse;
}
final R r = ur.apply(u);
if (r == null) {
return orElse;
}
return r;
}
}

View File

@ -0,0 +1,19 @@
package de.ph87.home.expression;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractExpressionController<E extends AbstractExpression, D extends AbstractExpressionDto> {
protected final AbstractExpressionService<E, D> expressionService;
@GetMapping("{uuid}")
public D byUuid(@PathVariable("uuid") String uuid) {
return expressionService.getDtoByUuid(uuid);
}
}

View File

@ -0,0 +1,27 @@
package de.ph87.home.expression;
import jakarta.annotation.Nullable;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString
public abstract class AbstractExpressionDto {
@NonNull
private final String uuid;
@NonNull
private final String name;
@Nullable
private final Object value;
protected AbstractExpressionDto(@NonNull final AbstractExpression expression, @Nullable final Object value) {
this.uuid = expression.getUuid();
this.name = expression.getName();
this.value = value;
}
}

View File

@ -0,0 +1,9 @@
package de.ph87.home.expression;
import org.springframework.data.repository.ListCrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
@NoRepositoryBean
public interface AbstractExpressionRepository<E extends AbstractExpression> extends ListCrudRepository<E, String> {
}

View File

@ -0,0 +1,48 @@
package de.ph87.home.expression;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.function.Consumer;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public abstract class AbstractExpressionService<E extends AbstractExpression, D extends AbstractExpressionDto> {
protected final AbstractExpressionRepository<E> repository;
protected final ApplicationEventPublisher applicationEventPublisher;
@NonNull
public D set(@NonNull final String uuid, @NonNull final Consumer<E> setter) {
final E expression = getByUuid(uuid);
setter.accept(expression);
return publish(expression);
}
@NonNull
private E getByUuid(@NonNull final String uuid) {
return repository.findById(uuid).orElseThrow();
}
@NonNull
public D getDtoByUuid(@NonNull final String uuid) {
return toDto(getByUuid(uuid));
}
@NonNull
private D publish(@NonNull final E expression) {
final D dto = toDto(expression);
applicationEventPublisher.publishEvent(dto);
return dto;
}
public abstract D toDto(final E expression);
}

View File

@ -0,0 +1,26 @@
package de.ph87.home.expression;
import de.ph87.home.property.PropertyService;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
@Getter
@ToString
public class Runtime {
public final ZonedDateTime now = ZonedDateTime.now();
public final Map<String, Object> write = new HashMap<>();
public final PropertyService propertyService;
public Runtime(@NonNull final PropertyService propertyService) {
this.propertyService = propertyService;
}
}

View File

@ -0,0 +1,20 @@
package de.ph87.home.expression.expression;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.AbstractExpressionController;
import de.ph87.home.expression.AbstractExpressionDto;
import de.ph87.home.expression.AbstractExpressionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("Expression")
public class ExpressionController extends AbstractExpressionController<AbstractExpression, AbstractExpressionDto> {
public ExpressionController(final AbstractExpressionService<AbstractExpression, AbstractExpressionDto> expressionService) {
super(expressionService);
}
}

View File

@ -0,0 +1,60 @@
package de.ph87.home.expression.expression;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.AbstractExpressionDto;
import de.ph87.home.expression.AbstractExpressionRepository;
import de.ph87.home.expression.AbstractExpressionService;
import de.ph87.home.expression.expression.literal.bool.ExpressionLiteralBoolean;
import de.ph87.home.expression.expression.literal.bool.ExpressionLiteralBooleanService;
import de.ph87.home.expression.expression.literal.number.ExpressionLiteralDouble;
import de.ph87.home.expression.expression.literal.number.ExpressionLiteralDoubleService;
import de.ph87.home.expression.expression.list.ExpressionList;
import de.ph87.home.expression.expression.list.ExpressionListService;
import de.ph87.home.expression.expression.property.ExpressionProperty;
import de.ph87.home.expression.expression.property.ExpressionPropertyService;
import de.ph87.home.expression.expression.unary.ExpressionUnary;
import de.ph87.home.expression.expression.unary.ExpressionUnaryService;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
public class ExpressionMapper extends AbstractExpressionService<AbstractExpression, AbstractExpressionDto> {
private final ExpressionLiteralBooleanService expressionLiteralBooleanService;
private final ExpressionLiteralDoubleService expressionLiteralDoubleService;
private final ExpressionUnaryService expressionUnaryService;
private final ExpressionListService expressionNaryService;
private final ExpressionPropertyService expressionPropertyService;
public ExpressionMapper(final AbstractExpressionRepository<AbstractExpression> repository, final ApplicationEventPublisher applicationEventPublisher, final ExpressionLiteralBooleanService expressionLiteralBooleanService, final ExpressionLiteralDoubleService expressionLiteralDoubleService, final ExpressionUnaryService expressionUnaryService, final ExpressionListService expressionNaryService, final ExpressionPropertyService expressionPropertyService) {
super(repository, applicationEventPublisher);
this.expressionLiteralBooleanService = expressionLiteralBooleanService;
this.expressionLiteralDoubleService = expressionLiteralDoubleService;
this.expressionUnaryService = expressionUnaryService;
this.expressionNaryService = expressionNaryService;
this.expressionPropertyService = expressionPropertyService;
}
@NonNull
@Override
public AbstractExpressionDto toDto(@NonNull final AbstractExpression expression) {
return switch (expression) {
case final ExpressionLiteralBoolean expressionLiteralBoolean -> expressionLiteralBooleanService.toDto(expressionLiteralBoolean);
case final ExpressionLiteralDouble expressionLiteralDouble -> expressionLiteralDoubleService.toDto(expressionLiteralDouble);
case final ExpressionProperty expressionProperty -> expressionPropertyService.toDto(expressionProperty);
case final ExpressionUnary expressionUnary -> expressionUnaryService.toDto(expressionUnary);
case final ExpressionList expressionNary -> expressionNaryService.toDto(expressionNary);
default -> throw new RuntimeException("Expression type not implemented: " + expression.getClass().getSimpleName());
};
}
}

View File

@ -0,0 +1,8 @@
package de.ph87.home.expression.expression;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.AbstractExpressionRepository;
public interface ExpressionRepository extends AbstractExpressionRepository<AbstractExpression> {
}

View File

@ -0,0 +1,60 @@
package de.ph87.home.expression.expression;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.AbstractExpressionDto;
import de.ph87.home.expression.AbstractExpressionRepository;
import de.ph87.home.expression.AbstractExpressionService;
import de.ph87.home.expression.expression.literal.bool.ExpressionLiteralBoolean;
import de.ph87.home.expression.expression.literal.bool.ExpressionLiteralBooleanService;
import de.ph87.home.expression.expression.literal.number.ExpressionLiteralDouble;
import de.ph87.home.expression.expression.literal.number.ExpressionLiteralDoubleService;
import de.ph87.home.expression.expression.list.ExpressionList;
import de.ph87.home.expression.expression.list.ExpressionListService;
import de.ph87.home.expression.expression.property.ExpressionProperty;
import de.ph87.home.expression.expression.property.ExpressionPropertyService;
import de.ph87.home.expression.expression.unary.ExpressionUnary;
import de.ph87.home.expression.expression.unary.ExpressionUnaryService;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
public class ExpressionService extends AbstractExpressionService<AbstractExpression, AbstractExpressionDto> {
private final ExpressionLiteralBooleanService expressionLiteralBooleanService;
private final ExpressionLiteralDoubleService expressionLiteralDoubleService;
private final ExpressionUnaryService expressionUnaryService;
private final ExpressionListService expressionNaryService;
private final ExpressionPropertyService expressionPropertyService;
public ExpressionService(final AbstractExpressionRepository<AbstractExpression> repository, final ApplicationEventPublisher applicationEventPublisher, final ExpressionLiteralBooleanService expressionLiteralBooleanService, final ExpressionLiteralDoubleService expressionLiteralDoubleService, final ExpressionUnaryService expressionUnaryService, final ExpressionListService expressionNaryService, final ExpressionPropertyService expressionPropertyService) {
super(repository, applicationEventPublisher);
this.expressionLiteralBooleanService = expressionLiteralBooleanService;
this.expressionLiteralDoubleService = expressionLiteralDoubleService;
this.expressionUnaryService = expressionUnaryService;
this.expressionNaryService = expressionNaryService;
this.expressionPropertyService = expressionPropertyService;
}
@NonNull
@Override
public AbstractExpressionDto toDto(@NonNull final AbstractExpression expression) {
return switch (expression) {
case final ExpressionLiteralBoolean expressionLiteralBoolean -> expressionLiteralBooleanService.toDto(expressionLiteralBoolean);
case final ExpressionLiteralDouble expressionLiteralDouble -> expressionLiteralDoubleService.toDto(expressionLiteralDouble);
case final ExpressionProperty expressionProperty -> expressionPropertyService.toDto(expressionProperty);
case final ExpressionUnary expressionUnary -> expressionUnaryService.toDto(expressionUnary);
case final ExpressionList expressionNary -> expressionNaryService.toDto(expressionNary);
default -> throw new RuntimeException("Expression type not implemented: " + expression.getClass().getSimpleName());
};
}
}

View File

@ -0,0 +1,42 @@
package de.ph87.home.expression.expression.list;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.Runtime;
import jakarta.persistence.*;
import lombok.*;
import java.util.ArrayList;
import java.util.List;
@Setter
@Getter
@Entity
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ExpressionList extends AbstractExpression {
@NonNull
@Enumerated(EnumType.STRING)
private ExpressionListOperator operator = ExpressionListOperator.OR;
@NonNull
@ManyToMany
@OrderColumn
private List<AbstractExpression> list = new ArrayList<>();
@Override
public Object evaluate(@NonNull final Runtime runtime) {
if (list.size() < 2) {
return null;
}
final List<Object> valueList = list.stream().map(e -> evalOrNull(runtime, e)).toList();
Object vLast = valueList.removeFirst();
while (!valueList.isEmpty()) {
final Object vNext = evalOrNull(runtime, list.removeFirst());
vLast = operator.apply(vLast, vNext);
}
return vLast;
}
}

View File

@ -0,0 +1,29 @@
package de.ph87.home.expression.expression.list;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.AbstractExpressionDto;
import jakarta.annotation.Nullable;
import lombok.*;
import java.util.List;
import java.util.stream.Collectors;
@Setter
@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ExpressionListDto extends AbstractExpressionDto {
@NonNull
private final ExpressionListOperator operator;
@NonNull
private final List<String> expressionUuidList;
public ExpressionListDto(@NonNull final ExpressionList expression, @Nullable final Object value) {
super(expression, value);
this.operator = expression.getOperator();
this.expressionUuidList = expression.getList().stream().map(AbstractExpression::getUuid).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,39 @@
package de.ph87.home.expression.expression.list;
import de.ph87.home.expression.AbstractExpression;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import java.util.function.BiFunction;
import static de.ph87.home.expression.AbstractExpression.applyFallback;
import static de.ph87.home.expression.AbstractExpression.applyNullToNull;
public enum ExpressionListOperator {
OR((v0, v1) -> applyFallback(v0, v1, AbstractExpression::convertToBoolean, false, (c0, c1) -> c0 || c1)),
AND((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToBoolean, (c0, c1) -> c0 && c1)),
XOR((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToBoolean, (c0, c1) -> c0 ^ c1)),
ADD((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToNumber, Double::sum)),
SUB((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToNumber, (c0, c1) -> c0 - c1)),
MUL((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToNumber, (c0, c1) -> c0 * c1)),
DIV((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToNumber, (c0, c1) -> c0 / c1)),
MOD((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToNumber, (c0, c1) -> c0 % c1)),
POW((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToNumber, Math::pow)),
HYPOT((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToNumber, Math::hypot)),
ATAN2((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToNumber, Math::atan2)),
MIN((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToNumber, Math::min)),
MAX((v0, v1) -> applyNullToNull(v0, v1, AbstractExpression::convertToNumber, Math::max)),
;
private final BiFunction<Object, Object, Object> function;
ExpressionListOperator(@NonNull final BiFunction<Object, Object, Object> function) {
this.function = function;
}
@Nullable
public Object apply(@Nullable final Object v0, @Nullable final Object v1) {
return function.apply(v0, v1);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.expression.expression.list;
import de.ph87.home.expression.AbstractExpressionRepository;
public interface ExpressionListRepository extends AbstractExpressionRepository<ExpressionList> {
}

View File

@ -0,0 +1,28 @@
package de.ph87.home.expression.expression.list;
import de.ph87.home.expression.AbstractExpressionService;
import de.ph87.home.expression.Runtime;
import de.ph87.home.property.PropertyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
public class ExpressionListService extends AbstractExpressionService<ExpressionList, ExpressionListDto> {
private final PropertyService propertyService;
public ExpressionListService(final ExpressionListRepository repository, final ApplicationEventPublisher applicationEventPublisher, final PropertyService propertyService) {
super(repository, applicationEventPublisher);
this.propertyService = propertyService;
}
@Override
public ExpressionListDto toDto(final ExpressionList expression) {
return new ExpressionListDto(expression, expression.evaluate(new Runtime(propertyService)));
}
}

View File

@ -0,0 +1,32 @@
package de.ph87.home.expression.expression.literal;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.Runtime;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import lombok.*;
@Setter
@Getter
@MappedSuperclass
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public abstract class ExpressionLiteral<T> extends AbstractExpression {
@Nullable
@Column(name = "`value`")
private T value = null;
public ExpressionLiteral(final T value) {
this.value = value;
}
@Nullable
@Override
public Object evaluate(@NonNull final Runtime runtime) {
return value;
}
}

View File

@ -0,0 +1,23 @@
package de.ph87.home.expression.expression.literal;
import de.ph87.home.expression.AbstractExpressionController;
import de.ph87.home.expression.AbstractExpressionDto;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
public abstract class ExpressionLiteralController<T, E extends ExpressionLiteral<T>, D extends AbstractExpressionDto> extends AbstractExpressionController<E, D> {
public ExpressionLiteralController(final ExpressionLiteralService<T, E, D> expressionService) {
super(expressionService);
}
@NonNull
@PostMapping("{uuid}/setValue")
public D setValue(@PathVariable final String uuid, @RequestBody(required = false) @Nullable final T value) {
return expressionService.set(uuid, e -> e.setValue(value));
}
}

View File

@ -0,0 +1,20 @@
package de.ph87.home.expression.expression.literal;
import de.ph87.home.expression.AbstractExpressionDto;
import de.ph87.home.expression.AbstractExpressionRepository;
import de.ph87.home.expression.AbstractExpressionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
public abstract class ExpressionLiteralService<T, E extends ExpressionLiteral<T>, D extends AbstractExpressionDto> extends AbstractExpressionService<E, D> {
public ExpressionLiteralService(final AbstractExpressionRepository<E> repository, final ApplicationEventPublisher applicationEventPublisher) {
super(repository, applicationEventPublisher);
}
}

View File

@ -0,0 +1,20 @@
package de.ph87.home.expression.expression.literal.bool;
import de.ph87.home.expression.expression.literal.ExpressionLiteral;
import jakarta.annotation.Nullable;
import jakarta.persistence.Entity;
import lombok.*;
@Setter
@Getter
@Entity
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ExpressionLiteralBoolean extends ExpressionLiteral<Boolean> {
public ExpressionLiteralBoolean(@Nullable final Boolean value) {
super(value);
}
}

View File

@ -0,0 +1,16 @@
package de.ph87.home.expression.expression.literal.bool;
import de.ph87.home.expression.expression.literal.ExpressionLiteralController;
import de.ph87.home.expression.expression.literal.ExpressionLiteralService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("ExpressionLiteralBoolean")
public class ExpressionLiteralBooleanController extends ExpressionLiteralController<Boolean, ExpressionLiteralBoolean, ExpressionLiteralBooleanDto> {
public ExpressionLiteralBooleanController(final ExpressionLiteralService<Boolean, ExpressionLiteralBoolean, ExpressionLiteralBooleanDto> expressionLiteralService) {
super(expressionLiteralService);
}
}

View File

@ -0,0 +1,18 @@
package de.ph87.home.expression.expression.literal.bool;
import de.ph87.home.expression.AbstractExpressionDto;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ExpressionLiteralBooleanDto extends AbstractExpressionDto {
protected ExpressionLiteralBooleanDto(@NonNull final ExpressionLiteralBoolean expression) {
super(expression, expression.getValue());
}
}

View File

@ -0,0 +1,9 @@
package de.ph87.home.expression.expression.literal.bool;
import de.ph87.home.expression.AbstractExpressionRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ExpressionLiteralBooleanRepository extends AbstractExpressionRepository<ExpressionLiteralBoolean> {
}

View File

@ -0,0 +1,23 @@
package de.ph87.home.expression.expression.literal.bool;
import de.ph87.home.expression.expression.literal.ExpressionLiteralService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
public class ExpressionLiteralBooleanService extends ExpressionLiteralService<Boolean, ExpressionLiteralBoolean, ExpressionLiteralBooleanDto> {
public ExpressionLiteralBooleanService(final ExpressionLiteralBooleanRepository repository, final ApplicationEventPublisher applicationEventPublisher) {
super(repository, applicationEventPublisher);
}
@Override
public ExpressionLiteralBooleanDto toDto(final ExpressionLiteralBoolean expression) {
return new ExpressionLiteralBooleanDto(expression);
}
}

View File

@ -0,0 +1,20 @@
package de.ph87.home.expression.expression.literal.number;
import de.ph87.home.expression.expression.literal.ExpressionLiteral;
import jakarta.annotation.Nullable;
import jakarta.persistence.Entity;
import lombok.*;
@Setter
@Getter
@Entity
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ExpressionLiteralDouble extends ExpressionLiteral<Double> {
public ExpressionLiteralDouble(@Nullable final Double value) {
super(value);
}
}

View File

@ -0,0 +1,16 @@
package de.ph87.home.expression.expression.literal.number;
import de.ph87.home.expression.expression.literal.ExpressionLiteralController;
import de.ph87.home.expression.expression.literal.ExpressionLiteralService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("ExpressionLiteralDouble")
public class ExpressionLiteralDoubleController extends ExpressionLiteralController<Double, ExpressionLiteralDouble, ExpressionLiteralDoubleDto> {
public ExpressionLiteralDoubleController(final ExpressionLiteralService<Double, ExpressionLiteralDouble, ExpressionLiteralDoubleDto> expressionLiteralService) {
super(expressionLiteralService);
}
}

View File

@ -0,0 +1,18 @@
package de.ph87.home.expression.expression.literal.number;
import de.ph87.home.expression.AbstractExpressionDto;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ExpressionLiteralDoubleDto extends AbstractExpressionDto {
protected ExpressionLiteralDoubleDto(@NonNull final ExpressionLiteralDouble expression) {
super(expression, expression.getValue());
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.expression.expression.literal.number;
import de.ph87.home.expression.AbstractExpressionRepository;
public interface ExpressionLiteralDoubleRepository extends AbstractExpressionRepository<ExpressionLiteralDouble> {
}

View File

@ -0,0 +1,23 @@
package de.ph87.home.expression.expression.literal.number;
import de.ph87.home.expression.expression.literal.ExpressionLiteralService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
public class ExpressionLiteralDoubleService extends ExpressionLiteralService<Double, ExpressionLiteralDouble, ExpressionLiteralDoubleDto> {
public ExpressionLiteralDoubleService(final ExpressionLiteralDoubleRepository repository, final ApplicationEventPublisher applicationEventPublisher) {
super(repository, applicationEventPublisher);
}
@Override
public ExpressionLiteralDoubleDto toDto(final ExpressionLiteralDouble expression) {
return new ExpressionLiteralDoubleDto(expression);
}
}

View File

@ -0,0 +1,28 @@
package de.ph87.home.expression.expression.property;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.Runtime;
import de.ph87.home.property.Property;
import de.ph87.home.property.State;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import lombok.*;
@Setter
@Getter
@Entity
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ExpressionProperty extends AbstractExpression {
@NonNull
@Column(nullable = false)
private String propertyId;
@Override
public Object evaluate(@NonNull final Runtime runtime) {
return runtime.propertyService.findById(propertyId).map(Property::getState).map(State::getValue).orElse(null);
}
}

View File

@ -0,0 +1,29 @@
package de.ph87.home.expression.expression.property;
import de.ph87.home.expression.AbstractExpressionDto;
import de.ph87.home.property.IProperty;
import de.ph87.home.property.PropertyDto;
import jakarta.annotation.Nullable;
import lombok.*;
import static de.ph87.home.expression.AbstractExpression.mapOrElse;
@Setter
@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ExpressionPropertyDto extends AbstractExpressionDto {
@NonNull
private final String propertyId;
@Nullable
private final PropertyDto<?> property;
public ExpressionPropertyDto(@NonNull final ExpressionProperty expressionProperty, @Nullable final PropertyDto<?> property) {
super(expressionProperty, mapOrElse(property, IProperty::getStateValue, null));
this.propertyId = expressionProperty.getPropertyId();
this.property = property;
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.expression.expression.property;
import de.ph87.home.expression.AbstractExpressionRepository;
public interface ExpressionPropertyRepository extends AbstractExpressionRepository<ExpressionProperty> {
}

View File

@ -0,0 +1,29 @@
package de.ph87.home.expression.expression.property;
import de.ph87.home.expression.AbstractExpressionService;
import de.ph87.home.property.PropertyDto;
import de.ph87.home.property.PropertyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
public class ExpressionPropertyService extends AbstractExpressionService<ExpressionProperty, ExpressionPropertyDto> {
private final PropertyService propertyService;
public ExpressionPropertyService(final ExpressionPropertyRepository repository, final ApplicationEventPublisher applicationEventPublisher, final PropertyService propertyService) {
super(repository, applicationEventPublisher);
this.propertyService = propertyService;
}
@Override
public ExpressionPropertyDto toDto(final ExpressionProperty expressionProperty) {
final PropertyDto<?> property = propertyService.dtoByIdOrNull(expressionProperty.getPropertyId());
return new ExpressionPropertyDto(expressionProperty, property);
}
}

View File

@ -0,0 +1,31 @@
package de.ph87.home.expression.expression.unary;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.Runtime;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import lombok.*;
@Setter
@Getter
@Entity
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ExpressionUnary extends AbstractExpression {
@NonNull
@Enumerated(EnumType.STRING)
private ExpressionUnaryOperator operator = ExpressionUnaryOperator.NOT;
@Nullable
@ManyToOne(fetch = FetchType.LAZY)
private AbstractExpression expression;
@Override
public Object evaluate(@NonNull final Runtime runtime) {
final Object value = evalOrNull(runtime, expression);
return operator.apply(value);
}
}

View File

@ -0,0 +1,28 @@
package de.ph87.home.expression.expression.unary;
import de.ph87.home.expression.AbstractExpression;
import de.ph87.home.expression.AbstractExpressionDto;
import jakarta.annotation.Nullable;
import lombok.*;
import static de.ph87.home.expression.AbstractExpression.mapOrElse;
@Setter
@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ExpressionUnaryDto extends AbstractExpressionDto {
@NonNull
private final ExpressionUnaryOperator operator;
@Nullable
private final String expressionUuid;
public ExpressionUnaryDto(@NonNull final ExpressionUnary expressionUnary, @Nullable final Object value) {
super(expressionUnary, value);
this.operator = expressionUnary.getOperator();
this.expressionUuid = mapOrElse(expressionUnary.getExpression(), AbstractExpression::getUuid, null);
}
}

View File

@ -0,0 +1,42 @@
package de.ph87.home.expression.expression.unary;
import de.ph87.home.expression.AbstractExpression;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import java.util.function.Function;
public enum ExpressionUnaryOperator {
NOT(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToBoolean, c -> !c)),
NEG(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, c -> -c)),
POS(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, c -> +c)),
FLOOR(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::floor)),
CEIL(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::ceil)),
ROUND(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::round)),
SQRT(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::sqrt)),
ABS(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::abs)),
COS(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::cos)),
SIN(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::sin)),
TAN(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::tan)),
ACOS(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::acos)),
ASIN(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::asin)),
ATAN(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::atan)),
COSH(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::cosh)),
SINH(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::sinh)),
TANH(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::tanh)),
EXP(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::exp)),
EXPM1(v -> AbstractExpression.applyNullToNull(v, AbstractExpression::convertToNumber, Math::expm1)),
;
private final Function<Object, Object> function;
ExpressionUnaryOperator(@NonNull final Function<Object, Object> function) {
this.function = function;
}
@Nullable
public Object apply(@Nullable final Object value) {
return function.apply(value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.expression.expression.unary;
import de.ph87.home.expression.AbstractExpressionRepository;
public interface ExpressionUnaryRepository extends AbstractExpressionRepository<ExpressionUnary> {
}

View File

@ -0,0 +1,28 @@
package de.ph87.home.expression.expression.unary;
import de.ph87.home.expression.AbstractExpressionService;
import de.ph87.home.expression.Runtime;
import de.ph87.home.property.PropertyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
public class ExpressionUnaryService extends AbstractExpressionService<ExpressionUnary, ExpressionUnaryDto> {
private final PropertyService propertyService;
public ExpressionUnaryService(final ExpressionUnaryRepository repository, final ApplicationEventPublisher applicationEventPublisher, final PropertyService propertyService) {
super(repository, applicationEventPublisher);
this.propertyService = propertyService;
}
@Override
public ExpressionUnaryDto toDto(final ExpressionUnary expression) {
return new ExpressionUnaryDto(expression, expression.evaluate(new Runtime(propertyService)));
}
}

View File

@ -89,7 +89,7 @@ public class PropertyService {
} }
@NonNull @NonNull
private Optional<Property<?>> findById(final String id) { public Optional<Property<?>> findById(final String id) {
return propertyList.stream().filter(p -> p.getId().equals(id)).findFirst(); return propertyList.stream().filter(p -> p.getId().equals(id)).findFirst();
} }
@ -120,4 +120,9 @@ public class PropertyService {
} }
} }
@Nullable
public PropertyDto<?> dtoByIdOrNull(@NonNull final String propertyId) {
return findById(propertyId).map(this::toDto).orElse(null);
}
} }