Merge legacy dataforge and migrate to git

This commit is contained in:
Alexander Nozik 2021-05-29 13:45:33 +03:00
commit c4164f2681
1087 changed files with 117263 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
build/
.gradle/
.idea/
out/

9
README.md Normal file
View File

@ -0,0 +1,9 @@
# Purpose #
This repository contains tools and utilities for [Trotsk nu-mass](http://www.inr.ru/~trdat/) experiment.
# Set-up #
This project build using [DataForge](http://www.inr.ru/~nozik/dataforge/) framework. Currently in order to compile numass tools, one need to download dataforge gradle project [here](https://bitbucket.org/Altavir/dataforge). If both projects (numass and dataforge) are in the same directory, everything will work out of the box, otherwise, one needs to edit `gradle.properties` file in the root of numass project and set `dataforgePath` to the relative path of dataforge directory.
It is intended to fix this problem with public maven repository later.

65
build.gradle Normal file
View File

@ -0,0 +1,65 @@
buildscript {
ext.kotlin_version = "1.4.30"
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
plugins{
id 'org.openjfx.javafxplugin' version '0.0.9' apply false
}
allprojects {
apply plugin: 'java'
apply plugin: "org.jetbrains.kotlin.jvm"
group = 'inr.numass'
version = '1.0.0'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
repositories {
mavenCentral()
jcenter()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile 'org.jetbrains:annotations:16.0.2'
testImplementation group: 'junit', name: 'junit', version: '4.+'
//Spock dependencies. To be removed later
testCompile 'org.codehaus.groovy:groovy-all:2.5.+'
testCompile "org.spockframework:spock-core:1.2-groovy-2.5"
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
javaParameters = true
freeCompilerArgs += [
'-Xjvm-default=enable',
"-progressive",
"-Xuse-experimental=kotlin.Experimental"
]
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
javaParameters = true
freeCompilerArgs += [
'-Xjvm-default=enable',
"-progressive",
"-Xuse-experimental=kotlin.Experimental"
]
}
}
}

View File

@ -0,0 +1,10 @@
description = 'dataforge-control'
dependencies {
// Adding dependencies here will add the dependencies to each subproject.
compile project(':dataforge-core')
//TODO consider removing storage dependency
compile project(':dataforge-storage')
compile 'org.scream3r:jssc:2.8.0'
}

Binary file not shown.

View File

@ -0,0 +1,22 @@
package hep.dataforge.control;
import hep.dataforge.control.devices.Device;
import hep.dataforge.io.envelopes.Envelope;
import hep.dataforge.meta.Meta;
/**
* Created by darksnake on 11-Oct-16.
*/
public class ControlUtils {
public static String getDeviceType(Meta meta){
return meta.getString("type");
}
public static String getDeviceName(Meta meta){
return meta.getString("name","");
}
public static Envelope getDefaultDeviceResponse(Device device, Envelope request){
throw new UnsupportedOperationException("Not implemented");
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.control.collectors;
import hep.dataforge.tables.ValuesListener;
import hep.dataforge.utils.DateTimeUtils;
import hep.dataforge.values.Value;
import hep.dataforge.values.ValueFactory;
import hep.dataforge.values.ValueMap;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* A class to dynamically collect measurements from multi-channel devices and
* bundle them into DataPoints. The collect method is called when all values are
* present.
*
* @author Alexander Nozik
*/
public class PointCollector implements ValueCollector {
private final List<String> names;
private final ValuesListener consumer;
private final Map<String, Value> valueMap = new ConcurrentHashMap<>();
//TODO make time averaging?
public PointCollector(ValuesListener consumer, Collection<String> names) {
this.names = new ArrayList<>(names);
this.consumer = consumer;
}
public PointCollector(ValuesListener consumer, String... names) {
this.names = Arrays.asList(names);
this.consumer = consumer;
}
@Override
public void put(String name, Value value) {
valueMap.put(name, value);
if (valueMap.keySet().containsAll(names)) {
collect();
}
}
@Override
public void put(String name, Object value) {
valueMap.put(name, ValueFactory.of(value));
if (valueMap.keySet().containsAll(names)) {
collect();
}
}
/**
* Could be used to force collect even if not all values are present
*/
@Override
public void collect() {
collect(DateTimeUtils.now());
}
public synchronized void collect(Instant time) {
ValueMap.Builder point = new ValueMap.Builder();
point.putValue("timestamp", time);
valueMap.entrySet().forEach((entry) -> {
point.putValue(entry.getKey(), entry.getValue());
});
// filling all missing values with nulls
names.stream().filter((name) -> (!point.build().hasValue(name))).forEach((name) -> {
point.putValue(name, ValueFactory.NULL);
});
consumer.accept(point.build());
valueMap.clear();
}
@Override
public void clear() {
valueMap.clear();
}
}

View File

@ -0,0 +1,117 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.collectors;
import hep.dataforge.utils.DateTimeUtils;
import hep.dataforge.values.Value;
import hep.dataforge.values.ValueFactory;
import hep.dataforge.values.ValueMap;
import hep.dataforge.values.Values;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
/**
* An averaging DataPoint collector that starts timer on first put operation and
* forces collection when timer expires. If there are few Values with same time
* during this period, they are averaged.
*
* @author <a href="mailto:altavir@gmail.com">Alexander Nozik</a>
*/
public class RegularPointCollector implements ValueCollector {
private final Map<String, List<Value>> values = new ConcurrentHashMap<>();
private final Consumer<Values> consumer;
private final Duration duration;
private Instant startTime;
/**
* The names that must be in the dataPoint
*/
private List<String> names = new ArrayList<>();
private Timer timer;
public RegularPointCollector(Duration duration, Consumer<Values> consumer) {
this.consumer = consumer;
this.duration = duration;
}
public RegularPointCollector(Duration duration, Collection<String> names, Consumer<Values> consumer) {
this(duration, consumer);
this.names = new ArrayList<>(names);
}
@Override
public void collect() {
collect(DateTimeUtils.now());
}
public synchronized void collect(Instant time) {
if(!values.isEmpty()) {
ValueMap.Builder point = new ValueMap.Builder();
Instant average = Instant.ofEpochMilli((time.toEpochMilli() + startTime.toEpochMilli()) / 2);
point.putValue("timestamp", average);
for (Map.Entry<String, List<Value>> entry : values.entrySet()) {
point.putValue(entry.getKey(), entry.getValue().stream().mapToDouble(Value::getDouble).sum() / entry.getValue().size());
}
// filling all missing values with nulls
for (String name : names) {
if (!point.build().hasValue(name)) {
point.putValue(name, ValueFactory.NULL);
}
}
startTime = null;
values.clear();
consumer.accept(point.build());
}
}
@Override
public synchronized void put(String name, Value value) {
if (startTime == null) {
startTime = DateTimeUtils.now();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
collect();
}
}, duration.toMillis());
}
if (!values.containsKey(name)) {
values.put(name, new ArrayList<>());
}
values.get(name).add(value);
}
private void cancel() {
if (timer != null && startTime != null) {
timer.cancel();
}
}
@Override
public void clear() {
values.clear();
}
public void stop() {
cancel();
clear();
startTime = null;
}
}

View File

@ -0,0 +1,36 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.collectors;
import hep.dataforge.values.Value;
import hep.dataforge.values.ValueFactory;
/**
* A collector of values which listens to some input values until condition
* satisfied then pushes the result to external listener.
*
* @author <a href="mailto:altavir@gmail.com">Alexander Nozik</a>
*/
public interface ValueCollector {
void put(String name, Value value);
default void put(String name, Object value) {
put(name, ValueFactory.of(value));
}
/**
* Send current cached result to listener. Could be used to force collect
* even if not all values are present.
*/
void collect();
/**
* Clear currently collected data
*/
void clear();
}

View File

@ -0,0 +1,157 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.measurements;
import hep.dataforge.exceptions.MeasurementException;
import hep.dataforge.utils.DateTimeUtils;
import kotlin.Pair;
import org.slf4j.LoggerFactory;
import java.time.Instant;
import java.util.function.Consumer;
/**
* A boilerplate code for measurements
*
* @author Alexander Nozik
*/
@Deprecated
public abstract class AbstractMeasurement<T> implements Measurement<T> {
// protected final ReferenceRegistry<MeasurementListener<T>> listeners = new ReferenceRegistry<>();
protected Pair<T, Instant> lastResult;
protected Throwable exception;
private MeasurementState state;
protected MeasurementState getMeasurementState() {
return state;
}
protected void setMeasurementState(MeasurementState state) {
this.state = state;
}
/**
* Call after measurement started
*/
protected void afterStart() {
setMeasurementState(MeasurementState.PENDING);
notifyListeners(it -> it.onMeasurementStarted(this));
}
/**
* Call after measurement stopped
*/
protected void afterStop() {
setMeasurementState(MeasurementState.FINISHED);
notifyListeners(it -> it.onMeasurementFinished(this));
}
/**
* Reset measurement to initial state
*/
protected void afterPause() {
setMeasurementState(MeasurementState.OK);
notifyListeners(it -> it.onMeasurementFinished(this));
}
protected synchronized void onError(String message, Throwable error) {
LoggerFactory.getLogger(getClass()).error("Measurement failed with error: " + message, error);
setMeasurementState(MeasurementState.FAILED);
this.exception = error;
notify();
notifyListeners(it -> it.onMeasurementFailed(this, error));
}
/**
* Internal method to notify measurement complete. Uses current system time
*
* @param result
*/
protected final void result(T result) {
result(result, DateTimeUtils.now());
}
/**
* Internal method to notify measurement complete
*
* @param result
*/
protected synchronized void result(T result, Instant time) {
this.lastResult = new Pair<>(result, time);
setMeasurementState(MeasurementState.OK);
notify();
notifyListeners(it -> it.onMeasurementResult(this, result, time));
}
protected void updateProgress(double progress) {
notifyListeners(it -> it.onMeasurementProgress(this, progress));
}
protected void updateMessage(String message) {
notifyListeners(it -> it.onMeasurementProgress(this, message));
}
protected final void notifyListeners(Consumer<MeasurementListener> consumer) {
getDevice().forEachConnection(MeasurementListener.class, consumer);
}
@Override
public boolean isFinished() {
return state == MeasurementState.FINISHED;
}
@Override
public boolean isStarted() {
return state == MeasurementState.PENDING || state == MeasurementState.OK;
}
@Override
public Throwable getError() {
return this.exception;
}
protected synchronized Pair<T, Instant> get() throws MeasurementException {
if (getMeasurementState() == MeasurementState.INIT) {
start();
LoggerFactory.getLogger(getClass()).debug("Measurement not started. Starting");
}
while (state == MeasurementState.PENDING) {
try {
//Wait for result could cause deadlock if called in main thread
wait();
} catch (InterruptedException ex) {
throw new MeasurementException(ex);
}
}
if (this.lastResult != null) {
return this.lastResult;
} else if (state == MeasurementState.FAILED) {
throw new MeasurementException(getError());
} else {
throw new MeasurementException("Measurement failed for unknown reason");
}
}
@Override
public Instant getTime() throws MeasurementException {
return get().getSecond();
}
@Override
public T getResult() throws MeasurementException {
return get().getFirst();
}
protected enum MeasurementState {
INIT, //Measurement not started
PENDING, // Measurement in process
OK, // Last measurement complete, next is planned
FAILED, // Last measurement failed
FINISHED, // Measurement finished or stopped
}
}

View File

@ -0,0 +1,74 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.measurements;
import hep.dataforge.control.devices.Device;
import hep.dataforge.exceptions.MeasurementException;
import java.time.Instant;
/**
* A general representation of ongoing or completed measurement. Could be
* regular.
*
* @author Alexander Nozik
*/
@Deprecated
public interface Measurement<T> {
Device getDevice();
/**
* Begin the measurement
*/
void start();
/**
* Stop the measurement
*
* @param force force stop if measurement in progress
* @throws MeasurementException
*/
boolean stop(boolean force) throws MeasurementException;
/**
* Measurement is started
*
* @return
*/
boolean isStarted();
/**
* Measurement is complete or stopped and could be recycled
*
* @return
*/
boolean isFinished();
/**
* Get the time of the last measurement
*
* @return
* @throws MeasurementException
*/
Instant getTime() throws MeasurementException;
/**
* Get last measurement result or wait for measurement to complete and
* return its result. Synchronous call.
*
* @return
* @throws MeasurementException
*/
T getResult() throws MeasurementException;
/**
* Last thrown exception. Null if no exceptions are thrown
*
* @return
*/
Throwable getError();
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.control.measurements;
import hep.dataforge.exceptions.MeasurementException;
import java.time.Instant;
/**
* A listener for device measurements
*
* @author Alexander Nozik
*/
@Deprecated
public interface MeasurementListener {
/**
* Measurement started. Ignored by default
* @param measurement
*/
default void onMeasurementStarted(Measurement<?> measurement){
}
/**
* Measurement stopped. Ignored by default
* @param measurement
*/
default void onMeasurementFinished(Measurement<?> measurement){
}
/**
* Measurement result obtained
* @param measurement
* @param result
*/
void onMeasurementResult(Measurement<?> measurement, Object result, Instant time);
/**
* Measurement failed with exception
* @param measurement
* @param exception
*/
void onMeasurementFailed(Measurement<?> measurement, Throwable exception);
/**
* Measurement failed with message
* @param measurement
* @param message
*/
default void onMeasurementFailed(Measurement<?> measurement, String message){
onMeasurementFailed(measurement, new MeasurementException(message));
}
/**
* Measurement progress updated. Ignored by default
* @param measurement
* @param progress
*/
default void onMeasurementProgress(Measurement<?> measurement, double progress) {
}
/**
* Measurement progress message updated. Ignored by default
* @param measurement
* @param message
*/
default void onMeasurementProgress(Measurement<?> measurement, String message) {
}
}

View File

@ -0,0 +1,46 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.measurements;
import java.time.Duration;
/**
*
* @author Alexander Nozik
*/
@Deprecated
public abstract class RegularMeasurement<T> extends SimpleMeasurement<T> {
private boolean stopFlag = false;
@Override
protected void finishTask() {
if (stopFlag || (stopOnError() && getMeasurementState() == MeasurementState.FAILED)) {
afterStop();
} else {
startTask();
}
}
@Override
public boolean stop(boolean force) {
if (isFinished()) {
return false;
} else if (force) {
return interruptTask(force);
} else {
stopFlag = true;
return true;
}
}
protected boolean stopOnError() {
return true;
}
protected abstract Duration getDelay();
}

View File

@ -0,0 +1,142 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.measurements;
import hep.dataforge.exceptions.MeasurementException;
import hep.dataforge.utils.DateTimeUtils;
import kotlin.Pair;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* A simple one-time measurement wrapping FutureTask. Could be restarted
*
* @author Alexander Nozik
*/
@Deprecated
public abstract class SimpleMeasurement<T> extends AbstractMeasurement<T> {
private FutureTask<Pair<T, Instant>> task;
/**
* invalidate current task. New task will be created on next getFuture call.
* This method does not guarantee that task is finished when it is cleared
*/
private void clearTask() {
task = null;
}
/**
* Perform synchronous measurement
*
* @return
* @throws Exception
*/
protected abstract T doMeasure() throws Exception;
@Override
public synchronized void start() {
//PENDING do we need executor here?
//Executors.newSingleThreadExecutor().submit(getTask());
if (!isStarted()) {
afterStart();
startTask();
} else {
LoggerFactory.getLogger(getClass()).warn("Alredy started");
}
}
@Override
public synchronized boolean stop(boolean force) {
if (isStarted()) {
afterStop();
return interruptTask(force);
} else {
return false;
}
}
protected boolean interruptTask(boolean force) {
if (task != null) {
if (task.isCancelled() || task.isDone()) {
task = null;
return true;
} else {
return task.cancel(force);
}
} else {
return false;
}
}
protected ThreadGroup getThreadGroup() {
return null;
}
protected Duration getMeasurementTimeout() {
return null;
}
protected String getThreadName() {
return "measurement thread";
}
protected void startTask() {
Runnable process = () -> {
Pair<T, Instant> res;
try {
Duration timeout = getMeasurementTimeout();
task = buildTask();
task.run();
if (timeout == null) {
res = task.get();
} else {
res = task.get(getMeasurementTimeout().toMillis(), TimeUnit.MILLISECONDS);
}
if (res != null) {
result(res.getFirst(), res.getSecond());
} else {
throw new MeasurementException("Empty result");
}
} catch (Exception ex) {
onError("failed to start measurement task", ex);
}
clearTask();
finishTask();
};
new Thread(getThreadGroup(), process, getThreadName()).start();
}
/**
* Reset measurement task and notify listeners
*/
protected void finishTask() {
afterStop();
}
private FutureTask<Pair<T, Instant>> buildTask() {
return new FutureTask<>(() -> {
try {
T res = doMeasure();
if (res == null) {
return null;
}
Instant time = DateTimeUtils.now();
return new Pair<>(res, time);
} catch (Exception ex) {
onError("failed to report measurement results", ex);
return null;
}
});
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
*
* @author Alexander Nozik
*/
public class ControlException extends Exception {
/**
* Creates a new instance of <code>ControlException</code> without detail
* message.
*/
public ControlException() {
}
/**
* Constructs an instance of <code>ControlException</code> with the
* specified detail message.
*
* @param msg the detail message.
*/
public ControlException(String msg) {
super(msg);
}
public ControlException(String message, Throwable cause) {
super(message, cause);
}
public ControlException(Throwable cause) {
super(cause);
}
protected ControlException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
*
* @author Alexander Nozik
*/
public class MeasurementException extends ControlException {
/**
* Creates a new instance of <code>MeasurementException</code> without
* detail message.
*/
public MeasurementException() {
}
/**
* Constructs an instance of <code>MeasurementException</code> with the
* specified detail message.
*
* @param msg the detail message.
*/
public MeasurementException(String msg) {
super(msg);
}
public MeasurementException(String message, Throwable cause) {
super(message, cause);
}
public MeasurementException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
*
* @author Alexander Nozik
*/
public class MeasurementInterruptedException extends MeasurementException {
/**
* Creates a new instance of <code>MeasurementInterruptedException</code>
* without detail message.
*/
public MeasurementInterruptedException() {
}
/**
* Constructs an instance of <code>MeasurementInterruptedException</code>
* with the specified detail message.
*
* @param msg the detail message.
*/
public MeasurementInterruptedException(String msg) {
super(msg);
}
public MeasurementInterruptedException(String message, Throwable cause) {
super(message, cause);
}
public MeasurementInterruptedException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
*
* @author Alexander Nozik
*/
public class MeasurementNotReadyException extends MeasurementException {
/**
* Creates a new instance of <code>MeasurementNotReadyException</code>
* without detail message.
*/
public MeasurementNotReadyException() {
}
/**
* Constructs an instance of <code>MeasurementNotReadyException</code> with
* the specified detail message.
*
* @param msg the detail message.
*/
public MeasurementNotReadyException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
*
* @author Alexander Nozik
*/
public class MeasurementTimeoutException extends MeasurementException {
/**
* Creates a new instance of <code>MeasurementTimeoutException</code>
* without detail message.
*/
public MeasurementTimeoutException() {
}
/**
* Constructs an instance of <code>MeasurementTimeoutException</code> with
* the specified detail message.
*
* @param msg the detail message.
*/
public MeasurementTimeoutException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
*
* @author Alexander Nozik
*/
public class PortException extends ControlException {
/**
* Creates a new instance of <code>PortException</code> without detail
* message.
*/
public PortException() {
}
/**
* Constructs an instance of <code>PortException</code> with the specified
* detail message.
*
* @param msg the detail message.
*/
public PortException(String msg) {
super(msg);
}
public PortException(String message, Throwable cause) {
super(message, cause);
}
public PortException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
public class PortLockException extends RuntimeException {
public PortLockException() {
}
public PortLockException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.control
import hep.dataforge.connections.Connection
import hep.dataforge.context.*
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.control.devices.DeviceHub
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import java.util.*
/**
* A plugin for creating and using different devices
* Created by darksnake on 11-Oct-16.
*/
@PluginDef(name = "devices", info = "Management plugin for devices an their interaction")
class DeviceManager : BasicPlugin(), DeviceHub {
/**
* Registered devices
*/
private val _devices = HashMap<Name, Device>()
/**
* the list of top level devices
*/
val devices: Collection<Device> = _devices.values
override val deviceNames: List<Name>
get() = _devices.entries.flatMap { entry ->
if (entry.value is DeviceHub) {
(entry.value as DeviceHub).deviceNames.map { it -> entry.key.plus(it) }
} else {
listOf(entry.key)
}
}
fun add(device: Device) {
val name = Name.ofSingle(device.name)
if (_devices.containsKey(name)) {
logger.warn("Replacing existing device in device manager!")
remove(name)
}
_devices[name] = device
}
fun remove(name: Name) {
Optional.ofNullable(this._devices.remove(name)).ifPresent { it ->
try {
it.shutdown()
} catch (e: ControlException) {
logger.error("Failed to stop the device: " + it.name, e)
}
}
}
fun buildDevice(deviceMeta: Meta): Device {
val factory = context
.findService(DeviceFactory::class.java) { it.type == ControlUtils.getDeviceType(deviceMeta) }
?: throw RuntimeException("Can't find factory for given device type")
val device = factory.build(context, deviceMeta)
deviceMeta.getMetaList("connection").forEach { connectionMeta -> device.connectionHelper.connect(context, connectionMeta) }
add(device)
return device
}
override fun optDevice(name: Name): Optional<Device> {
return when {
name.isEmpty() -> throw IllegalArgumentException("Can't provide a device with zero name")
name.length == 1 -> Optional.ofNullable(_devices[name])
else -> Optional.ofNullable(_devices[name.first]).flatMap { hub ->
if (hub is DeviceHub) {
(hub as DeviceHub).optDevice(name.cutFirst())
} else {
Optional.empty()
}
}
}
}
override fun detach() {
_devices.values.forEach { it ->
try {
it.shutdown()
} catch (e: ControlException) {
logger.error("Failed to stop the device: " + it.name, e)
}
}
super.detach()
}
override fun connectAll(connection: Connection, vararg roles: String) {
this._devices.values.forEach { device -> device.connect(connection, *roles) }
}
override fun connectAll(context: Context, meta: Meta) {
this._devices.values.forEach { device -> device.connectionHelper.connect(context, meta) }
}
class Factory : PluginFactory() {
override val type: Class<out Plugin>
get() = DeviceManager::class.java
override fun build(meta: Meta): Plugin {
return DeviceManager()
}
}
}

View File

@ -0,0 +1,29 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.connections
import hep.dataforge.connections.Connection
import hep.dataforge.control.devices.Device
abstract class DeviceConnection : Connection {
private var _device: Device? = null
val device: Device
get() = _device ?: throw RuntimeException("Connection closed")
override fun isOpen(): Boolean {
return this._device != null
}
override fun open(device: Any) {
this._device = Device::class.java.cast(device)
}
override fun close() {
this._device = null
}
}

View File

@ -0,0 +1,44 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.connections
import hep.dataforge.connections.Connection
import hep.dataforge.context.Context
import hep.dataforge.context.ContextAware
import hep.dataforge.context.launch
import hep.dataforge.storage.tables.MutableTableLoader
import hep.dataforge.tables.ValuesListener
import hep.dataforge.values.Values
/**
*
* @author Alexander Nozik
*/
class LoaderConnection(private val loader: MutableTableLoader) : Connection, ValuesListener, ContextAware {
override val context: Context
get() = loader.context
override fun accept(point: Values) {
launch {
loader.append(point)
}
}
override fun isOpen(): Boolean {
return true
}
override fun open(`object`: Any) {
}
@Throws(Exception::class)
override fun close() {
loader.close()
}
}

View File

@ -0,0 +1,17 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.connections
/**
*
* @author Alexander Nozik
*/
object Roles {
const val DEVICE_LISTENER_ROLE = "deviceListener"
const val MEASUREMENT_LISTENER_ROLE = "measurementListener"
const val STORAGE_ROLE = "storage"
const val VIEW_ROLE = "view"
}

View File

@ -0,0 +1,199 @@
/*
* Copyright 2017 Alexander Nozik.
*
* 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 hep.dataforge.control.devices
import hep.dataforge.connections.Connection
import hep.dataforge.connections.ConnectionHelper
import hep.dataforge.context.Context
import hep.dataforge.context.Global
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.devices.Device.Companion.INITIALIZED_STATE
import hep.dataforge.description.ValueDef
import hep.dataforge.events.Event
import hep.dataforge.events.EventHandler
import hep.dataforge.exceptions.ControlException
import hep.dataforge.listAnnotations
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaHolder
import hep.dataforge.names.AnonymousNotAlowed
import hep.dataforge.states.*
import hep.dataforge.values.ValueType
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import java.time.Duration
import java.util.concurrent.*
/**
*
*
* State has two components: physical and logical. If logical state does not
* coincide with physical, it should be invalidated and automatically updated on
* next request.
*
*
* @author Alexander Nozik
*/
@AnonymousNotAlowed
@StateDef(
value = ValueDef(
key = INITIALIZED_STATE,
type = [ValueType.BOOLEAN],
def = "false",
info = "Initialization state of the device"
), writable = true
)
abstract class AbstractDevice(override final val context: Context = Global, meta: Meta) : MetaHolder(meta), Device {
final override val states = StateHolder()
val initializedState: ValueState = valueState(INITIALIZED_STATE) { old, value ->
if (old != value) {
if (value.boolean) {
init()
} else {
shutdown()
}
}
}
/**
* Initialization state
*/
val initialized by initializedState.booleanDelegate
private var stateListenerJob: Job? = null
private val _connectionHelper: ConnectionHelper by lazy { ConnectionHelper(this) }
override fun getConnectionHelper(): ConnectionHelper {
return _connectionHelper
}
/**
* A single thread executor for this device. All state changes and similar work must be done on this thread.
*
* @return
*/
protected val executor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor { r ->
val res = Thread(r)
res.name = "device::$name"
res.priority = Thread.MAX_PRIORITY
res.isDaemon = true
res
}
init {
//initialize states
javaClass.listAnnotations(StateDef::class.java, true).forEach {
states.init(ValueState(it))
}
javaClass.listAnnotations(MetaStateDef::class.java, true).forEach {
states.init(MetaState(it))
}
}
@Throws(ControlException::class)
override fun init() {
logger.info("Initializing device '{}'...", name)
states.update(INITIALIZED_STATE, true)
stateListenerJob = context.launch {
val flow = states.changes()
flow.collect {
onStateChange(it.first, it.second)
}
}
}
@Throws(ControlException::class)
override fun shutdown() {
logger.info("Shutting down device '{}'...", name)
forEachConnection(Connection::class.java) { c ->
try {
c.close()
} catch (e: Exception) {
logger.error("Failed to close connection", e)
}
}
states.update(INITIALIZED_STATE, false)
stateListenerJob?.cancel()
executor.shutdown()
}
override val name: String
get() = meta.getString("name", type)
protected fun runOnDeviceThread(runnable: () -> Unit): Future<*> {
return executor.submit(runnable)
}
protected fun <T> callOnDeviceThread(callable: () -> T): Future<T> {
return executor.submit(callable)
}
protected fun scheduleOnDeviceThread(delay: Duration, runnable: () -> Unit): ScheduledFuture<*> {
return executor.schedule(runnable, delay.toMillis(), TimeUnit.MILLISECONDS)
}
protected fun repeatOnDeviceThread(
interval: Duration,
delay: Duration = Duration.ZERO,
runnable: () -> Unit
): ScheduledFuture<*> {
return executor.scheduleWithFixedDelay(runnable, delay.toMillis(), interval.toMillis(), TimeUnit.MILLISECONDS)
}
/**
* Override to apply custom internal reaction of state change
*/
protected open fun onStateChange(stateName: String, value: Any) {
forEachConnection(Roles.DEVICE_LISTENER_ROLE, DeviceListener::class.java) {
it.notifyStateChanged(this, stateName, value)
}
}
override val type: String
get() = meta.getString("type", "unknown")
protected fun updateState(stateName: String, value: Any?) {
states.update(stateName, value)
}
}
val Device.initialized: Boolean
get() {
return if (this is AbstractDevice) {
this.initialized
} else {
this.states
.filter { it.name == INITIALIZED_STATE }
.filterIsInstance(ValueState::class.java).firstOrNull()?.value?.boolean ?: false
}
}
fun Device.notifyError(message: String, error: Throwable? = null) {
logger.error(message, error)
forEachConnection(DeviceListener::class.java) {
it.evaluateDeviceException(this, message, error)
}
}
fun Device.dispatchEvent(event: Event) {
forEachConnection(EventHandler::class.java) { it -> it.pushEvent(event) }
}

View File

@ -0,0 +1,107 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.control.devices
import hep.dataforge.Named
import hep.dataforge.connections.AutoConnectible
import hep.dataforge.connections.Connection.EVENT_HANDLER_ROLE
import hep.dataforge.connections.Connection.LOGGER_ROLE
import hep.dataforge.connections.RoleDef
import hep.dataforge.connections.RoleDefs
import hep.dataforge.context.ContextAware
import hep.dataforge.control.connections.Roles.DEVICE_LISTENER_ROLE
import hep.dataforge.control.connections.Roles.VIEW_ROLE
import hep.dataforge.events.EventHandler
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Metoid
import hep.dataforge.states.Stateful
import org.slf4j.Logger
/**
* The Device is general abstract representation of any physical or virtual
* apparatus that can interface with data acquisition and control system.
*
*
* The device has following important features:
*
*
* *
* **States:** each device has a number of states that could be
* accessed by `getState` method. States could be either stored as some
* internal variables or calculated on demand. States calculation is
* synchronous!
*
* *
* **Listeners:** some external class which listens device state
* changes and events. By default listeners are represented by weak references
* so they could be finalized any time if not used.
* *
* **Connections:** any external device connectors which are used
* by device. The difference between listener and connection is that device is
* obligated to notify all registered listeners about all changes, but
* connection is used by device at its own discretion. Also usually only one
* connection is used for each single purpose.
*
*
*
* @author Alexander Nozik
*/
@RoleDefs(
RoleDef(name = DEVICE_LISTENER_ROLE, objectType = DeviceListener::class, info = "A device listener"),
RoleDef(name = LOGGER_ROLE, objectType = Logger::class, unique = true, info = "The logger for this device"),
RoleDef(name = EVENT_HANDLER_ROLE, objectType = EventHandler::class, info = "The listener for device events"),
RoleDef(name = VIEW_ROLE)
)
interface Device : AutoConnectible, Metoid, ContextAware, Named, Stateful {
/**
* Device type
*
* @return
*/
val type: String
@JvmDefault
override val logger: Logger
get() = optConnection(LOGGER_ROLE, Logger::class.java).orElse(context.logger)
/**
* Initialize device and check if it is working but do not start any
* measurements or issue commands. Init method could be called only once per
* MeasurementDevice object. On second call it throws exception or does
* nothing.
*
* @throws ControlException
*/
@Throws(ControlException::class)
fun init()
/**
* Release all resources locked during init. No further work with device is
* possible after shutdown. The init method called after shutdown can cause
* exceptions or incorrect work.
*
* @throws ControlException
*/
@Throws(ControlException::class)
fun shutdown()
companion object {
const val INITIALIZED_STATE = "initialized"
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.control.devices
import hep.dataforge.utils.ContextMetaFactory
/**
* Created by darksnake on 06-May-17.
*/
interface DeviceFactory : ContextMetaFactory<Device> {
/**
* The type of the device factory. One factory can supply multiple device classes depending on configuration.
*
* @return
*/
val type: String
}

View File

@ -0,0 +1,91 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.control.devices
import hep.dataforge.connections.Connection
import hep.dataforge.context.Context
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.providers.Provider
import hep.dataforge.providers.Provides
import hep.dataforge.providers.ProvidesNames
import java.util.*
import java.util.stream.Stream
/**
* A hub containing several devices
*/
interface DeviceHub : Provider {
val deviceNames: List<Name>
fun optDevice(name: Name): Optional<Device>
@Provides(DEVICE_TARGET)
fun optDevice(name: String): Optional<Device> {
return optDevice(Name.of(name))
}
@ProvidesNames(DEVICE_TARGET)
fun listDevices(): Stream<String> {
return deviceNames.stream().map{ it.toString() }
}
fun getDevices(recursive: Boolean): Stream<Device> {
return if (recursive) {
deviceNames.stream().map { it -> optDevice(it).get() }
} else {
deviceNames.stream().filter { it -> it.length == 1 }.map { it -> optDevice(it).get() }
}
}
/**
* Add a connection to each of child devices
*
* @param connection
* @param roles
*/
fun connectAll(connection: Connection, vararg roles: String) {
deviceNames.stream().filter { it -> it.length == 1 }
.map<Optional<Device>>{ this.optDevice(it) }
.map<Device>{ it.get() }
.forEach { it ->
if (it is DeviceHub) {
(it as DeviceHub).connectAll(connection, *roles)
} else {
it.connect(connection, *roles)
}
}
}
fun connectAll(context: Context, meta: Meta) {
deviceNames.stream().filter { it -> it.length == 1 }
.map<Optional<Device>>{ this.optDevice(it) }
.map<Device>{ it.get() }
.forEach { it ->
if (it is DeviceHub) {
(it as DeviceHub).connectAll(context, meta)
} else {
it.connectionHelper.connect(context, meta)
}
}
}
companion object {
const val DEVICE_TARGET = "device"
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.control.devices
import hep.dataforge.connections.Connection
/**
* A listener that listens to device state change initialization and shut down
*
* @author Alexander Nozik
*/
interface DeviceListener: Connection {
/**
* Notify that state of device is changed.
*
* @param device
* @param name the name of the state
* @param state
*/
fun notifyStateChanged(device: Device, name: String, state: Any)
/**
*
* @param device
* @param message
* @param exception
*/
fun evaluateDeviceException(device: Device, message: String, exception: Throwable?) {
}
}

View File

@ -0,0 +1,208 @@
/*
* Copyright 2017 Alexander Nozik.
*
* 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.
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.devices
import hep.dataforge.context.Context
import hep.dataforge.control.devices.PortSensor.Companion.CONNECTED_STATE
import hep.dataforge.control.devices.PortSensor.Companion.DEBUG_STATE
import hep.dataforge.control.ports.GenericPortController
import hep.dataforge.control.ports.PortFactory
import hep.dataforge.description.NodeDef
import hep.dataforge.description.ValueDef
import hep.dataforge.description.ValueDefs
import hep.dataforge.events.EventBuilder
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
import hep.dataforge.nullable
import hep.dataforge.states.*
import hep.dataforge.useValue
import hep.dataforge.values.ValueType.BOOLEAN
import hep.dataforge.values.ValueType.NUMBER
import java.time.Duration
/**
* A Sensor that uses a Port to obtain data
*
* @param <T>
* @author darksnake
*/
@StateDefs(
StateDef(
value = ValueDef(
key = CONNECTED_STATE,
type = [BOOLEAN],
def = "false",
info = "The connection state for this device"
), writable = true
),
//StateDef(value = ValueDef(name = PORT_STATE, info = "The name of the port to which this device is connected")),
StateDef(
value = ValueDef(
key = DEBUG_STATE,
type = [BOOLEAN],
def = "false",
info = "If true, then all received phrases would be shown in the log"
), writable = true
)
)
@MetaStateDef(
value = NodeDef(
key = "port",
descriptor = "method::hep.dataforge.control.ports.PortFactory.build",
info = "Information about port"
), writable = true
)
@ValueDefs(
ValueDef(key = "timeout", type = arrayOf(NUMBER), def = "400", info = "A timeout for port response in milliseconds")
)
abstract class PortSensor(context: Context, meta: Meta) : Sensor(context, meta) {
private var _connection: GenericPortController? = null
protected val connection: GenericPortController
get() = _connection ?: throw RuntimeException("Not connected")
val connected = valueState(CONNECTED_STATE, getter = { connection.port.isOpen }) { old, value ->
if (old != value) {
logger.info("State 'connect' changed to $value")
connect(value.boolean)
}
update(value)
}
var debug by valueState(DEBUG_STATE) { old, value ->
if (old != value) {
logger.info("Turning debug mode to $value")
setDebugMode(value.boolean)
}
update(value)
}.booleanDelegate
var port by metaState(PORT_STATE, getter = { connection.port.toMeta() }) { old, value ->
if (old != value) {
setupConnection(value)
}
update(value)
}.delegate
private val defaultTimeout: Duration = Duration.ofMillis(meta.getInt("timeout", 400).toLong())
init {
// meta.useMeta(PORT_STATE) {
// port = it
// }
meta.useValue(DEBUG_STATE) {
updateState(DEBUG_STATE, it.boolean)
}
}
private fun setDebugMode(debugMode: Boolean) {
//Add debug listener
if (debugMode) {
connection.apply {
onAnyPhrase("$name[debug]") { phrase -> logger.debug("Device {} received phrase: \n{}", name, phrase) }
onError("$name[debug]") { message, error ->
logger.error(
"Device {} exception: \n{}",
name,
message,
error
)
}
}
} else {
connection.apply {
removePhraseListener("$name[debug]")
removeErrorListener("$name[debug]")
}
}
updateState(DEBUG_STATE, debugMode)
}
private fun connect(connected: Boolean) {
if (connected) {
try {
if (_connection == null) {
logger.debug("Setting up connection using device meta")
val initialPort = meta.optMeta(PORT_STATE).nullable
?: meta.optString(PORT_STATE).nullable?.let { PortFactory.nameToMeta(it) }
?: Meta.empty()
setupConnection(initialPort)
}
connection.open()
this.connected.update(true)
} catch (ex: Exception) {
notifyError("Failed to open connection", ex)
this.connected.update(false)
}
} else {
_connection?.close()
_connection = null
this.connected.update(false)
}
}
protected open fun buildConnection(meta: Meta): GenericPortController {
val port = PortFactory.build(meta)
return GenericPortController(context, port)
}
private fun setupConnection(portMeta: Meta) {
_connection?.close()
this._connection = buildConnection(portMeta)
setDebugMode(debug)
updateState(PORT_STATE, portMeta)
}
@Throws(ControlException::class)
override fun shutdown() {
super.shutdown()
connected.set(false)
}
protected fun sendAndWait(request: String, timeout: Duration = defaultTimeout): String {
return connection.sendAndWait(request, timeout) { true }
}
protected fun sendAndWait(
request: String,
timeout: Duration = defaultTimeout,
predicate: (String) -> Boolean
): String {
return connection.sendAndWait(request, timeout, predicate)
}
protected fun send(message: String) {
connection.send(message)
dispatchEvent(
EventBuilder
.make(name)
.setMetaValue("request", message)
.build()
)
}
companion object {
const val CONNECTED_STATE = "connected"
const val PORT_STATE = "port"
const val DEBUG_STATE = "debug"
}
}

View File

@ -0,0 +1,259 @@
/*
* Copyright 2017 Alexander Nozik.
*
* 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.
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.devices
import hep.dataforge.context.Context
import hep.dataforge.control.devices.Sensor.Companion.MEASUREMENT_MESSAGE_STATE
import hep.dataforge.control.devices.Sensor.Companion.MEASUREMENT_META_STATE
import hep.dataforge.control.devices.Sensor.Companion.MEASUREMENT_PROGRESS_STATE
import hep.dataforge.control.devices.Sensor.Companion.MEASUREMENT_RESULT_STATE
import hep.dataforge.control.devices.Sensor.Companion.MEASUREMENT_STATUS_STATE
import hep.dataforge.control.devices.Sensor.Companion.MEASURING_STATE
import hep.dataforge.description.NodeDef
import hep.dataforge.description.ValueDef
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaMorph
import hep.dataforge.meta.buildMeta
import hep.dataforge.states.*
import hep.dataforge.values.ValueType
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import kotlinx.coroutines.time.delay
import java.time.Duration
import java.time.Instant
/**
* A device with which could perform of one-time or regular measurements. Only one measurement is allowed at a time
*
* @author Alexander Nozik
*/
@ValueDef(
key = "resultBuffer",
type = [ValueType.NUMBER],
def = "100",
info = "The size of the buffer for results of measurements"
)
@StateDefs(
StateDef(
value = ValueDef(
key = MEASURING_STATE,
type = [ValueType.BOOLEAN],
info = "Shows if this sensor is actively measuring"
), writable = true
),
StateDef(
ValueDef(
key = MEASUREMENT_STATUS_STATE,
enumeration = Sensor.MeasurementState::class,
info = "Shows if this sensor is actively measuring"
)
),
StateDef(ValueDef(key = MEASUREMENT_MESSAGE_STATE, info = "Current message")),
StateDef(ValueDef(key = MEASUREMENT_PROGRESS_STATE, type = [ValueType.NUMBER], info = "Current progress"))
)
@MetaStateDefs(
MetaStateDef(
value = NodeDef(key = MEASUREMENT_META_STATE, info = "Configuration of current measurement."),
writable = true
),
MetaStateDef(NodeDef(key = MEASUREMENT_RESULT_STATE, info = "The result of the last measurement in Meta form"))
)
abstract class Sensor(context: Context, meta: Meta) : AbstractDevice(context, meta) {
private val coroutineContext = executor.asCoroutineDispatcher()
protected var job: Job? = null
val resultState = metaState(MEASUREMENT_RESULT_STATE)
/**
* The result of last measurement
*/
val result: Meta by resultState.delegate
/**
* The error from last measurement
*/
val error: Meta by metaState(MEASUREMENT_ERROR_STATE).delegate
/**
* Current measurement configuration
*/
var measurement by metaState(MEASUREMENT_META_STATE) { old: Meta?, value: Meta ->
startMeasurement(old, value)
update(value)
}.delegate
/**
* true if measurement in process
*/
val measuring = valueState(MEASURING_STATE) { value ->
if (value.boolean) {
startMeasurement(null, measurement)
} else {
stopMeasurement()
}
update(value)
}
/**
* Current state of the measurement
*/
val measurementState by valueState(MEASUREMENT_STATUS_STATE).enumDelegate<MeasurementState>()
var message by valueState(MEASUREMENT_MESSAGE_STATE).stringDelegate
var progress by valueState(MEASUREMENT_PROGRESS_STATE).doubleDelegate
override fun shutdown() {
stopMeasurement()
super.shutdown()
}
/**
* Start measurement with current configuration if it is not in progress
*/
fun measure() {
if (!measuring.booleanValue) {
measuring.set(true)
}
}
/**
* Notify measurement state changed
*/
protected fun notifyMeasurementState(state: MeasurementState) {
updateState(MEASUREMENT_STATUS_STATE, state.name)
when (state) {
MeasurementState.NOT_STARTED -> updateState(MEASURING_STATE, false)
MeasurementState.STOPPED -> updateState(MEASURING_STATE, false)
MeasurementState.IN_PROGRESS -> updateState(MEASURING_STATE, true)
MeasurementState.WAITING -> updateState(MEASURING_STATE, true)
}
}
/**
* Set active measurement using given meta
* @param oldMeta Meta of previous active measurement. If null no measurement was set
* @param newMeta Meta of new measurement. If null, then clear measurement
* @return actual meta for new measurement
*/
protected abstract fun startMeasurement(oldMeta: Meta?, newMeta: Meta)
/**
* stop measurement with given meta
*/
protected open fun stopMeasurement() {
synchronized(this) {
job?.cancel()
notifyMeasurementState(MeasurementState.STOPPED)
}
}
protected fun measurement(action: suspend () -> Unit) {
job = context.launch {
notifyMeasurementState(MeasurementState.IN_PROGRESS)
action.invoke()
notifyMeasurementState(MeasurementState.STOPPED)
}
}
protected fun scheduleMeasurement(interval: Duration, action: suspend () -> Unit) {
job = context.launch {
delay(interval)
notifyMeasurementState(MeasurementState.IN_PROGRESS)
action.invoke()
notifyMeasurementState(MeasurementState.STOPPED)
}
}
@InternalCoroutinesApi
protected fun regularMeasurement(interval: Duration, action: suspend () -> Unit) {
job = context.launch {
while (true) {
notifyMeasurementState(MeasurementState.IN_PROGRESS)
action.invoke()
notifyMeasurementState(MeasurementState.WAITING)
delay(interval)
}
}.apply {
invokeOnCompletion(onCancelling = true, invokeImmediately = true) {
notifyMeasurementState(MeasurementState.STOPPED)
}
}
}
protected fun notifyResult(value: Any, timestamp: Instant = Instant.now()) {
val result = buildMeta("result") {
RESULT_TIMESTAMP to timestamp
when (value) {
is Meta -> setNode(RESULT_VALUE, value)
is MetaMorph -> setNode(RESULT_VALUE, value.toMeta())
else -> RESULT_VALUE to value
}
}
updateState(MEASUREMENT_RESULT_STATE, result)
forEachConnection(SensorListener::class.java){
it.reading(this,value)
}
}
protected fun notifyError(value: Any, timestamp: Instant = Instant.now()) {
val result = buildMeta("error") {
RESULT_TIMESTAMP to timestamp
if (value is Meta) {
setNode(RESULT_VALUE, value)
} else {
RESULT_VALUE to value
}
}
updateState(MEASUREMENT_ERROR_STATE, result)
}
enum class MeasurementState {
NOT_STARTED, // initial state, not started
IN_PROGRESS, // in progress
WAITING, // waiting on scheduler
STOPPED // stopped
}
companion object {
const val MEASURING_STATE = "measurement.active"
const val MEASUREMENT_STATUS_STATE = "measurement.state"
const val MEASUREMENT_META_STATE = "measurement.meta"
const val MEASUREMENT_RESULT_STATE = "measurement.result"
const val MEASUREMENT_ERROR_STATE = "measurement.error"
const val MEASUREMENT_MESSAGE_STATE = "measurement.message"
const val MEASUREMENT_PROGRESS_STATE = "measurement.progress"
const val RESULT_SUCCESS = "success"
const val RESULT_TIMESTAMP = "timestamp"
const val RESULT_VALUE = "value"
}
}
interface SensorListener{
fun reading(sensor: Sensor, any:Any)
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2017 Alexander Nozik.
*
* 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 hep.dataforge.control.devices
import hep.dataforge.context.Context
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import java.time.Duration
import java.time.Instant
import java.util.*
private val VIRTUAL_SENSOR_TYPE = "@test"
private val generator = Random()
class VirtualSensor(context: Context) : Sensor(context, Meta.empty()) {
override fun startMeasurement(oldMeta: Meta?, newMeta: Meta) {
if (oldMeta !== newMeta) {
val delay = Duration.parse(newMeta.getString("duration", "PT0.2S"))
val mean = newMeta.getDouble("mean", 1.0)
val sigma = newMeta.getDouble("sigma", 0.1)
measurement {
Thread.sleep(delay.toMillis())
val value = generator.nextDouble() * sigma + mean
MetaBuilder("result").setValue("value", value).setValue("timestamp", Instant.now())
}
}
}
override val type: String
get() {
return VIRTUAL_SENSOR_TYPE
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright 2017 Alexander Nozik.
*
* 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 hep.dataforge.control.ports
import hep.dataforge.exceptions.PortException
import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import jssc.SerialPort
import jssc.SerialPort.*
import jssc.SerialPortEventListener
import jssc.SerialPortException
import kotlinx.coroutines.launch
import java.io.IOException
/**
* @author Alexander Nozik
*/
class ComPort(val address: String, val config: Meta) : Port() {
override val name: String = if (address.startsWith("com::")) {
address
} else {
"com::$address"
}
// private static final int CHAR_SIZE = 1;
// private static final int MAX_SIZE = 50;
private val port: SerialPort by lazy {
SerialPort(name)
}
private val serialPortListener = SerialPortEventListener { event ->
if (event.isRXCHAR) {
val chars = event.eventValue
try {
val bytes = port.readBytes(chars)
receive(bytes)
} catch (ex: IOException) {
throw RuntimeException(ex)
} catch (ex: SerialPortException) {
throw RuntimeException(ex)
}
}
}
override val isOpen: Boolean
get() = port.isOpened
override fun toString(): String {
return name
}
@Throws(PortException::class)
override fun open() {
try {
if (!port.isOpened) {
port.apply {
openPort()
val baudRate = config.getInt("baudRate", BAUDRATE_9600)
val dataBits = config.getInt("dataBits", DATABITS_8)
val stopBits = config.getInt("stopBits", STOPBITS_1)
val parity = config.getInt("parity", PARITY_NONE)
setParams(baudRate, dataBits, stopBits, parity)
addEventListener(serialPortListener)
}
}
} catch (ex: SerialPortException) {
throw PortException("Can't open the port", ex)
}
}
@Throws(PortException::class)
fun clearPort() {
try {
port.purgePort(PURGE_RXCLEAR or PURGE_TXCLEAR)
} catch (ex: SerialPortException) {
throw PortException(ex)
}
}
@Throws(Exception::class)
override fun close() {
port.let {
it.removeEventListener()
if (it.isOpened) {
it.closePort()
}
}
super.close()
}
@Throws(PortException::class)
public override fun send(message: ByteArray) {
if (!isOpen) {
open()
}
launch {
try {
logger.debug("SEND: $message")
port.writeBytes(message)
} catch (ex: SerialPortException) {
throw RuntimeException(ex)
}
}
}
override fun toMeta(): Meta = buildMeta {
"type" to "com"
"name" to this@ComPort.name
"address" to address
update(config)
}
companion object {
/**
* Construct ComPort with default parameters:
*
*
* Baud rate: 9600
*
*
* Data bits: 8
*
*
* Stop bits: 1
*
*
* Parity: non
*
* @param portName
*/
@JvmOverloads
fun create(portName: String, baudRate: Int = BAUDRATE_9600, dataBits: Int = DATABITS_8, stopBits: Int = STOPBITS_1, parity: Int = PARITY_NONE): ComPort {
return ComPort(portName, buildMeta {
setValue("type", "com")
putValue("name", portName)
putValue("baudRate", baudRate)
putValue("dataBits", dataBits)
putValue("stopBits", stopBits)
putValue("parity", parity)
})
}
}
}

View File

@ -0,0 +1,329 @@
/*
* Copyright 2017 Alexander Nozik.
*
* 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 hep.dataforge.control.ports
import hep.dataforge.context.Context
import hep.dataforge.context.ContextAware
import hep.dataforge.context.Global
import hep.dataforge.exceptions.ControlException
import hep.dataforge.exceptions.PortException
import hep.dataforge.utils.ReferenceRegistry
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.ByteArrayOutputStream
import java.nio.charset.Charset
import java.time.Duration
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
/**
* A port controller helper that allows both synchronous and asynchronous operations on port
* @property port the port associated with this controller
*/
open class GenericPortController(
override val context: Context,
val port: Port,
private val phraseCondition: (String) -> Boolean = { it.endsWith("\n") }
) : PortController, AutoCloseable, ContextAware {
constructor(context: Context, port: Port, delimiter: String) : this(context, port, { it.endsWith(delimiter) })
private val waiters = ReferenceRegistry<FuturePhrase>()
private val listeners = ReferenceRegistry<PhraseListener>()
private val exceptionListeners = ReferenceRegistry<ErrorListener>()
private val buffer = ByteArrayOutputStream();
fun open() {
try {
port.holdBy(this)
if (!port.isOpen) {
port.open()
}
} catch (e: PortException) {
throw RuntimeException("Can't hold the port $port by generic handler", e)
}
}
override val logger: Logger
get() = LoggerFactory.getLogger("${context.name}.$port")
override fun accept(byte: Byte) {
synchronized(port) {
buffer.write(byte.toInt())
val string = buffer.toString("UTF8")
if (phraseCondition(string)) {
acceptPhrase(string)
buffer.reset()
}
}
}
private fun acceptPhrase(message: String) {
waiters.forEach { waiter -> waiter.acceptPhrase(message) }
listeners.forEach { listener -> listener.acceptPhrase(message) }
}
override fun error(errorMessage: String, error: Throwable) {
exceptionListeners.forEach { it ->
context.executors.defaultExecutor.submit {
try {
it.action(errorMessage, error)
} catch (ex: Exception) {
logger.error("Failed to execute error listener action", ex)
}
}
}
}
/**
* Wait for next phrase matching condition and return its result
*
* @return
*/
@JvmOverloads
fun next(condition: (String) -> Boolean = { true }): CompletableFuture<String> {
//No need for synchronization since ReferenceRegistry is synchronized
waiters.removeIf { it.isDone }
val res = FuturePhrase(condition)
waiters.add(res)
return res
}
/**
* Get next phrase matching pattern
*
* @param pattern
* @return
*/
fun next(pattern: String): CompletableFuture<String> {
return next { it -> it.matches(pattern.toRegex()) }
}
/**
* Block until specific phrase is received
*
* @param timeout
* @param predicate
* @return
* @throws PortException
*/
@JvmOverloads
fun waitFor(timeout: Duration, predicate: (String) -> Boolean = { true }): String {
return next(predicate).get(timeout.toMillis(), TimeUnit.MILLISECONDS)
}
/**
* Hook specific reaction to the specific phrase. Whenever it is possible, it is better to use `weakOnPhrase` to avoid memory leaks due to obsolete listeners.
*
* @param condition
* @param action
*/
fun onPhrase(condition: (String) -> Boolean, owner: Any? = null, action: (String) -> Unit) {
val listener = PhraseListener(condition, owner, action)
listeners.add(listener)
}
/**
* Add weak phrase listener
*
* @param condition
* @param action
* @return
*/
fun weakOnPhrase(condition: (String) -> Boolean, owner: Any? = null, action: (String) -> Unit) {
val listener = PhraseListener(condition, owner, action)
listeners.add(listener, false)
}
fun weakOnPhrase(pattern: String, owner: Any? = null, action: (String) -> Unit) {
weakOnPhrase({ it.matches(pattern.toRegex()) }, owner, action)
}
fun weakOnPhrase(owner: Any? = null, action: (String) -> Unit) {
weakOnPhrase({ true }, owner, action)
}
/**
* Remove a specific phrase listener
*
* @param listener
*/
fun removePhraseListener(owner: Any) {
this.listeners.removeIf { it.owner == owner }
}
/**
* Add action to phrase matching specific pattern
*
* @param pattern
* @param action
* @return
*/
fun onPhrase(pattern: String, owner: Any? = null, action: (String) -> Unit) {
onPhrase({ it.matches(pattern.toRegex()) }, owner, action)
}
/**
* Add reaction to any phrase
*
* @param action
* @return
*/
fun onAnyPhrase(owner: Any? = null, action: (String) -> Unit) {
onPhrase({ true }, owner, action)
}
/**
* Add error listener
*
* @param listener
* @return
*/
fun onError(owner: Any? = null, listener: (String, Throwable?) -> Unit) {
this.exceptionListeners.add(ErrorListener(owner, listener))
}
/**
* Add weak error listener
*
* @param listener
* @return
*/
fun weakOnError(owner: Any? = null, listener: (String, Throwable?) -> Unit) {
this.exceptionListeners.add(ErrorListener(owner, listener), false)
}
/**
* remove specific error listener
*
*/
fun removeErrorListener(owner: Any) {
this.exceptionListeners.removeIf { it.owner == owner }
}
/**
* Send async message to port
*
* @param message
*/
fun send(message: ByteArray) {
try {
open()
port.send(this, message)
} catch (e: PortException) {
throw RuntimeException("Failed to send message to port $port")
}
}
fun send(message: String, charset: Charset = Charsets.US_ASCII) {
send(message.toByteArray(charset))
}
/**
* Send and return the future with the result
*
* @param message
* @param condition
*/
fun sendAndGet(message: String, condition: (String) -> Boolean): CompletableFuture<String> {
val res = next(condition) // in case of immediate reaction
send(message)
return res
}
/**
* Send and block thread until specific result is obtained. All listeners and reactions work as usual.
*
* @param message
* @param timeout
* @param condition
* @return
*/
fun sendAndWait(message: String, timeout: Duration, condition: (String) -> Boolean = { true }): String {
return sendAndGet(message, condition).get(timeout.toMillis(), TimeUnit.MILLISECONDS)
}
/**
* Cancel all pending waiting actions and release the port. Does not close the port
*/
@Throws(Exception::class)
override fun close() {
close(Duration.ofMillis(1000))
}
/**
* Blocking close operation. Waits at most for timeout to finish all operations and then closes.
*
* @param timeout
*/
@Throws(Exception::class)
fun close(timeout: Duration) {
CompletableFuture.allOf(*waiters.toTypedArray()).get(timeout.toMillis(), TimeUnit.MILLISECONDS)
port.releaseBy(this)
}
private inner class FuturePhrase(internal val condition: (String) -> Boolean) : CompletableFuture<String>() {
internal fun acceptPhrase(phrase: String) {
if (condition(phrase)) {
complete(phrase)
}
}
}
private inner class PhraseListener(private val condition: (String) -> Boolean, val owner: Any? = null, private val action: (String) -> Unit) {
internal fun acceptPhrase(phrase: String) {
if (condition(phrase)) {
context.executors.defaultExecutor.submit {
try {
action(phrase)
} catch (ex: Exception) {
logger.error("Failed to execute hooked action", ex)
}
}
}
}
}
private inner class ErrorListener(val owner: Any? = null, val action: (String, Throwable?) -> Unit)
companion object {
/**
* Use temporary controller to safely send request and receive response
*
* @param port
* @param request
* @param timeout
* @return
* @throws ControlException
*/
@Throws(ControlException::class)
fun sendAndWait(port: Port, request: String, timeout: Duration): String {
try {
GenericPortController(Global, port).use { controller -> return controller.sendAndWait(request, timeout) { true } }
} catch (e: Exception) {
throw ControlException("Failed to close the port", e)
}
}
}
}

View File

@ -0,0 +1,200 @@
/*
* Copyright 2017 Alexander Nozik.
*
* 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 hep.dataforge.control.ports
import hep.dataforge.Named
import hep.dataforge.exceptions.PortException
import hep.dataforge.exceptions.PortLockException
import hep.dataforge.meta.MetaID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.time.Duration
import java.util.concurrent.Executors
import java.util.concurrent.locks.ReentrantLock
/**
* The controller which is currently working with this handler. One
* controller can simultaneously hold many handlers, but handler could be
* held by only one controller.
*/
interface PortController {
fun accept(byte: Byte)
fun accept(bytes: ByteArray){
//TODO improve performance using byte buffers
bytes.forEach { accept(it) }
}
fun error(errorMessage: String, error: Throwable) {
//do nothing
}
}
/**
* The universal asynchronous port handler
*
* @author Alexander Nozik
*/
abstract class Port : AutoCloseable, MetaID, Named, CoroutineScope {
private val portLock = ReentrantLock(true)
private var controller: PortController? = null
override val coroutineContext = Executors.newSingleThreadExecutor { r ->
val res = Thread(r)
res.name = "port::$name"
res.priority = Thread.MAX_PRIORITY
res
}.asCoroutineDispatcher()
protected val logger: Logger by lazy { LoggerFactory.getLogger("port[$name]") }
abstract val isOpen: Boolean
private val isLocked: Boolean
get() = this.portLock.isLocked
@Throws(PortException::class)
abstract fun open()
/**
* Emergency hold break.
*/
@Synchronized
fun breakHold() {
if (isLocked) {
logger.warn("Breaking hold on port $name")
launch { portLock.unlock() }
}
}
/**
* An unique ID for this port
*
* @return
*/
override fun toString(): String {
return name
}
/**
* Acquire lock on this instance of port handler with given controller
* object. If port is currently locked by another controller, the wait until it is released.
* Only the same controller can release the port.
*
* @param controller
* @throws hep.dataforge.exceptions.PortException
*/
@Throws(PortException::class)
fun holdBy(controller: PortController) {
if (!isOpen) {
open()
}
launch {
try {
portLock.lockInterruptibly()
} catch (ex: InterruptedException) {
logger.error("Lock on port {} is broken", toString())
throw RuntimeException(ex)
}
}
logger.debug("Locked by {}", controller)
this.controller = controller
}
/**
* Receive a single byte
*/
fun receive(byte: Byte) {
controller?.accept(byte)
}
/**
* Receive an array of bytes
*/
fun receive(bytes: ByteArray) {
controller?.accept(bytes)
}
/**
* send the message to the port
*
* @param message
* @throws hep.dataforge.exceptions.PortException
*/
@Throws(PortException::class)
protected abstract fun send(message: ByteArray)
/**
* Send the message if the controller is correct
*
* @param controller
* @param message
* @throws PortException
*/
@Throws(PortException::class)
fun send(controller: PortController, message: ByteArray) {
if (controller === this.controller) {
send(message)
} else {
throw PortException("Port locked by another controller")
}
}
/**
* Release hold of this portHandler from given controller.
*
* @param controller
* @throws PortLockException in case given holder is not the one that holds
* handler
*/
@Synchronized
@Throws(PortLockException::class)
fun releaseBy(controller: PortController) {
if (isLocked) {
if (controller == this.controller) {
this.controller = null
launch {
portLock.unlock()
logger.debug("Unlocked by {}", controller)
}
} else {
throw PortLockException("Can't unlock port with wrong controller")
}
} else {
logger.warn("Attempting to release unlocked port")
}
}
@Throws(Exception::class)
override fun close() {
coroutineContext.close()
}
class PortTimeoutException(timeout: Duration) : PortException() {
override val message: String = String.format("The timeout time of '%s' is exceeded", timeout)
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 2017 Alexander Nozik.
*
* 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.
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.control.ports
import hep.dataforge.description.ValueDef
import hep.dataforge.description.ValueDefs
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.set
import hep.dataforge.utils.MetaFactory
import java.util.*
/**
*
* @author [Alexander Nozik](mailto:altavir@gmail.com)
*/
object PortFactory : MetaFactory<Port> {
private val portMap = HashMap<Meta, Port>()
@ValueDefs(
ValueDef(key = "type", def = "tcp", info = "The type of the port"),
ValueDef(key = "address", required = true, info = "The specific designation of this port according to type"),
ValueDef(key = "type", def = "tcp", info = "The type of the port")
)
override fun build(meta: Meta): Port {
val protocol = meta.getString("type", "tcp")
val port = when (protocol) {
"com" -> {
if (meta.hasValue("address")) {
ComPort(meta.getString("address"), meta)
} else {
throw IllegalArgumentException("Not enough information to create a port")
}
}
"tcp" -> {
if (meta.hasValue("ip") && meta.hasValue("port")) {
TcpPort(meta.getString("ip"), meta.getInt("port"), meta)
} else {
throw IllegalArgumentException("Not enough information to create a port")
}
}
"virtual" -> buildVirtualPort(meta)
else -> throw ControlException("Unknown protocol")
}
return portMap.getOrPut(port.toMeta()) { port }
}
private fun buildVirtualPort(meta: Meta): Port {
val className = meta.getString("class")
val theClass = Class.forName(className)
return theClass.getDeclaredConstructor(Meta::class.java).newInstance(meta) as Port
}
/**
* Create new port or reuse existing one if it is already created
* @param portName
* @return
* @throws ControlException
*/
fun build(portName: String): Port {
return build(nameToMeta(portName))
}
fun nameToMeta(portName: String): Meta {
val builder = MetaBuilder("port")
.setValue("name", portName)
val type = portName.substringBefore("::", "com")
val address = portName.substringAfter("::")
builder["type"] = type
builder["address"] = address
if (type == "tcp") {
builder["ip"] = address.substringBefore(":")
builder["port"] = address.substringAfter(":").toInt()
}
return builder.build();
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.control.ports
import hep.dataforge.context.Context
import hep.dataforge.context.ContextAware
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.dispatchEvent
import hep.dataforge.description.ValueDef
import hep.dataforge.events.EventBuilder
import hep.dataforge.meta.Meta
import hep.dataforge.nullable
import hep.dataforge.states.*
import hep.dataforge.useValue
import hep.dataforge.values.ValueType
import org.slf4j.Logger
import java.time.Duration
@StateDef(value = ValueDef(key = "connected",type = [ValueType.BOOLEAN], def = "false"), writable = true)
class PortHelper(
val device: Device,
val builder: ((Context, Meta) -> GenericPortController) = { context, meta -> GenericPortController(context, PortFactory.build(meta)) }
) : Stateful, ContextAware {
override val logger: Logger
get() = device.logger
override val states: StateHolder
get() = device.states
override val context: Context
get() = device.context
private val Device.portMeta: Meta
get() = meta.optMeta(PORT_STATE).nullable
?: device.meta.optValue(PORT_STATE).map {
PortFactory.nameToMeta(it.string)
}.orElse(Meta.empty())
var connection: GenericPortController = builder(context, device.portMeta)
private set
val connectedState = valueState(CONNECTED_STATE, getter = { connection.port.isOpen }) { old, value ->
if (old != value) {
logger.info("State 'connect' changed to $value")
if (value.boolean) {
connection.open()
} else {
connection.close()
}
//connect(value.boolean)
}
update(value)
}
var connected by connectedState.booleanDelegate
var debug by valueState(DEBUG_STATE) { old, value ->
if (old != value) {
logger.info("Turning debug mode to $value")
setDebugMode(value.boolean)
}
update(value)
}.booleanDelegate
var port by metaState(PORT_STATE, getter = { connection.port.toMeta() }) { old, value ->
if (old != value) {
setDebugMode(false)
connectedState.update(false)
connection.close()
connection = builder(context, value)
connection.open()
setDebugMode(debug)
}
update(value)
}.delegate
private val defaultTimeout: Duration = Duration.ofMillis(device.meta.getInt("port.timeout", 400).toLong())
val name get() = device.name
init {
states.update(PORT_STATE, connection.port.toMeta())
device.meta.useValue(DEBUG_STATE) {
debug = it.boolean
}
}
private fun setDebugMode(debugMode: Boolean) {
//Add debug listener
if (debugMode) {
connection.apply {
onAnyPhrase("$name[debug]") { phrase -> logger.debug("Device {} received phrase: \n{}", name, phrase) }
onError("$name[debug]") { message, error -> logger.error("Device {} exception: \n{}", name, message, error) }
}
} else {
connection.apply {
removePhraseListener("$name[debug]")
removeErrorListener("$name[debug]")
}
}
states.update(DEBUG_STATE, debugMode)
}
fun shutdown() {
connectedState.set(false)
}
fun sendAndWait(request: String, timeout: Duration = defaultTimeout): String {
return connection.sendAndWait(request, timeout) { true }
}
fun sendAndWait(request: String, timeout: Duration = defaultTimeout, predicate: (String) -> Boolean): String {
return connection.sendAndWait(request, timeout, predicate)
}
fun send(message: String) {
connection.send(message)
device.dispatchEvent(
EventBuilder
.make(device.name)
.setMetaValue("request", message)
.build()
)
}
companion object {
const val CONNECTED_STATE = "connected"
const val PORT_STATE = "port"
const val DEBUG_STATE = "debug"
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2017 Alexander Nozik.
*
* 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 hep.dataforge.control.ports
import hep.dataforge.exceptions.PortException
import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.channels.SocketChannel
/**
* @author Alexander Nozik
*/
class TcpPort(val ip: String, val port: Int, val config: Meta = Meta.empty()) : Port() {
private var channel: SocketChannel = SocketChannel.open()
override val isOpen: Boolean
get() = channel.isConnected
override val name = String.format("tcp::%s:%d", ip, port)
private var listenerJob: Job? = null
private fun openChannel(): SocketChannel{
return SocketChannel.open(InetSocketAddress(ip, port)).apply {
this.configureBlocking(false)
}
}
@Throws(PortException::class)
override fun open() {
launch {
if (!channel.isConnected && !channel.isConnectionPending) {
channel = openChannel()
startListener()
}
}
}
@Synchronized
@Throws(Exception::class)
override fun close() {
launch {
if(isOpen) {
listenerJob?.cancel()
channel.shutdownInput()
channel.shutdownOutput()
channel.close()
super.close()
}
}
}
private fun startListener() {
listenerJob = launch {
val buffer = ByteBuffer.allocate(1024)
while (true) {
try {
//read all content
do {
val num = channel.read(buffer)
if (num > 0) {
receive(buffer.toArray(num))
}
buffer.rewind()
} while (num > 0)
delay(50)
} catch (ex: Exception) {
logger.error("Channel read error", ex)
logger.info("Reconnecting")
channel = openChannel()
}
}
}
}
@Throws(PortException::class)
public override fun send(message: ByteArray) {
launch {
try {
channel.write(ByteBuffer.wrap(message))
logger.debug("SEND: ${String(message)}")
} catch (ex: Exception) {
throw RuntimeException(ex)
}
}
}
override fun toMeta(): Meta = buildMeta {
"type" to "tcp"
"name" to this@TcpPort.name
"ip" to ip
"port" to port
}
}
fun ByteBuffer.toArray(limit: Int = limit()): ByteArray{
rewind()
val response = ByteArray(limit)
get(response)
rewind()
return response
}

View File

@ -0,0 +1,127 @@
/*
* Copyright 2017 Alexander Nozik.
*
* 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 hep.dataforge.control.ports
import hep.dataforge.exceptions.PortException
import hep.dataforge.meta.Configurable
import hep.dataforge.meta.Configuration
import hep.dataforge.meta.Meta
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.time.Duration
import java.util.concurrent.CopyOnWriteArraySet
import java.util.function.Supplier
/**
* @author Alexander Nozik
*/
abstract class VirtualPort protected constructor(meta: Meta) : Port(), Configurable {
private val futures = CopyOnWriteArraySet<TaggedFuture>()
override var isOpen = false
var meta = Configuration(meta)
protected open val delimeter = meta.getString("delimenter", "\n")
@Throws(PortException::class)
override fun open() {
//scheduler = Executors.newScheduledThreadPool(meta.getInt("numThreads", 4))
isOpen = true
}
override fun getConfig(): Configuration {
return meta
}
override fun configure(config: Meta): Configurable {
meta.update(config)
return this
}
override fun toString(): String {
return meta.getString("id", javaClass.simpleName)
}
@Throws(PortException::class)
public override fun send(message: ByteArray) {
evaluateRequest(String(message, Charsets.US_ASCII))
}
/**
* The device logic here
*
* @param request
*/
protected abstract fun evaluateRequest(request: String)
@Synchronized
protected fun clearCompleted() {
futures.stream().filter { future -> future.future.isCompleted }.forEach { futures.remove(it) }
}
@Synchronized
protected fun cancelByTag(tag: String) {
futures.stream().filter { future -> future.hasTag(tag) }.forEach { it.cancel() }
}
/**
* Plan the response with given delay
*
* @param response
* @param delay
* @param tags
*/
@Synchronized
protected fun planResponse(response: String, delay: Duration, vararg tags: String) {
clearCompleted()
val future = launch {
kotlinx.coroutines.time.delay(delay)
receive((response + delimeter).toByteArray())
}
this.futures.add(TaggedFuture(future, *tags))
}
@Synchronized
protected fun planRegularResponse(responseBuilder: Supplier<String>, delay: Duration, period: Duration, vararg tags: String) {
clearCompleted()
val future = launch {
kotlinx.coroutines.time.delay(delay)
while (true) {
receive((responseBuilder.get() + delimeter).toByteArray())
kotlinx.coroutines.time.delay(period)
}
}
this.futures.add(TaggedFuture(future, *tags))
}
@Throws(Exception::class)
override fun close() {
futures.clear()
isOpen = false
super.close()
}
private inner class TaggedFuture(internal val future: Job, vararg tags: String) {
internal val tags = setOf(*tags)
fun hasTag(tag: String): Boolean {
return tags.contains(tag)
}
fun cancel() {
return future.cancel()
}
}
}

View File

@ -0,0 +1 @@
hep.dataforge.control.DeviceManager$Factory

View File

@ -0,0 +1,73 @@
package hep.dataforge.control.ports
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.channels.ServerSocketChannel
import java.nio.channels.SocketChannel
class TcpPortTest {
var job: Job? = null
@Before
fun startServer() {
GlobalScope.launch {
println("Starting server")
val serverSocketChannel = ServerSocketChannel.open()
serverSocketChannel.socket().bind(InetSocketAddress(9999))
while (true) {
delay(0)
println("Accepting client")
serverSocketChannel.accept().use {
val buffer = ByteBuffer.allocate(1024)
val num = it.read(buffer)
println("Received $num bytes")
buffer.flip()
it.write(buffer)
buffer.rewind()
}
}
}
}
@After
fun stopServer() {
job?.cancel()
}
@Test
fun testClient() {
val channel: SocketChannel = SocketChannel.open(InetSocketAddress("localhost", 9999))
println("Sending 3 bytes")
val request = "ddd".toByteArray()
channel.write(ByteBuffer.wrap(request))
val buffer = ByteBuffer.allocate(1024)
channel.read(buffer)
buffer.flip()
val response = buffer.toArray()
assertEquals(request.size, response.size)
assertEquals(String(request), String(response))
}
@Test
fun testPort() {
val port = TcpPort("localhost", 9999)
port.holdBy(object : PortController {
override fun accept(byte: Byte) {
println(byte)
}
})
port.send("ddd".toByteArray())
Thread.sleep(500)
}
}

View File

@ -0,0 +1,8 @@
description = 'dataforge-core'
dependencies {
compile 'ch.qos.logback:logback-classic:1.2.3'
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.2'
compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version
compile group: 'javax.cache', name: 'cache-api', version: '1.1.0'
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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.
*/
description = 'json meta type for dataforge'
dependencies {
compile project(":dataforge-core")
compile 'com.github.cliftonlabs:json-simple:3.0.2'
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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.
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.io
import com.github.cliftonlabs.json_simple.JsonArray
import com.github.cliftonlabs.json_simple.JsonObject
import com.github.cliftonlabs.json_simple.Jsoner
import hep.dataforge.meta.KMetaBuilder
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.buildMeta
import hep.dataforge.values.LateParseValue
import hep.dataforge.values.Value
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.text.ParseException
/**
* Reader for JSON meta
*
* @author Alexander Nozik
*/
object JSONMetaReader : MetaStreamReader {
@Throws(IOException::class, ParseException::class)
override fun read(stream: InputStream, length: Long): MetaBuilder {
return if (length == 0L) {
MetaBuilder("")
} else {
val json = if (length > 0) {
//Read into intermediate buffer
val buffer = ByteArray(length.toInt())
stream.read(buffer)
Jsoner.deserialize(InputStreamReader(ByteArrayInputStream(buffer), Charsets.UTF_8)) as JsonObject
} else {
Jsoner.deserialize(InputStreamReader(stream, Charsets.UTF_8)) as JsonObject
}
json.toMeta()
}
}
@Throws(ParseException::class)
private fun JsonObject.toMeta(): MetaBuilder {
return buildMeta {
this@toMeta.forEach { key, value -> appendValue(this, key as String, value) }
}
}
private fun JsonArray.toListValue(): Value {
val list = this.map {value->
when (value) {
is JsonArray -> value.toListValue()
is Number -> Value.of(value)
is Boolean -> Value.of(value)
is String -> LateParseValue(value)
null -> Value.NULL
is JsonObject -> throw RuntimeException("Object values inside multidimensional arrays are not allowed")
else -> throw Error("Unknown token $value in json")
}
}
return Value.of(list)
}
private fun appendValue(builder: KMetaBuilder, key: String, value: Any?) {
when (value) {
is JsonObject -> builder.attachNode(value.toMeta().rename(key))
is JsonArray -> {
value.forEach {
if(it is JsonArray){
builder.putValue(key, it.toListValue())
} else {
appendValue(builder, key, it)
}
}
}
is Number -> builder.putValue(key, value)
is Boolean -> builder.putValue(key, value)
is String -> builder.putValue(key, LateParseValue(value))
//ignore anything else
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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.
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.io
import hep.dataforge.io.envelopes.MetaType
class JSONMetaType : MetaType {
override val codes: List<Short> = listOf(0x4a53, 1)//JS
override val name: String = "JSON"
override val reader: MetaStreamReader = JSONMetaReader
override val writer: MetaStreamWriter = JSONMetaWriter
override val fileNameFilter: (String) -> Boolean = { it.toLowerCase().endsWith(".json") }
}
val jsonMetaType = JSONMetaType()

View File

@ -0,0 +1,123 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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.
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.io
import com.github.cliftonlabs.json_simple.JsonArray
import com.github.cliftonlabs.json_simple.JsonObject
import com.github.cliftonlabs.json_simple.Jsoner
import hep.dataforge.meta.Meta
import hep.dataforge.values.Value
import hep.dataforge.values.ValueType
import java.io.OutputStream
/**
* A converter from Meta object to JSON character stream
*
* @author Alexander Nozik
*/
object JSONMetaWriter : MetaStreamWriter {
override fun write(stream: OutputStream, meta: Meta) {
val json = meta.toJson()
val string = Jsoner.prettyPrint(Jsoner.serialize(json))
stream.write(string.toByteArray(Charsets.UTF_8))
stream.flush()
}
private fun Value.toJson(): Any {
return if (list.size == 1) {
when (type) {
ValueType.NUMBER -> number
ValueType.BOOLEAN -> boolean
else -> string
}
} else {
JsonArray().apply {
list.forEach { add(it.toJson()) }
}
}
}
private fun Meta.toJson(): JsonObject {
val builder = JsonObject()
nodeNames.forEach {
val nodes = getMetaList(it)
if (nodes.size == 1) {
builder[it] = nodes[0].toJson()
} else {
val array = JsonArray()
nodes.forEach { array.add(it.toJson()) }
builder[it] = array
}
}
valueNames.forEach {
builder[it] = getValue(it).toJson()
}
return builder
}
}
//private class JSONWriter : StringWriter() {
//
// private var indentlevel = 0
//
// override fun write(c: Int) {
// val ch = c.toChar()
// if (ch == '[' || ch == '{') {
// super.write(c)
// super.write("\n")
// indentlevel++
// writeIndentation()
// } else if (ch == ',') {
// super.write(c)
// super.write("\n")
// writeIndentation()
// } else if (ch == ']' || ch == '}') {
// super.write("\n")
// indentlevel--
// writeIndentation()
// super.write(c)
// } else if (ch == ':') {
// super.write(c)
// super.write(spaceaftercolon)
// } else {
// super.write(c)
// }
//
// }
//
// private fun writeIndentation() {
// for (i in 0 until indentlevel) {
// super.write(indentstring)
// }
// }
//
// companion object {
// internal val indentstring = " " //define as you wish
// internal val spaceaftercolon = " " //use "" if you don't want space after colon
// }
//}

View File

@ -0,0 +1 @@
hep.dataforge.io.JSONMetaType

View File

@ -0,0 +1,53 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.io
import hep.dataforge.get
import hep.dataforge.meta.buildMeta
import org.junit.Assert.assertEquals
import org.junit.Test
class JSONMetaTypeTest {
@Test
fun testRead() {
val json = """
{
"a" : 22,
"b" : {
"c" : [1, 2, [3.1, 3.2]]
"d" : "my string value"
}
}
""".trimMargin()
val meta = jsonMetaType.reader.readString(json)
assertEquals(22, meta["a"].int)
assertEquals(3.1, meta["b.c"][2][0].double,0.01)
}
@Test
fun testWrite(){
val meta = buildMeta {
"a" to 22
"b" to {
"c" to listOf(1, 2, listOf(3.1, 3.2))
"d" to "my string value"
}
}
val string = jsonMetaType.writer.writeString(meta)
println(string)
}
}

View File

@ -0,0 +1,4 @@
---
title: Descriptors
---

View File

@ -0,0 +1,22 @@
---
title: Logging and History
---
DataForge supports two separate logging system:
1. Generic logging system based on `slf4j` on JVM and possibly any other native logging on remote platform. It is used
for debugging and online information about the process. Logger could be obtained in any point of the program (e.g. via
`LoggerFactrory.getLogger(...)` in JVM). Critical elements and all `ContextAware` blocks automatically have a pre-defined
logger accessible via `logger` property.
2. Internal logging utilizes hierarchical structure called `History`. Each `Hisotory` object has a reference to `Chronocle`
which stores history entries called `Record`. Also any `History` but the root one which is `Global.history` must have a parent
`History`. Any `Record` entry appended to the history is automatically appended to its parent with appropriate trace element
which specifies where it entry comes from. One can also attach hooks to any chronicle to be triggered on entry addition.
Global history logging behavior is controlled via `Chronicler` context plugin, which is loaded by default into Global context.
History ususally could not be created on sight (only from context chronicler) and should be passed to the process explicitly.
The key difference between logger and history is that logs are intended for debugging and information and are discarded after
being read. The history on the other hand is supposed to be the important part of the analysis and should be stored with
analysis results after it is complete. It is strongly discouraged to use `History` in a performance-sensitive code. Also
it is bad idea to output any sensitive information to log since it could be discarded.

View File

@ -0,0 +1,31 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.actions;
/**
* <p>ActionEvent interface.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public interface ActionEvent {
/**
* <p>source.</p>
*
* @return a {@link hep.dataforge.actions.Action} object.
*/
Action source();
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.actions;
/**
* <p>ActionEventListener interface.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public interface ActionEventListener {
/**
* <p>listenTo.</p>
*
* @param event a {@link hep.dataforge.actions.ActionEvent} object.
*/
void listenTo(ActionEvent event);
}

View File

@ -0,0 +1,141 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.actions;
import hep.dataforge.context.BasicPlugin;
import hep.dataforge.context.Plugin;
import hep.dataforge.context.PluginDef;
import hep.dataforge.context.PluginFactory;
import hep.dataforge.meta.Meta;
import hep.dataforge.providers.Provides;
import hep.dataforge.providers.ProvidesNames;
import hep.dataforge.tables.ReadPointSetAction;
import hep.dataforge.tables.TransformTableAction;
import hep.dataforge.utils.Optionals;
import hep.dataforge.workspace.tasks.Task;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
/**
* A support manager to dynamically load actions and tasks into the context
*
* @author Alexander Nozik
*/
@PluginDef(name = "actions", group = "hep.dataforge", info = "A list of available actions and task for given context")
public class ActionManager extends BasicPlugin {
private final Map<String, Action> actionMap = new HashMap<>();
private final Map<String, Task> taskMap = new HashMap<>();
public ActionManager() {
//TODO move somewhere else
putAction(TransformTableAction.class);
putAction(ReadPointSetAction.class);
}
protected Optional<ActionManager> getParent() {
if (getContext().getParent() == null) {
return Optional.empty();
} else {
return getContext().getParent().provide("actions", ActionManager.class);
}
}
@Provides(Action.ACTION_TARGET)
public Optional<Action> optAction(String name) {
return Optionals.either(actionMap.get(name))
.or(getParent().flatMap(parent -> parent.optAction(name)))
.opt();
}
@Provides(Task.TASK_TARGET)
public Optional<Task> optTask(String name) {
return Optionals.either(taskMap.get(name))
.or(getParent().flatMap(parent -> parent.optTask(name)))
.opt();
}
public void put(Action action) {
if (actionMap.containsKey(action.getName())) {
LoggerFactory.getLogger(getClass()).warn("Duplicate action names in ActionManager.");
} else {
actionMap.put(action.getName(), action);
}
}
public void put(Task task) {
if (taskMap.containsKey(task.getName())) {
LoggerFactory.getLogger(getClass()).warn("Duplicate task names in ActionManager.");
} else {
taskMap.put(task.getName(), task);
}
}
/**
* Put a task into the manager using action construction by reflections. Action must have empty constructor
*
* @param actionClass a {@link java.lang.Class} object.
*/
public final void putAction(Class<? extends Action> actionClass) {
try {
put(actionClass.newInstance());
} catch (IllegalAccessException ex) {
throw new RuntimeException("Action must have default empty constructor to be registered.");
} catch (InstantiationException ex) {
throw new RuntimeException("Error while constructing Action", ex);
}
}
public final void putTask(Class<? extends Task> taskClass) {
try {
put(taskClass.getConstructor().newInstance());
} catch (IllegalAccessException ex) {
throw new RuntimeException("Task must have default empty constructor to be registered.");
} catch (Exception ex) {
throw new RuntimeException("Error while constructing Task", ex);
}
}
/**
* Stream of all available actions
*
* @return
*/
@ProvidesNames(Action.ACTION_TARGET)
public Stream<String> listActions() {
return this.actionMap.keySet().stream();
}
/**
* Stream of all available tasks
*
* @return
*/
@ProvidesNames(Task.TASK_TARGET)
public Stream<String> listTasks() {
return this.taskMap.keySet().stream();
}
public static class Factory extends PluginFactory {
@NotNull
@Override
public ActionManager build(@NotNull Meta meta) {
return new ActionManager();
}
@NotNull
@Override
public Class<? extends Plugin> getType() {
return ActionManager.class;
}
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.actions;
import hep.dataforge.data.NamedData;
import hep.dataforge.goals.Goal;
import hep.dataforge.io.history.Chronicle;
import hep.dataforge.meta.Meta;
/**
* The asynchronous result of the action
*
* @author Alexander Nozik
* @param <R>
*/
public class ActionResult<R> extends NamedData<R> {
private final Chronicle log;
public ActionResult(String name, Class<R> type, Goal<R> goal, Meta meta, Chronicle log) {
super(name, type, goal, meta);
this.log = log;
}
public Chronicle log() {
return log;
}
}

View File

@ -0,0 +1,23 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.actions;
/**
*
* @author Alexander Nozik
*/
public interface ActionStateListener {
void notifyActionStarted(Action action, Object argument);
void notifyActionFinished(Action action, Object result);
/**
* Notify action progress
* @param action
* @param progress the value between 0 and 1; negative values are ignored
* @param message
*/
void notifyAcionProgress(Action action, double progress, String message);
}

View File

@ -0,0 +1,52 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.actions;
import hep.dataforge.context.Context;
import hep.dataforge.data.DataNode;
import hep.dataforge.goals.GeneratorGoal;
import hep.dataforge.goals.Goal;
import hep.dataforge.io.history.Chronicle;
import hep.dataforge.meta.Meta;
import org.jetbrains.annotations.NotNull;
import java.util.stream.Stream;
/**
* An action that does not take any input data, only generates output. Each
* output token is generated separately.
*
* @author Alexander Nozik
*/
public abstract class GeneratorAction<R> extends GenericAction<Void, R> {
public GeneratorAction(@NotNull String name,@NotNull Class<R> outputType) {
super(name, Void.class, outputType);
}
@Override
public DataNode<R> run(Context context, DataNode<? extends Void> data, Meta actionMeta) {
Chronicle log = context.getHistory().getChronicle(getName());
Stream<ActionResult<R>> results = nameStream().map(name -> {
Goal<R> goal = new GeneratorGoal<>(getExecutorService(context, actionMeta), () -> generateData(name));
return new ActionResult<>(name, getOutputType(), goal, generateMeta(name), log);
});
return wrap(resultNodeName(), actionMeta, results);
}
protected abstract Stream<String> nameStream();
protected abstract Meta generateMeta(String name);
protected abstract R generateData(String name);
protected String resultNodeName() {
return "";
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.actions;
import hep.dataforge.data.DataNode;
import hep.dataforge.data.DataNodeBuilder;
import hep.dataforge.data.DataSet;
import hep.dataforge.data.NamedData;
import hep.dataforge.description.ValueDef;
import hep.dataforge.meta.Meta;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* The class to builder groups of content with annotation defined rules
*
* @author Alexander Nozik
*/
public class GroupBuilder {
/**
* Create grouping rule that creates groups for different values of value
* field with name {@code tag}
*
* @param tag
* @param defaultTagValue
* @return
*/
public static GroupRule byValue(final String tag, String defaultTagValue) {
return new GroupRule() {
@Override
public <T> List<DataNode<T>> group(DataNode<T> input) {
Map<String, DataNodeBuilder<T>> map = new HashMap<>();
input.forEach((NamedData<? extends T> data) -> {
String tagValue = data.getMeta().getString(tag, defaultTagValue);
if (!map.containsKey(tagValue)) {
DataNodeBuilder<T> builder = DataSet.Companion.edit(input.getType());
builder.setName(tagValue);
//builder.setMeta(new MetaBuilder(DEFAULT_META_NAME).putValue("tagValue", tagValue));
//PENDING share meta here?
map.put(tagValue, builder);
}
map.get(tagValue).add(data);
});
return map.values().stream().<DataNode<T>>map(DataNodeBuilder::build).collect(Collectors.toList());
}
};
}
@ValueDef(key = "byValue", required = true, info = "The name of annotation value by which grouping should be made")
@ValueDef(key = "defaultValue", def = "default", info = "Default value which should be used for content in which the grouping value is not presented")
public static GroupRule byMeta(Meta config) {
//TODO expand grouping options
if (config.hasValue("byValue")) {
return byValue(config.getString("byValue"), config.getString("defaultValue", "default"));
} else {
return Collections::singletonList;
}
}
public interface GroupRule {
<T> List<DataNode<T>> group(DataNode<T> input);
}
}

View File

@ -0,0 +1,170 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.actions;
import hep.dataforge.context.Context;
import hep.dataforge.data.Data;
import hep.dataforge.data.DataNode;
import hep.dataforge.data.NamedData;
import hep.dataforge.goals.AbstractGoal;
import hep.dataforge.goals.Goal;
import hep.dataforge.meta.Laminate;
import hep.dataforge.meta.Meta;
import hep.dataforge.meta.MetaBuilder;
import hep.dataforge.names.Name;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Action with multiple input data pieces but single output
*
* @param <T>
* @param <R>
* @author Alexander Nozik
*/
public abstract class ManyToOneAction<T, R> extends GenericAction<T, R> {
public ManyToOneAction(@NotNull String name, @NotNull Class<T> inputType, @NotNull Class<R> outputType) {
super(name, inputType, outputType);
}
@Override
@NotNull
public DataNode<R> run(Context context, DataNode<? extends T> set, Meta actionMeta) {
checkInput(set);
List<DataNode<T>> groups = buildGroups(context, (DataNode<T>) set, actionMeta);
return wrap(getResultName(set.getName(), actionMeta), set.getMeta(), groups.stream().map(group->runGroup(context, group,actionMeta)));
}
public ActionResult<R> runGroup(Context context, DataNode<T> data, Meta actionMeta) {
Meta outputMeta = outputMeta(data).build();
Goal<R> goal = new ManyToOneGoal(context, data, actionMeta, outputMeta);
String resultName = data.getName() == null ? getName() : data.getName();
return new ActionResult<>(resultName, getOutputType(), goal, outputMeta, context.getHistory().getChronicle(resultName));
}
protected List<DataNode<T>> buildGroups(Context context, DataNode<T> input, Meta actionMeta) {
return GroupBuilder.byMeta(inputMeta(context, input.getMeta(), actionMeta)).group(input);
}
/**
* Perform actual calculation
*
* @param nodeName
* @param input
* @param meta
* @return
*/
protected abstract R execute(Context context, String nodeName, Map<String, T> input, Laminate meta);
/**
* Build output meta for resulting object
*
* @param input
* @return
*/
protected MetaBuilder outputMeta(DataNode<T> input) {
MetaBuilder builder = new MetaBuilder(MetaBuilder.DEFAULT_META_NAME)
.putValue("name", input.getName())
.putValue("type", input.getType().getName());
input.dataStream().forEach((NamedData<? extends T> data) -> {
MetaBuilder dataNode = new MetaBuilder("data")
.putValue("name", data.getName());
if (!data.getType().equals(input.getType())) {
dataNode.putValue("type", data.getType().getName());
}
// if (!data.meta().isEmpty()) {
// dataNode.putNode(DataFactory.NODE_META_KEY, data.meta());
// }
builder.putNode(dataNode);
});
return builder;
}
/**
* An action to be performed before each group evaluation
*
* @param input
*/
protected void beforeGroup(Context context, DataNode<? extends T> input) {
}
/**
* An action to be performed after each group evaluation
*
* @param output
*/
protected void afterGroup(Context context, String groupName, Meta outputMeta, R output) {
}
/**
* Action goal {@code fainOnError()} delegate
*
* @return
*/
protected boolean failOnError() {
return true;
}
private class ManyToOneGoal extends AbstractGoal<R> {
private final Context context;
private final DataNode<T> data;
private final Meta actionMeta;
private final Meta outputMeta;
public ManyToOneGoal(Context context, DataNode<T> data, Meta actionMeta, Meta outputMeta) {
super(getExecutorService(context, actionMeta));
this.context = context;
this.data = data;
this.actionMeta = actionMeta;
this.outputMeta = outputMeta;
}
@Override
protected boolean failOnError() {
return ManyToOneAction.this.failOnError();
}
@Override
public Stream<Goal<?>> dependencies() {
return data.nodeGoal().dependencies();
}
@Override
protected R compute() throws Exception {
Laminate meta = inputMeta(context, data.getMeta(), actionMeta);
Thread.currentThread().setName(Name.Companion.joinString(getThreadName(actionMeta), data.getName()));
beforeGroup(context, data);
// In this moment, all the data is already calculated
Map<String, T> collection = data.dataStream()
.filter(Data::isValid) // filter valid data only
.collect(Collectors.toMap(NamedData::getName, Data::get));
R res = execute(context, data.getName(), collection, meta);
afterGroup(context, data.getName(), outputMeta, res);
return res;
}
}
}

View File

@ -0,0 +1,67 @@
package hep.dataforge.actions;
import hep.dataforge.context.Context;
import hep.dataforge.data.Data;
import hep.dataforge.data.DataNode;
import hep.dataforge.data.DataNodeBuilder;
import hep.dataforge.data.DataTree;
import hep.dataforge.goals.Goal;
import hep.dataforge.goals.PipeGoal;
import hep.dataforge.meta.Laminate;
import hep.dataforge.meta.Meta;
import hep.dataforge.names.Name;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
/**
* A split action that creates multiple Data from each input element (some input elements could be ignored)
* Created by darksnake on 28-Jan-17.
*/
public abstract class OneToManyAction<T, R> extends GenericAction<T, R> {
public OneToManyAction(@NotNull String name, @NotNull Class<T> inputType, @NotNull Class<R> outputType) {
super(name, inputType, outputType);
}
@Override
public DataNode<R> run(Context context, DataNode<? extends T> data, Meta actionMeta) {
checkInput(data);
DataNodeBuilder<R> builder = DataTree.Companion.edit(getOutputType());
data.forEach(datum -> {
String inputName = datum.getName();
Laminate inputMeta = new Laminate(datum.getMeta(), actionMeta);
Map<String, Meta> metaMap = prepareMeta(context, inputName, inputMeta);
metaMap.forEach((outputName, outputMeta) -> {
Goal<R> goal = new PipeGoal<>(datum.getGoal(), input -> execute(context, inputName, outputName, input, inputMeta));
Data<R> res = new Data<R>(getOutputType(), goal, outputMeta);
builder.putData(placement(inputName, outputName), res, false);
});
});
return builder.build();
}
/**
* The placement rule for result Data. By default each input element is transformed into a node with
*
* @param inputName
* @param outputName
* @return
*/
protected String placement(String inputName, String outputName) {
return Name.Companion.joinString(inputName, outputName);
}
//TODO add node meta
/**
* @param context
* @param inputName
* @param meta
* @return
*/
protected abstract Map<String, Meta> prepareMeta(Context context, String inputName, Laminate meta);
protected abstract R execute(Context context, String inputName, String outputName, T input, Laminate meta);
}

View File

@ -0,0 +1,150 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.actions;
import hep.dataforge.context.Context;
import hep.dataforge.context.Global;
import hep.dataforge.data.DataNode;
import hep.dataforge.data.NamedData;
import hep.dataforge.goals.PipeGoal;
import hep.dataforge.io.history.Chronicle;
import hep.dataforge.meta.Laminate;
import hep.dataforge.meta.Meta;
import hep.dataforge.names.Name;
import kotlin.Pair;
import org.slf4j.Logger;
/**
* A template to builder actions that reflect strictly one to one content
* transformations
*
* @param <T>
* @param <R>
* @author Alexander Nozik
* @version $Id: $Id
*/
public abstract class OneToOneAction<T, R> extends GenericAction<T, R> {
public OneToOneAction(String name, Class<T> inputType, Class<R> outputType) {
super(name,inputType,outputType);
}
@Override
public DataNode<R> run(Context context, DataNode<? extends T> set, Meta actionMeta) {
checkInput(set);
if (set.isEmpty()) {
throw new RuntimeException(getName() + ": Running 1 to 1 action on empty data node");
}
return wrap(
set.getName(),
set.getMeta(),
set.dataStream(true).map(data -> runOne(context, data, actionMeta))
);
}
/**
* Build asynchronous result for single data. Data types separated from
* action generics to be able to operate maps instead of raw data
*
* @param data
* @param actionMeta
* @return
*/
protected ActionResult<R> runOne(Context context, NamedData<? extends T> data, Meta actionMeta) {
if (!this.getInputType().isAssignableFrom(data.getType())) {
throw new RuntimeException(String.format("Type mismatch in action %s. %s expected, but %s recieved",
getName(), getInputType().getName(), data.getType().getName()));
}
Pair<String, Meta> resultParamters = outputParameters(context, data, actionMeta);
Laminate meta = inputMeta(context, data.getMeta(), actionMeta);
String resultName = resultParamters.getFirst();
Meta outputMeta = resultParamters.getSecond();
PipeGoal<? extends T, R> goal = new PipeGoal<>(getExecutorService(context, meta), data.getGoal(),
input -> {
Thread.currentThread().setName(Name.Companion.joinString(getThreadName(actionMeta), resultName));
return transform(context, resultName, input, meta);
}
);
return new ActionResult<>(resultName, getOutputType(), goal, outputMeta, context.getHistory().getChronicle(resultName));
}
protected Chronicle getLog(Context context, String dataName) {
return context.getHistory().getChronicle(Name.Companion.joinString(dataName, getName()));
}
/**
* @param name name of the input item
* @param input input data
* @param inputMeta combined meta for this evaluation. Includes data meta,
* group meta and action meta
* @return
*/
private R transform(Context context, String name, T input, Laminate inputMeta) {
beforeAction(context, name, input, inputMeta);
R res = execute(context, name, input, inputMeta);
afterAction(context, name, res, inputMeta);
return res;
}
/**
* Utility method to run action outside of context or execution chain
*
* @param input
* @param metaLayers
* @return
*/
public R simpleRun(T input, Meta... metaLayers) {
return transform(Global.INSTANCE, "simpleRun", input, inputMeta(Global.INSTANCE, metaLayers));
}
protected abstract R execute(Context context, String name, T input, Laminate meta);
/**
* Build output meta for given data. This meta is calculated on action call
* (no lazy calculations). By default output meta is the same as input data
* meta.
*
* @param actionMeta
* @param data
* @return
*/
protected Pair<String, Meta> outputParameters(Context context, NamedData<? extends T> data, Meta actionMeta) {
return new Pair<>(getResultName(data.getName(), actionMeta), data.getMeta());
}
protected void afterAction(Context context, String name, R res, Laminate meta) {
Logger logger = getLogger(context, meta);
if (res == null) {
logger.error("Action {} returned 'null' on data {}", getName(), name);
throw new RuntimeException("Null result of action");//TODO add emty data to handle this
}
logger.debug("Action '{}[{}]' is finished", getName(), name);
}
protected void beforeAction(Context context, String name, T datum, Laminate meta) {
if (context.getBoolean("actions.reportStart", false)) {
report(context, name, "Starting action {} on data with name {} with following configuration: \n\t {}", getName(), name, meta.toString());
}
getLogger(context, meta).debug("Starting action '{}[{}]'", getName(), name);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.connections;
import java.util.Optional;
import java.util.stream.Stream;
public interface AutoConnectible extends Connectible {
ConnectionHelper getConnectionHelper();
@Override
default void connect(Connection connection, String... roles) {
getConnectionHelper().connect(connection, roles);
}
@Override
default <T> Stream<T> connections(String role, Class<T> type) {
return getConnectionHelper().connections(role, type);
}
default <T> Optional<T> optConnection(String role, Class<T> type) {
return getConnectionHelper().optConnection(role, type);
}
@Override
default void disconnect(Connection connection) {
getConnectionHelper().disconnect(connection);
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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.
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.connections;
import hep.dataforge.UtilsKt;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
* Something that could be connected
*
* @author Alexander Nozik
*/
public interface Connectible {
/**
* Register a new connection with given roles
*
* @param connection
* @param roles
*/
void connect(Connection connection, String... roles);
/**
* Get a stream of all connections with given role and type. Role could be regexp
*
* @param role
* @param type
* @param <T>
* @return
*/
<T> Stream<T> connections(String role, Class<T> type);
/**
* Disconnect given connection
*
* @param connection
*/
void disconnect(Connection connection);
/**
* For each connection of given class and role. Role may be empty, but type
* is mandatory
*
* @param <T>
* @param role
* @param type
* @param action
*/
default <T> void forEachConnection(String role, Class<T> type, Consumer<T> action) {
connections(role, type).forEach(action);
}
default <T> void forEachConnection(Class<T> type, Consumer<T> action) {
forEachConnection(".*", type, action);
}
/**
* A list of all available roles
*
* @return
*/
default List<RoleDef> roleDefs() {
return UtilsKt.listAnnotations(this.getClass(), RoleDef.class, true);
}
/**
* Find a role definition for given name. Null if not found.
*
* @param name
* @return
*/
default Optional<RoleDef> optRoleDef(String name) {
return roleDefs().stream().filter((roleDef) -> roleDef.name().equals(name)).findFirst();
}
/**
* A quick way to find if this object accepts connection with given role
*
* @param name
* @return
*/
default boolean acceptsRole(String name) {
return roleDefs().stream().anyMatch((roleDef) -> roleDef.name().equals(name));
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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.
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.connections;
import hep.dataforge.context.Context;
import hep.dataforge.meta.Meta;
import java.util.Objects;
import java.util.Optional;
/**
* A connection which could be applied to object that could receive connection.
* Usually connection does not invoke {@code open} method itself, but delegates it to {@code Connectible}
*
* @author Alexander Nozik
*/
public interface Connection extends AutoCloseable {
/**
* Create a connection using context connection factory provider if it is possible
*
* @param context
* @param meta
* @return
*/
static Optional<Connection> buildConnection(Connectible obj, Context context, Meta meta) {
String type = meta.getString("type");
return Optional.ofNullable(context.findService(ConnectionFactory.class, it -> Objects.equals(it.getType(), type)))
.map(it -> it.build(obj, context, meta));
}
String LOGGER_ROLE = "log";
String EVENT_HANDLER_ROLE = "eventHandler";
default boolean isOpen() {
return true;
}
default void open(Object object) throws Exception {
//do nothing
}
@Override
default void close() throws Exception {
//do nothing
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.connections;
import hep.dataforge.context.Context;
import hep.dataforge.meta.Meta;
/**
* A factory SPI class for connections
*/
public interface ConnectionFactory {
String getType();
/**
*
* @param obj an object for which this connections is intended
* @param context context of the connection (could be different from connectible context)
* @param meta configuration for connection
* @param <T> type of the connectible
* @return
*/
<T extends Connectible> Connection build(T obj, Context context, Meta meta);
}

View File

@ -0,0 +1,163 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.connections;
import hep.dataforge.Named;
import hep.dataforge.context.Context;
import hep.dataforge.meta.Meta;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Stream;
/**
* A helper class to manage Connectible objects in the same fashion
*/
public class ConnectionHelper implements Connectible {
//TODO isolate errors inside connections
private final Map<Connection, Set<String>> connections = new HashMap<>();
private final Connectible caller;
private final Logger logger = LoggerFactory.getLogger(getClass());
public ConnectionHelper(Connectible caller) {
this.caller = caller;
}
// public Logger getLogger() {
// return logger;
// }
/**
* Attach connection
*
* @param connection
* @param roles
*/
@Override
public synchronized void connect(Connection connection, String... roles) {
logger.info("Attaching connection {} with roles {}", connection.toString(), String.join(", ", roles));
//Checking if connection could serve given roles
for (String role : roles) {
if (!acceptsRole(role)) {
logger.warn("The connectible does not support role {}", role);
} else {
roleDefs().stream().filter((roleDef) -> roleDef.name().equals(role)).forEach(rd -> {
if (!rd.objectType().isInstance(connection)) {
logger.error("Connection does not meet type requirement for role {}. Must be {}.",
role, rd.objectType().getName());
}
});
}
}
Set<String> roleSet = new HashSet<>(Arrays.asList(roles));
if (this.connections.containsKey(connection)) {
//updating roles of existing connection
connections.get(connection).addAll(roleSet);
} else {
this.connections.put(connection, roleSet);
}
try {
logger.debug("Opening connection {}", connection.toString());
connection.open(caller);
} catch (Exception ex) {
throw new RuntimeException("Can not open connection", ex);
}
}
/**
* Build connection (or connections if meta has multiple "connection" entries) and connect
*
* @param context
* @param meta
*/
public void connect(Context context, Meta meta) {
if (meta.hasMeta("connection")) {
meta.getMetaList("connection").forEach(it -> connect(context, it));
} else {
String[] roles = meta.getStringArray("role", new String[]{});
Connection.buildConnection(caller, context, meta).ifPresent(connection -> connect(connection, roles));
}
}
@Override
public synchronized void disconnect(Connection connection) {
if (connections.containsKey(connection)) {
String conName = Named.Companion.nameOf(connection);
try {
logger.debug("Closing connection {}", conName);
connection.close();
} catch (Exception ex) {
logger.error("Can not close connection", ex);
}
logger.info("Detaching connection {}", conName);
this.connections.remove(connection);
}
}
/**
* Get a stream of all connections for a given role. Stream could be empty
*
* @param role
* @param type
* @param <T>
* @return
*/
@Override
public <T> Stream<T> connections(String role, Class<T> type) {
return connections.entrySet().stream()
.filter(entry -> type.isInstance(entry.getKey()))
.filter(entry -> role.isEmpty() || entry.getValue().stream().anyMatch(r -> r.matches(role)))
.map(entry -> type.cast(entry.getKey()));
}
public <T> Stream<T> connections(Class<T> type) {
return connections.entrySet().stream()
.filter(entry -> type.isInstance(entry.getKey()))
.map(entry -> type.cast(entry.getKey()));
}
/**
* Return a unique connection or first connection satisfying condition
*
* @param role
* @param type
* @param <T>
* @return
*/
public <T> Optional<T> optConnection(String role, Class<T> type) {
return connections(role, type).findFirst();
}
@Override
public boolean acceptsRole(String name) {
return caller.acceptsRole(name);
}
@Override
public List<RoleDef> roleDefs() {
return caller.roleDefs();
}
@Override
public Optional<RoleDef> optRoleDef(String name) {
return caller.optRoleDef(name);
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.connections;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ConnectsTo {
String type();
String[] roles() default {};
Class<? extends Connection> cl();
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.connections;
import hep.dataforge.values.Value;
import hep.dataforge.values.ValueFactory;
/**
* Created by darksnake on 25-May-17.
*/
public interface NamedValueListener {
void pushValue(String valueName, Value value);
default void pushValue(String valueName, Object obj) {
pushValue(valueName, ValueFactory.of(obj));
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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.
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.connections;
import java.lang.annotation.*;
/**
* The role of connection served by this device
*
* @author Alexander Nozik
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Repeatable(RoleDefs.class)
public @interface RoleDef {
/**
* Role name
*
* @return
*/
String name();
/**
* The type of the object that could play this role
*
* @return
*/
Class objectType() default Object.class;
/**
* If true then only one connection of this role is allowed per object
* @return
*/
boolean unique() default false;
/**
* Role description
*
* @return
*/
String info() default "";
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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.
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.connections;
import java.lang.annotation.*;
/**
*
* @author Alexander Nozik
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RoleDefs {
RoleDef[] value();
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2018 Alexander Nozik.
*
* 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 hep.dataforge.connections;
import hep.dataforge.values.Value;
import hep.dataforge.values.ValueFactory;
/**
*
* @author Alexander Nozik
*/
public interface ValueListener {
void pushValue(Value value);
default void pushValue(Object obj){
pushValue(ValueFactory.of(obj));
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.description;
import java.lang.annotation.*;
/**
* <p>
* NodeDef class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Repeatable(NodeDefs.class)
public @interface NodeDef {
String key();
String info() default "";
boolean multiple() default false;
boolean required() default false;
String[] tags() default {};
/**
* A list of child value descriptors
*/
ValueDef[] values() default {};
/**
* A target class for this node to describe
* @return
*/
Class type() default Object.class;
/**
* The DataForge path to the resource containing the description. Following targets are supported:
* <ol>
* <li>resource</li>
* <li>file</li>
* <li>class</li>
* <li>method</li>
* <li>property</li>
* </ol>
*
* Does not work if [type] is provided
*
* @return
*/
String descriptor() default "";
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.description;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* <p>NodeDefs class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface NodeDefs {
NodeDef[] value();
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.description;
import java.lang.annotation.*;
/**
* The annotation defining Action name, info, input and output types.
*
* @author Alexander Nozik
* @version $Id: $Id
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TypedActionDef {
String name();
String info() default "";
Class<?> inputType() default Object.class;
Class<?> outputType() default Object.class;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.description;
import hep.dataforge.values.ValueType;
import java.lang.annotation.*;
/**
* Декларация параметра аннотации, который исползуется в контенте или методе,
* параметром которого является аннотация
*
* @author Alexander Nozik
* @version $Id: $Id
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Repeatable(ValueDefs.class)
public @interface ValueDef {
String key();
ValueType[] type() default {ValueType.STRING};
boolean multiple() default false;
String def() default "";
String info() default "";
boolean required() default true;
String[] allowed() default {};
Class enumeration() default Object.class;
String[] tags() default {};
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.description;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* <p>ValueDefs class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ValueDefs {
ValueDef[] value();
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.events;
import hep.dataforge.meta.Meta;
import hep.dataforge.meta.MetaBuilder;
import hep.dataforge.meta.SimpleMetaMorph;
import hep.dataforge.utils.DateTimeUtils;
import java.time.Instant;
/**
* A metamorph representing framework event
*
* @author Alexander Nozik
*/
public class Event extends SimpleMetaMorph {
public static final String EVENT_PRIORITY_KEY = "priority";
public static final String EVENT_TYPE_KEY = "type";
public static final String EVENT_SOURCE_KEY = "sourceTag";
public static final String EVENT_TIME_KEY = "time";
/**
* Create an event with given basic parameters and additional meta data. All
* values except type could be null or empty
*
* @param type
* @param source
* @param priority
* @param time
* @param additionalMeta
*/
public static Event make(String type, String source, int priority, Instant time, Meta additionalMeta) {
MetaBuilder builder = new MetaBuilder("event");
if (additionalMeta != null) {
builder.update(additionalMeta.getBuilder());
}
builder.setValue(EVENT_TYPE_KEY, type);
if (time == null) {
time = DateTimeUtils.now();
}
builder.setValue(EVENT_TIME_KEY, time);
if (source != null && !source.isEmpty()) {
builder.setValue(EVENT_SOURCE_KEY, source);
}
if (priority != 0) {
builder.setValue(EVENT_PRIORITY_KEY, priority);
}
return new Event(builder.build());
}
//TODO add source context to event?
public Event(Meta meta) {
super(meta);
}
public int priority() {
return getMeta().getInt(EVENT_PRIORITY_KEY, 0);
}
public String type() {
return getMeta().getString(EVENT_TYPE_KEY);
}
public String sourceTag() {
return getMeta().getString(EVENT_SOURCE_KEY, "");
}
public Instant time() {
return getMeta().getValue(EVENT_TIME_KEY).getTime();
}
// /**
// * get event string representation (header) to write in logs
// *
// * @return
// */
// @Override
// String toString();
}

View File

@ -0,0 +1,97 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.events;
import hep.dataforge.meta.Meta;
import hep.dataforge.meta.MetaBuilder;
import hep.dataforge.utils.DateTimeUtils;
import hep.dataforge.utils.GenericBuilder;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import static hep.dataforge.events.Event.*;
/**
* Th builder class for events
*
* @author Alexander Nozik
*/
public abstract class EventBuilder<E extends EventBuilder> implements GenericBuilder<Event, E> {
protected final MetaBuilder builder = new MetaBuilder("event");
private final Map<String, Object> objectMap = new HashMap<>();
protected EventBuilder(String type) {
this.builder.setValue(Event.EVENT_TYPE_KEY, type);
}
public static EventBuilder make(String type) {
return new ConcreteEventBuilder(type);
}
public EventBuilder setTime(Instant time) {
builder.setValue(EVENT_TIME_KEY, time);
return this;
}
/**
* Set time of the event to current time
*
* @return
*/
public EventBuilder setTime() {
builder.setValue(EVENT_TIME_KEY, DateTimeUtils.now());
return this;
}
public EventBuilder setSource(String source) {
builder.setValue(EVENT_SOURCE_KEY, source);
return this;
}
public EventBuilder setPriority(int priority) {
builder.setValue(EVENT_PRIORITY_KEY, priority);
return this;
}
public EventBuilder setMetaNode(String nodeName, Meta... nodes) {
builder.setNode(nodeName, nodes);
return this;
}
public EventBuilder setMetaValue(String valueName, Object value) {
builder.setValue(valueName, value);
return this;
}
public Meta buildEventMeta() {
if (!builder.hasValue(EVENT_TIME_KEY)) {
setTime();
}
return builder.build();
}
@Override
public Event build() {
return new Event(buildEventMeta());
}
private static class ConcreteEventBuilder extends EventBuilder<EventBuilder> {
public ConcreteEventBuilder(String type) {
super(type);
}
@Override
public EventBuilder self() {
return this;
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.events;
/**
* A handler for events
* @author Alexander Nozik
*/
@FunctionalInterface
public interface EventHandler{
boolean pushEvent(Event event);
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.events;
/**
* An interface marking an object that can dispatch messages (send it to named
* responder)
*
* @author Alexander Nozik
*/
public interface EventTransmitter {
/**
* Send message and return true if message is successfully sent
* @param address
* @param event
* @return
*/
boolean send(String address, Event event);
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* <p>AnonymousNotAlowedException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class AnonymousNotAlowedException extends NamingException {
/**
* Creates a new instance of <code>AnonymousNotAlowedException</code>
* without detail message.
*/
public AnonymousNotAlowedException() {
}
/**
* Constructs an instance of <code>AnonymousNotAlowedException</code> with
* the specified detail message.
*
* @param msg the detail message.
*/
public AnonymousNotAlowedException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* <p>ChainPathNotSupportedException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class ChainPathNotSupportedException extends NamingException {
/**
* Creates a new instance of <code>ChainPathNotSupportedException</code>
* without detail message.
*/
public ChainPathNotSupportedException(){
}
/**
* Constructs an instance of <code>ChainPathNotSupportedException</code>
* with the specified detail message.
*
* @param msg the detail message.
*/
public ChainPathNotSupportedException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
import hep.dataforge.meta.Meta;
/**
* Ошибка парсинга аннотации. В общем случае не обрабатывается
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class ConfigurationException extends RuntimeException {
Meta an;
/**
* Creates a new instance of <code>AnnotationParseException</code> without
* detail message.
*
* @param source a {@link hep.dataforge.meta.Meta} object.
*/
public ConfigurationException(Meta source) {
this.an = source;
}
/**
* Constructs an instance of <code>AnnotationParseException</code> with the
* specified detail message.
*
* @param source a {@link hep.dataforge.meta.Meta} object.
* @param msg the detail message.
*/
public ConfigurationException(Meta source, String msg) {
super(msg);
this.an = source;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* <p>ContentException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class ContentException extends RuntimeException {
/**
* Creates a new instance of <code>ContentException</code> without detail
* message.
*/
public ContentException() {
}
/**
* Constructs an instance of <code>ContentException</code> with the
* specified detail message.
*
* @param msg the detail message.
*/
public ContentException(String msg) {
super(msg);
}
/**
* <p>Constructor for ContentException.</p>
*
* @param string a {@link java.lang.String} object.
* @param thrwbl a {@link java.lang.Throwable} object.
*/
public ContentException(String string, Throwable thrwbl) {
super(string, thrwbl);
}
}

View File

@ -0,0 +1,32 @@
package hep.dataforge.exceptions;
import hep.dataforge.Named;
public class ContextLockException extends RuntimeException {
private final Object locker;
public ContextLockException(Object locker) {
this.locker = locker;
}
public ContextLockException() {
this.locker = null;
}
private String getObjectName() {
if (locker instanceof Named) {
return locker.getClass().getSimpleName() + ":" + ((Named) locker).getName();
} else {
return locker.getClass().getSimpleName();
}
}
@Override
public String getMessage() {
if (locker == null) {
return "Context is locked";
} else {
return "Context is locked by " + getObjectName();
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* <p>DataFormatException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class DataFormatException extends NamingException {
/**
* Creates a new instance of <code>DataSetFormatException</code> without
* detail message.
*/
public DataFormatException() {
}
/**
* Constructs an instance of <code>DataSetFormatException</code> with the
* specified detail message.
*
* @param msg the detail message.
*/
public DataFormatException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* <p>DescriptionNotFoundException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class DescriptionNotFoundException extends DescriptorException {
/**
* Creates a new instance of <code>DescriptionNotFoundException</code>
* without detail message.
*/
public DescriptionNotFoundException() {
}
/**
* Constructs an instance of <code>DescriptionNotFoundException</code> with
* the specified detail message.
*
* @param msg the detail message.
*/
public DescriptionNotFoundException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* <p>DescriptorException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class DescriptorException extends RuntimeException {
/**
* Creates a new instance of <code>DescriptorException</code> without detail
* message.
*/
public DescriptorException() {
}
/**
* Constructs an instance of <code>DescriptorException</code> with the
* specified detail message.
*
* @param msg the detail message.
*/
public DescriptorException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* <p>DuplicateDescriptionException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class DuplicateDescriptionException extends DescriptorException {
private final String name;
private final boolean forElement;
public DuplicateDescriptionException(String name, boolean forElement) {
this.name = name;
this.forElement = forElement;
}
@Override
public String getMessage() {
if(forElement){
return String.format("Duplicate description for element %s", name);
} else {
return String.format("Duplicate description for parameter %s", name);
}
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
*
* @author darksnake
*/
public class EnvelopeFormatException extends RuntimeException {
/**
* Creates a new instance of <code>EnvelopeFormatException</code> without
* detail message.
*/
public EnvelopeFormatException() {
}
/**
* Constructs an instance of <code>EnvelopeFormatException</code> with the
* specified detail message.
*
* @param msg the detail message.
*/
public EnvelopeFormatException(String msg) {
super(msg);
}
public EnvelopeFormatException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,57 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.exceptions;
import hep.dataforge.meta.Meta;
/**
* An exception thrown in case message target is not found by current {@code Responder}
* @author Alexander Nozik
*/
public class EnvelopeTargetNotFoundException extends RuntimeException {
private String targetType;
private String targetName;
private Meta targetMeta;
public EnvelopeTargetNotFoundException(String targetName) {
this.targetName = targetName;
}
public EnvelopeTargetNotFoundException(String targetType, String targetName) {
this.targetType = targetType;
this.targetName = targetName;
}
public EnvelopeTargetNotFoundException(Meta targetMeta) {
this.targetMeta = targetMeta;
}
public EnvelopeTargetNotFoundException(String targetName, Meta targetMeta) {
this.targetName = targetName;
this.targetMeta = targetMeta;
}
public EnvelopeTargetNotFoundException(String targetType, String targetName, Meta targetMeta) {
this.targetType = targetType;
this.targetName = targetName;
this.targetMeta = targetMeta;
}
public String getTargetType() {
return targetType;
}
public String getTargetName() {
return targetName;
}
public Meta getTargetMeta() {
return targetMeta;
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* <p>NameNotFoundException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class NameNotFoundException extends NamingException {
private String name;
/**
* Creates a new instance of
* <code>NameNotFoundException</code> without detail message.
*/
public NameNotFoundException() {
super();
}
/**
* Constructs an instance of
* <code>NameNotFoundException</code> with the specified detail message.
*
* @param name a {@link java.lang.String} object.
*/
public NameNotFoundException(String name) {
this.name = name;
}
/**
* <p>Constructor for NameNotFoundException.</p>
*
* @param name a {@link java.lang.String} object.
* @param msg a {@link java.lang.String} object.
*/
public NameNotFoundException(String name, String msg) {
super(msg);
this.name = name;
}
@Override
public String getMessage() {
return super.getMessage() + buildMessage();
}
protected String buildMessage() {
return " The element with name \"" + getName() + "\" is not found.";
}
/**
* <p>Getter for the field <code>name</code>.</p>
*
* @return a {@link java.lang.String} object.
*/
public String getName() {
return this.name;
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* <p>NamingException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class NamingException extends RuntimeException {
/**
* Creates a new instance of <code>NewException</code> without detail
* message.
*/
public NamingException() {
}
/**
* Constructs an instance of <code>NewException</code> with the specified
* detail message.
*
* @param msg the detail message.
*/
public NamingException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,14 @@
package hep.dataforge.exceptions;
/**
* Created by darksnake on 12-Nov-16.
*/
public class NonEmptyMetaMorphException extends IllegalStateException {
private Class<?> type;
public NonEmptyMetaMorphException(Class<?> type) {
super(String.format("Can not update non-empty MetaMorph for class '%s'", type.getSimpleName()));
this.type = type;
}
}

View File

@ -0,0 +1,32 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.exceptions;
import hep.dataforge.connections.Connection;
/**
* An exception thrown when the request is sent to the closed connection
*
* @author Alexander Nozik
*/
public class NotConnectedException extends Exception {
Connection connection;
public NotConnectedException(Connection connection) {
this.connection = connection;
}
public Connection getConnection() {
return connection;
}
@Override
public String getMessage() {
return "The connection is not open";
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* This exception is used when some parameters or functions are not defined by
* user.
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class NotDefinedException extends IllegalStateException {
/**
* Creates a new instance of
* <code>NotDefinedException</code> without detail message.
*/
public NotDefinedException() {
}
/**
* Constructs an instance of
* <code>NotDefinedException</code> with the specified detail message.
*
* @param msg the detail message.
*/
public NotDefinedException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* Ошибка синтаксиса пути
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class PathSyntaxException extends NamingException {
/**
* Creates a new instance of <code>PathSyntaxException</code> without detail
* message.
*/
public PathSyntaxException() {
}
/**
* Constructs an instance of <code>PathSyntaxException</code> with the
* specified detail message.
*
* @param msg the detail message.
*/
public PathSyntaxException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
/**
* <p>TargetNotProvidedException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class TargetNotProvidedException extends NameNotFoundException {
/**
* Creates a new instance of <code>TargetNotProvided</code> without detail
* message.
*/
public TargetNotProvidedException(String target) {
super(target);
}
/**
* Constructs an instance of <code>TargetNotProvided</code> with the
* specified detail message.
*
* @param msg the detail message.
*/
public TargetNotProvidedException(String msg, String target) {
super(msg, target);
}
@Override
protected String buildMessage() {
return "The target \"" + getName() + "\" is not provided.";
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2015 Alexander Nozik.
*
* 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 hep.dataforge.exceptions;
import hep.dataforge.values.Value;
import hep.dataforge.values.ValueType;
/**
* <p>ValueConversionException class.</p>
*
* @author Alexander Nozik
* @version $Id: $Id
*/
public class ValueConversionException extends RuntimeException {
Value value;
ValueType to;
/**
* <p>Constructor for ValueConversionException.</p>
*
* @param value a {@link hep.dataforge.values.Value} object.
* @param to a {@link hep.dataforge.values.ValueType} object.
*/
public ValueConversionException(Value value, ValueType to) {
this.value = value;
this.to = to;
}
/**
* {@inheritDoc}
*/
@Override
public String getMessage() {
return String.format("Failed to convert value '%s' of type %s to %s", value.getString(), value.getType(), to.name());
}
}

View File

@ -0,0 +1,148 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.goals;
import hep.dataforge.utils.ReferenceRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
/**
* @author Alexander Nozik
*/
public abstract class AbstractGoal<T> implements Goal<T> {
private final ReferenceRegistry<GoalListener<T>> listeners = new ReferenceRegistry<>();
private final Executor executor;
protected final GoalResult result = new GoalResult();
private CompletableFuture<?> computation;
private Thread thread;
public AbstractGoal(Executor executor) {
this.executor = executor;
}
public AbstractGoal() {
this.executor = ForkJoinPool.commonPool();
}
protected Logger getLogger() {
return LoggerFactory.getLogger(getClass());
}
@Override
public synchronized void run() {
if (!isRunning()) {
//start all dependencies so they will occupy threads
computation = CompletableFuture
.allOf(dependencies()
.map(dep -> {
dep.run();//starting all dependencies
return dep.asCompletableFuture();
})
.toArray(CompletableFuture[]::new))
.whenCompleteAsync((res, err) -> {
if (err != null) {
getLogger().error("One of goal dependencies failed with exception", err);
if (failOnError()) {
this.result.completeExceptionally(err);
}
}
try {
thread = Thread.currentThread();
//trigger start hooks
listeners.forEach(GoalListener::onGoalStart);
T r = compute();
//triggering result hooks
listeners.forEach(listener -> listener.onGoalComplete(r));
this.result.complete(r);
} catch (Exception ex) {
//trigger exception hooks
getLogger().error("Exception during goal execution", ex);
listeners.forEach(listener -> listener.onGoalFailed(ex));
this.result.completeExceptionally(ex);
} finally {
thread = null;
}
}, executor);
}
}
public Executor getExecutor() {
return executor;
}
protected abstract T compute() throws Exception;
/**
* If true the goal will result in error if any of dependencies throws exception.
* Otherwise it will be calculated event if some of dependencies are failed.
*
* @return
*/
protected boolean failOnError() {
return true;
}
/**
* Abort internal goals process without canceling result. Use with
* care
*/
protected void abort() {
if (isRunning()) {
if (this.computation != null) {
this.computation.cancel(true);
}
if (thread != null) {
thread.interrupt();
}
}
}
public boolean isRunning() {
return this.result.isDone() || this.computation != null;
}
/**
* Abort current goals if it is in progress and set result. Useful for
* caching purposes.
*
* @param result
*/
public final synchronized boolean complete(T result) {
abort();
return this.result.complete(result);
}
@Override
public void registerListener(GoalListener<T> listener) {
listeners.add(listener,true);
}
@Override
public CompletableFuture<T> asCompletableFuture() {
return result;
}
protected class GoalResult extends CompletableFuture<T> {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (mayInterruptIfRunning) {
abort();
}
return super.cancel(mayInterruptIfRunning);
}
}
}

Some files were not shown because too many files have changed in this diff Show More