blob: 27c9790fb2df5926e0082d2b228364c991a2f942 [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
package org.eclipse.mdm.api.dflt.model;
import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.eclipse.mdm.api.base.adapter.Core;
import org.eclipse.mdm.api.base.model.BaseEntity;
import org.eclipse.mdm.api.base.model.Deletable;
import org.eclipse.mdm.api.base.model.DoubleComplex;
import org.eclipse.mdm.api.base.model.Enumeration;
import org.eclipse.mdm.api.base.model.FileLink;
import org.eclipse.mdm.api.base.model.FloatComplex;
import org.eclipse.mdm.api.base.model.MimeType;
import org.eclipse.mdm.api.base.model.Value;
import org.eclipse.mdm.api.base.model.ValueType;
/**
* Implementation of the template attribute entity type. A template attribute
* adds meta data to a {@link CatalogAttribute} it is associated with. It always
* belongs to a {@link TemplateComponent} or a {@link TemplateSensor}. Its name
* is the same as the name of the associated {@code CatalogAttribute} and is not
* allowed to be modified at all.
*
* @since 1.0.0
* @author Viktor Stoehr, Gigatronik Ingolstadt GmbH
* @see TemplateComponent
* @see TemplateSensor
*/
public class TemplateAttribute extends BaseEntity implements Deletable {
// ======================================================================
// Class variables
// ======================================================================
/**
* This {@code Comparator} compares {@link TemplateAttribute}s by the sort index
* of their corresponding {@link CatalogAttribute} in ascending order.
*/
public static final Comparator<TemplateAttribute> COMPARATOR = Comparator
.comparing(ta -> ta.getCatalogAttribute().getSortIndex());
/**
* The 'DefaultValue' attribute name.
*/
public static final String ATTR_DEFAULT_VALUE = "DefaultValue";
/**
* The 'ValueReadOnly' attribute name.
*/
public static final String ATTR_VALUE_READONLY = "ValueReadonly";
/**
* The 'Optional' attribute name.
*/
public static final String ATTR_OPTIONAL = "Obligatory";
private static final Map<ValueType<?>, Function<String, Object>> VALUETYPE_FUNCTION_MAP = new HashMap<>();
static {
VALUETYPE_FUNCTION_MAP.put(ValueType.STRING, v -> v);
VALUETYPE_FUNCTION_MAP.put(ValueType.DATE, v -> LocalDateTime.parse(v, Value.LOCAL_DATE_TIME_FORMATTER));
VALUETYPE_FUNCTION_MAP.put(ValueType.BOOLEAN, Boolean::valueOf);
VALUETYPE_FUNCTION_MAP.put(ValueType.BYTE, Byte::valueOf);
VALUETYPE_FUNCTION_MAP.put(ValueType.SHORT, Short::valueOf);
VALUETYPE_FUNCTION_MAP.put(ValueType.INTEGER, Integer::valueOf);
VALUETYPE_FUNCTION_MAP.put(ValueType.LONG, Long::valueOf);
VALUETYPE_FUNCTION_MAP.put(ValueType.FLOAT, Float::valueOf);
VALUETYPE_FUNCTION_MAP.put(ValueType.DOUBLE, Double::valueOf);
VALUETYPE_FUNCTION_MAP.put(ValueType.FLOAT_COMPLEX, FloatComplex::valueOf);
VALUETYPE_FUNCTION_MAP.put(ValueType.DOUBLE_COMPLEX, DoubleComplex::valueOf);
}
// ======================================================================
// Constructors
// ======================================================================
/**
* Constructor.
*
* @param core The {@link Core}.
*/
TemplateAttribute(Core core) {
super(core);
}
// ======================================================================
// Public methods
// ======================================================================
/**
* Returns the default {@link Value} of this template attribute.
*
* @return The default {@code Value} is returned.
*/
@SuppressWarnings({ "rawtypes" })
public Value getDefaultValue() {
ValueType valueType = getCatalogAttribute().getValueType();
Value defaultValue = getValue(ATTR_DEFAULT_VALUE);
boolean isValid = defaultValue.isValid();
String value = defaultValue.extract();
if (valueType.isEnumerationType()) {
Enumeration enumObject = getCatalogAttribute().getEnumerationObject();
return valueType.create(getName(), "", isValid, isValid ? enumObject.valueOf(value) : null,
enumObject.getName());
} else {
return valueType.create(getName(), isValid ? parse(value, valueType) : null);
}
}
/**
* Sets a new default value for this template attribute. Given input will be
* stored in its {@link String} representation.
*
* @param input The new default value.
*/
public void setDefaultValue(Object input) {
if (input == null) {
getValue(ATTR_DEFAULT_VALUE).set(null);
return;
}
ValueType<?> valueType = getCatalogAttribute().getValueType();
boolean sequence = valueType.isSequence();
// if this passes -> input is valid
Value value = valueType.create("notRelevant", input);
String stringValue;
if (valueType.isFileLinkType()) {
stringValue = getStringValueForFileLinkType(value, sequence);
} else if (valueType.isDateType()) {
LocalDateTime[] values = sequence ? value.extract() : new LocalDateTime[] { value.extract() };
stringValue = Stream.of(values).map(ldt -> ldt.format(Value.LOCAL_DATE_TIME_FORMATTER))
.collect(Collectors.joining(","));
} else {
if (input.getClass().isArray()) {
stringValue = IntStream.range(0, Array.getLength(input)).mapToObj(i -> Array.get(input, i).toString())
.collect(Collectors.joining(","));
} else {
stringValue = value.extract().toString();
}
}
getValue(ATTR_DEFAULT_VALUE).set(stringValue);
}
/**
* Returns the value read only flag of this template attribute.
*
* @return Returns {@code true} if it is not allowed to modify {@link Value}s
* derived from this template attribute.
*/
public Boolean isValueReadOnly() {
return getValue(ATTR_VALUE_READONLY).extract();
}
/**
* Sets a new value read only flag for this template attribute.
*
* @param valueReadOnly The new value read only flag.
*/
public void setValueReadOnly(Boolean valueReadOnly) {
getValue(ATTR_VALUE_READONLY).set(valueReadOnly);
}
/**
* Returns the optional flag of this template attribute.
*
* @return Returns {@code true} if it is allowed to omit a {@link Value} derived
* from this template attribute.
*/
public Boolean isOptional() {
boolean mandatory = getValue(ATTR_OPTIONAL).extract();
return mandatory ? Boolean.FALSE : Boolean.TRUE;
}
/**
* Sets a new optional flag for this template attribute.
*
* @param optional The new optional flag.
*/
public void setOptional(Boolean optional) {
getValue(ATTR_OPTIONAL).set(optional ? Boolean.FALSE : Boolean.TRUE);
}
/**
* Returns the {@link CatalogAttribute} this template attribute is associated
* with.
*
* @return The associated {@code CatalogAttribute} is returned.
*/
public CatalogAttribute getCatalogAttribute() {
return getCore().getMutableStore().get(CatalogAttribute.class);
}
/**
* Returns the {@link TemplateRoot} this template attribute belongs to.
*
* @return The {@code TemplateRoot} is returned.
*/
public TemplateRoot getTemplateRoot() {
Optional<TemplateComponent> templateComponent = getTemplateComponent();
Optional<TemplateSensor> templateSensor = getTemplateSensor();
if (templateComponent.isPresent()) {
return templateComponent.get().getTemplateRoot();
} else if (templateSensor.isPresent()) {
return templateSensor.get().getTemplateRoot();
} else {
throw new IllegalStateException("Parent entity is unknown.");
}
}
/**
* Returns the parent {@link TemplateComponent} of this template attribute.
*
* @return {@code Optional} is empty if a {@link TemplateSensor} is parent of
* this template attribute.
* @see #getTemplateSensor()
*/
public Optional<TemplateComponent> getTemplateComponent() {
return Optional.ofNullable(getCore().getPermanentStore().get(TemplateComponent.class));
}
/**
* Returns the parent {@link TemplateSensor} of this template attribute.
*
* @return {@code Optional} is empty if a {@link TemplateComponent} is parent of
* this template attribute.
* @see #getTemplateComponent()
*/
public Optional<TemplateSensor> getTemplateSensor() {
return Optional.ofNullable(getCore().getPermanentStore().get(TemplateSensor.class));
}
// ======================================================================
// Private methods
// ======================================================================
/**
* Parses given {@code String} to the corresponding type of given
* {@link ValueType}.
*
* @param value The {@code String} value.
* @param valueType Used to resolve the corresponding converter.
* @return The parsed object is returned.
*/
private static Object parse(String value, ValueType<?> valueType) {
if (valueType.isFileLinkType()) {
Pattern pattern = Pattern.compile("([^,].*?)\\[(.*?),(.*?)\\]");
Matcher matcher = pattern.matcher(value);
List<FileLink> fileLinks = new ArrayList<>();
while (matcher.find()) {
fileLinks.add(FileLinkParser.parse(matcher.group()));
}
return valueType.isSequence() ? fileLinks.toArray(new FileLink[fileLinks.size()]) : fileLinks.get(0);
} else {
Function<String, Object> converter = getParser(valueType);
if (valueType.isSequence()) {
List<Object> values = Stream.of(value.split(",")).map(converter).collect(Collectors.toList());
Object array = Array.newInstance(valueType.getValueClass().getComponentType(), values.size());
if (valueType.getValueClass().getComponentType().isPrimitive()) {
IntStream.range(0, values.size()).forEach(i -> Array.set(array, i, values.get(i)));
} else {
values.toArray((Object[]) array);
}
return array;
} else {
return converter.apply(value);
}
}
}
/**
* Returns the {@code String} conversion function for given {@link ValueType}.
*
* @param valueType Used as identifier.
* @return The {@code String} conversion {@code Function} is returned.
* @throws IllegalArgumentException Thrown if a corresponding {@code String} is
* not supported.
*/
private static Function<String, Object> getParser(ValueType<?> valueType) {
Function<String, Object> converter = VALUETYPE_FUNCTION_MAP.get(valueType);
if (converter == null) {
throw new IllegalArgumentException("String conversion for value type '" + valueType + "' not supported.");
}
return converter;
}
// ======================================================================
// Inner classes
// ======================================================================
/**
* Utility class restore a {@link FileLink} from given {@code String}.
*/
private static final class FileLinkParser {
// ======================================================================
// Class variables
// ======================================================================
// markers
private static final String NO_DESC_MARKER = "NO_DESC#";
private static final String LOCAL_MARKER = "LOCALPATH#";
// pattern group names
private static final String DESCRIPTION = "description";
private static final String MIMETYPE = "mimetype";
private static final String PATH = "path";
// pattern
private static final Pattern FILE_LINK_PATTERN = Pattern
.compile("(?<" + DESCRIPTION + ">.*?)\\[(?<" + MIMETYPE + ">.*?),(?<" + PATH + ">.*?)\\]");
// ======================================================================
// Public methods
// ======================================================================
/**
* Parses given {@code String} and returns it as {@link FileLink}.
*
* @param value The {@code String} value which will be parsed.
* @return The corresponding {@code FileLink} is returned.
*/
public static FileLink parse(String value) {
Matcher matcher = FILE_LINK_PATTERN.matcher(value);
if (!matcher.find()) {
throw new IllegalStateException("Unable to restore file link.");
}
String description = matcher.group(DESCRIPTION);
String path = matcher.group(PATH);
FileLink fileLink;
if (path.startsWith(LOCAL_MARKER)) {
try {
fileLink = FileLink.newLocal(Paths.get(path.replaceFirst(LOCAL_MARKER, "")));
} catch (IOException e) {
throw new IllegalStateException("Unable to restore local file link.", e);
}
} else {
fileLink = FileLink.newRemote(path, new MimeType(matcher.group(MIMETYPE)), description);
}
fileLink.setDescription(NO_DESC_MARKER.equals(description) ? null : description);
return fileLink;
}
}
/**
* Get string value for FileLinkType
*
* @param value The {@code Value} value.
* @param sequence The {@code boolean} sequence.
* @return Return {@code String} string value for FileLinkType
*/
private String getStringValueForFileLinkType(Value value, boolean sequence) {
FileLink[] values = sequence ? value.extract() : new FileLink[] { value.extract() };
return Stream.of(values).map(fl -> {
StringBuilder sb = new StringBuilder();
if (fl.getDescription().isEmpty()) {
sb.append(FileLinkParser.NO_DESC_MARKER);
} else {
sb.append(fl.getDescription());
}
sb.append('[').append(fl.getMimeType()).append(',');
if (fl.isRemote()) {
sb.append(fl.getRemotePath());
} else if (fl.isLocal()) {
sb.append(FileLinkParser.LOCAL_MARKER).append(fl.getLocalPath());
} else {
throw new IllegalStateException("File link is neither in local nor remote state: " + fl);
}
return sb.append(']');
}).collect(Collectors.joining(","));
}
}