ManualComputation.java
/*
* MIT License
*
* Copyright (c) 2023-2024 Eugene Terekhov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package ru.ewc.decita.manual;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.SneakyThrows;
import org.yaml.snakeyaml.Yaml;
import ru.ewc.decita.ComputationContext;
import ru.ewc.decita.DecisionTable;
import ru.ewc.decita.DecitaException;
import ru.ewc.decita.DecitaFacade;
import ru.ewc.decita.InMemoryStorage;
import ru.ewc.decita.Locator;
import ru.ewc.decita.Locators;
import ru.ewc.decita.input.ContentReader;
import ru.ewc.decita.input.PlainTextContentReader;
/**
* I am a unique instance of a {@link DecisionTable} computation.
*
* @since 0.2.2
*/
@SuppressWarnings("PMD.ProhibitPublicStaticMethods")
public class ManualComputation {
/**
* An instance of object that reads all the tables from disk.
*/
private final ContentReader tables;
/**
* A URI pointing to a state yaml file.
*/
private final URI state;
/**
* Default Ctor.
*/
public ManualComputation() {
this(() -> new Locators(Map.of()), null);
}
/**
* Ctor.
*
* @param tables Reader that can read tables data from the file system.
* @param state Path to yaml describing the current system's state.
*/
private ManualComputation(final ContentReader tables, final URI state) {
this.tables = tables;
this.state = state;
}
/**
* Converts a string representation of the file system path to a correct URI.
*
* @param path File system path as a String.
* @return URI that corresponds to a given path.
*/
public static URI uriFrom(final String path) {
final StringBuilder result = new StringBuilder("file:/");
if (path.charAt(0) == '/') {
result.append(path.replace('\\', '/').substring(1));
} else {
result.append(path.replace('\\', '/'));
}
return URI.create(result.toString());
}
/**
* Reads all the tables from disk in a format suitable to construct {@link ComputationContext}.
*
* @return A dictionary of {@link DecisionTable}s.
*/
public Locators tablesAsLocators() {
return this.tables.allTables();
}
/**
* Creates a copy of this instance with a new path to state yaml.
*
* @param path Path to a file that holds the current state's description.
* @return A new instance of {@link ManualComputation}.
*/
public ManualComputation statePath(final String path) {
return new ManualComputation(
this.tables,
uriFrom(path)
);
}
/**
* Creates a copy of this instance with a new path to tables folder.
*
* @param path Path to a folder containing all the decision tables.
* @return A new instance of {@link ManualComputation}.
*/
public ManualComputation tablePath(final String path) {
return new ManualComputation(
new PlainTextContentReader(uriFrom(path), ".csv", ";"),
this.state
);
}
/**
* Converts yaml data read from input stream to a correct {@link InMemoryStorage} object.
*
* @return The collection of {@link InMemoryStorage} objects.
*/
@SneakyThrows
public Locators currentState() {
try (InputStream stream = Files.newInputStream(new File(this.state).toPath())) {
return stateFrom(stream);
}
}
/**
* Computes the decision for a specified table.
*
* @param table Name of the tables to make a decision against.
* @return The collection of outcomes from the specified table.
* @throws DecitaException If the table could not be found or computed.
*/
public Map<String, String> decideFor(final String table) throws DecitaException {
final DecitaFacade facade = new DecitaFacade(this::tablesAsLocators);
return facade.decisionFor(table, this.currentState());
}
/**
* Returns a complete set of all the table names, read from the user-specified folder.
*
* @return Set of Strings, representing decision table names.
*/
Set<String> tableNames() {
final Set<String> result;
if (this.tables == null) {
result = Collections.emptySet();
} else {
result = this.tablesAsLocators().names();
}
return result;
}
/**
* Loads the state from the specified {@code InputStream}.
*
* @param stream InputStream containing state info.
* @return Collection of {@link Locator} objects, containing desired state.
*/
@SuppressWarnings("unchecked")
private static Locators stateFrom(final InputStream stream) {
return new Locators(
((Map<String, Map<String, Object>>) new Yaml().loadAll(stream).iterator().next())
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entryToLocator()))
);
}
/**
* Converts a {@link Map.Entry} to a {@link Locator} object.
*
*@return A function that converts a {@link Map.Entry} to a {@link Locator} object.
*/
private static Function<Map.Entry<String, Map<String, Object>>, Locator> entryToLocator() {
return e -> new InMemoryStorage(e.getValue());
}
}