mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-29 12:34:22 +02:00
DSL Items Parser: Fix incorrect parsing of keywords encountered in the wrong context (#4928)
* DSL Items Parser: Fix incorrect parsing of keywords encountered in the wrong context When specifying tag names that match one of the valid item types, e.g. `Switch`, the parser incorrectly treated it as a new start of an item definition. Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
This commit is contained in:
+8
-1
@@ -330,7 +330,14 @@ public class ModelRepositoryImpl implements ModelRepository {
|
|||||||
final org.eclipse.emf.common.util.Diagnostic diagnostic = safeEmf
|
final org.eclipse.emf.common.util.Diagnostic diagnostic = safeEmf
|
||||||
.call(() -> Diagnostician.INSTANCE.validate(resource.getContents().getFirst()));
|
.call(() -> Diagnostician.INSTANCE.validate(resource.getContents().getFirst()));
|
||||||
for (org.eclipse.emf.common.util.Diagnostic d : diagnostic.getChildren()) {
|
for (org.eclipse.emf.common.util.Diagnostic d : diagnostic.getChildren()) {
|
||||||
warnings.add(d.getMessage());
|
if (d.getSeverity() == org.eclipse.emf.common.util.Diagnostic.ERROR) {
|
||||||
|
errors.add(d.getMessage());
|
||||||
|
} else {
|
||||||
|
warnings.add(d.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
// see https://github.com/eclipse/smarthome/issues/3335
|
// see https://github.com/eclipse/smarthome/issues/3335
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ Import-Package: javax.measure,\
|
|||||||
org.openhab.core.items,\
|
org.openhab.core.items,\
|
||||||
org.openhab.core.items.dto,\
|
org.openhab.core.items.dto,\
|
||||||
org.openhab.core.items.fileconverter,\
|
org.openhab.core.items.fileconverter,\
|
||||||
|
org.openhab.core.library,\
|
||||||
org.openhab.core.library.items,\
|
org.openhab.core.library.items,\
|
||||||
org.openhab.core.library.types,\
|
org.openhab.core.library.types,\
|
||||||
org.openhab.core.thing.util,\
|
org.openhab.core.thing.util,\
|
||||||
|
|||||||
@@ -14,31 +14,18 @@ ItemModel:
|
|||||||
;
|
;
|
||||||
|
|
||||||
ModelItem:
|
ModelItem:
|
||||||
(ModelNormalItem | ModelGroupItem) name=ID
|
type=ModelItemType ('(' args+=(ID|STRING) (',' args+=(ID|STRING))* ')')?
|
||||||
|
name=ID
|
||||||
(label=STRING)?
|
(label=STRING)?
|
||||||
('<' icon=Icon '>')?
|
('<' icon=Icon '>')?
|
||||||
('(' groups+=ID (',' groups+=ID)* ')')?
|
('(' groups+=ID (',' groups+=ID)* ')')?
|
||||||
('[' tags+=(ID|STRING) (',' tags+=(ID|STRING))* ']')?
|
('[' tags+=(ID|STRING) (',' tags+=(ID|STRING))* ']')?
|
||||||
('{' bindings+=ModelBinding (',' bindings+=ModelBinding)* '}')?
|
('{' bindings+=ModelBinding (',' bindings+=ModelBinding)* '}')?
|
||||||
;
|
|
||||||
|
|
||||||
ModelGroupItem:
|
|
||||||
{ModelGroupItem} 'Group' (':' type=ModelItemType ( ':' function=ModelGroupFunction ('(' args+=(ID|STRING) (',' args+=(ID|STRING))* ')')?)?)?
|
|
||||||
;
|
|
||||||
|
|
||||||
enum ModelGroupFunction:
|
|
||||||
EQUALITY='EQUALITY' | AND='AND' | OR='OR' | NAND='NAND' | NOR='NOR' | XOR='XOR' | AVG='AVG' | MEDIAN='MEDIAN' | SUM='SUM' | MAX='MAX' | MIN='MIN' | COUNT='COUNT' | LATEST='LATEST' | EARLIEST='EARLIEST'
|
|
||||||
;
|
|
||||||
|
|
||||||
ModelNormalItem:
|
|
||||||
type=ModelItemType
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
// Supports item types with up to 4 colon-separated segments, e.g., Group:Number:Dimension:MAX
|
||||||
ModelItemType:
|
ModelItemType:
|
||||||
BaseModelItemType | ('Number' (':' ID)?)
|
ID (':' ID (':' ID (':' ID )?)?)?
|
||||||
;
|
|
||||||
BaseModelItemType:
|
|
||||||
'Switch' | 'Rollershutter' | 'String' | 'Dimmer' | 'Contact' | 'DateTime' | 'Color' | 'Player' | 'Location' | 'Call' | 'Image'
|
|
||||||
;
|
;
|
||||||
|
|
||||||
ModelBinding:
|
ModelBinding:
|
||||||
@@ -56,7 +43,7 @@ ValueType returns ecore::EJavaObject:
|
|||||||
STRING | NUMBER | BOOLEAN
|
STRING | NUMBER | BOOLEAN
|
||||||
;
|
;
|
||||||
|
|
||||||
BOOLEAN returns ecore::EBoolean:
|
BOOLEAN returns ecore::EBoolean:
|
||||||
'true' | 'false'
|
'true' | 'false'
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -25,8 +25,10 @@ class ItemsFormatter extends AbstractDeclarativeFormatter {
|
|||||||
@Inject extension ItemsGrammarAccess
|
@Inject extension ItemsGrammarAccess
|
||||||
|
|
||||||
override protected void configureFormatting(FormattingConfig c) {
|
override protected void configureFormatting(FormattingConfig c) {
|
||||||
c.setLinewrap(1, 1, 2).before(modelGroupItemRule)
|
c.setLinewrap(1, 1, 2).before(modelItemRule)
|
||||||
c.setLinewrap(1, 1, 2).before(modelNormalItemRule)
|
|
||||||
|
// No space between the item type and the opening parenthesis for the group function arguments
|
||||||
|
c.setNoSpace().between(modelItemTypeRule, modelItemAccess.leftParenthesisKeyword_1_0)
|
||||||
|
|
||||||
c.setNoSpace().withinKeywordPairs("<", ">")
|
c.setNoSpace().withinKeywordPairs("<", ">")
|
||||||
c.setNoSpace().withinKeywordPairs("(", ")")
|
c.setNoSpace().withinKeywordPairs("(", ")")
|
||||||
|
|||||||
+89
-59
@@ -47,10 +47,7 @@ import org.openhab.core.model.item.BindingConfigParseException;
|
|||||||
import org.openhab.core.model.item.BindingConfigReader;
|
import org.openhab.core.model.item.BindingConfigReader;
|
||||||
import org.openhab.core.model.items.ItemModel;
|
import org.openhab.core.model.items.ItemModel;
|
||||||
import org.openhab.core.model.items.ModelBinding;
|
import org.openhab.core.model.items.ModelBinding;
|
||||||
import org.openhab.core.model.items.ModelGroupFunction;
|
|
||||||
import org.openhab.core.model.items.ModelGroupItem;
|
|
||||||
import org.openhab.core.model.items.ModelItem;
|
import org.openhab.core.model.items.ModelItem;
|
||||||
import org.openhab.core.model.items.ModelNormalItem;
|
|
||||||
import org.openhab.core.types.StateDescriptionFragment;
|
import org.openhab.core.types.StateDescriptionFragment;
|
||||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||||
import org.openhab.core.types.StateDescriptionFragmentProvider;
|
import org.openhab.core.types.StateDescriptionFragmentProvider;
|
||||||
@@ -234,62 +231,54 @@ public class GenericItemProvider extends AbstractProvider<Item>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable Item createItemFromModelItem(ModelItem modelItem, String modelName) {
|
private @Nullable Item createItemFromModelItem(ModelItem modelItem, String modelName) {
|
||||||
Item item;
|
String itemType = modelItem.getType();
|
||||||
if (modelItem instanceof ModelGroupItem modelGroupItem) {
|
if (itemType == null || itemType.isBlank()) {
|
||||||
Item baseItem;
|
logger.warn("Item '{}' has no type defined, ignoring it.", modelItem.getName());
|
||||||
try {
|
return null;
|
||||||
baseItem = createItemOfType(modelGroupItem.getType(), modelGroupItem.getName());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
logger.debug("Error creating base item for group item '{}', item will be ignored: {}",
|
|
||||||
modelGroupItem.getName(), e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (baseItem != null) {
|
|
||||||
// if the user did not specify a function the first value of the enum in xtext (EQUAL) will be used
|
|
||||||
ModelGroupFunction function = modelGroupItem.getFunction();
|
|
||||||
item = applyGroupFunction(baseItem, modelGroupItem, function);
|
|
||||||
} else {
|
|
||||||
item = new GroupItem(modelGroupItem.getName());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ModelNormalItem normalItem = (ModelNormalItem) modelItem;
|
|
||||||
try {
|
|
||||||
item = createItemOfType(normalItem.getType(), normalItem.getName());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
logger.debug("Error creating item '{}', item will be ignored: {}", normalItem.getName(),
|
|
||||||
e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (item instanceof ActiveItem activeItem) {
|
|
||||||
String label = modelItem.getLabel();
|
try {
|
||||||
String format = extractFormat(label);
|
String[] itemTypeSegments = itemType.split(ItemUtil.EXTENSION_SEPARATOR);
|
||||||
if (format != null) {
|
String mainItemType = itemTypeSegments[0];
|
||||||
label = label.substring(0, label.indexOf("[")).trim();
|
|
||||||
Map<String, String> formatters = Objects
|
Item item = switch (mainItemType) {
|
||||||
.requireNonNull(stateFormattersMap.computeIfAbsent(modelName, k -> new HashMap<>()));
|
case "Group" -> createGroupItem(modelItem, itemTypeSegments);
|
||||||
formatters.put(modelItem.getName(), format);
|
default -> createItemOfType(itemType, modelItem.getName());
|
||||||
if (!isIsolatedModel(modelName)) {
|
};
|
||||||
stateDescriptionFragments.put(modelItem.getName(),
|
|
||||||
StateDescriptionFragmentBuilder.create().withPattern(format).build());
|
if (item instanceof ActiveItem activeItem) {
|
||||||
}
|
String label = modelItem.getLabel();
|
||||||
} else {
|
String format = extractFormat(label);
|
||||||
Map<String, String> formatters = stateFormattersMap.get(modelName);
|
if (format != null) {
|
||||||
if (formatters != null) {
|
label = label.substring(0, label.indexOf("[")).trim();
|
||||||
formatters.remove(modelItem.getName());
|
Map<String, String> formatters = Objects
|
||||||
if (formatters.isEmpty()) {
|
.requireNonNull(stateFormattersMap.computeIfAbsent(modelName, k -> new HashMap<>()));
|
||||||
stateFormattersMap.remove(modelName);
|
formatters.put(modelItem.getName(), format);
|
||||||
|
if (!isIsolatedModel(modelName)) {
|
||||||
|
stateDescriptionFragments.put(modelItem.getName(),
|
||||||
|
StateDescriptionFragmentBuilder.create().withPattern(format).build());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Map<String, String> formatters = stateFormattersMap.get(modelName);
|
||||||
|
if (formatters != null) {
|
||||||
|
formatters.remove(modelItem.getName());
|
||||||
|
if (formatters.isEmpty()) {
|
||||||
|
stateFormattersMap.remove(modelName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isIsolatedModel(modelName)) {
|
||||||
|
stateDescriptionFragments.remove(modelItem.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isIsolatedModel(modelName)) {
|
activeItem.setLabel(label);
|
||||||
stateDescriptionFragments.remove(modelItem.getName());
|
activeItem.setCategory(modelItem.getIcon());
|
||||||
}
|
assignTags(modelItem, activeItem);
|
||||||
|
return item;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
activeItem.setLabel(label);
|
} catch (IllegalArgumentException e) {
|
||||||
activeItem.setCategory(modelItem.getIcon());
|
logger.debug("Error creating item '{}', item will be ignored: {}", modelItem.getName(), e.getMessage());
|
||||||
assignTags(modelItem, activeItem);
|
|
||||||
return item;
|
|
||||||
} else {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,14 +301,14 @@ public class GenericItemProvider extends AbstractProvider<Item>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private GroupItem applyGroupFunction(Item baseItem, ModelGroupItem modelGroupItem, ModelGroupFunction function) {
|
private GroupItem applyGroupFunction(Item baseItem, ModelItem modelItem, String function) {
|
||||||
GroupFunctionDTO dto = new GroupFunctionDTO();
|
GroupFunctionDTO dto = new GroupFunctionDTO();
|
||||||
dto.name = function.getName();
|
dto.name = function;
|
||||||
dto.params = modelGroupItem.getArgs().toArray(new String[0]);
|
dto.params = modelItem.getArgs().toArray(new String[0]);
|
||||||
|
|
||||||
GroupFunction groupFunction = ItemDTOMapper.mapFunction(baseItem, dto);
|
GroupFunction groupFunction = ItemDTOMapper.mapFunction(baseItem, dto);
|
||||||
|
|
||||||
return new GroupItem(modelGroupItem.getName(), baseItem, groupFunction);
|
return new GroupItem(modelItem.getName(), baseItem, groupFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dispatchBindingsPerItemType(String[] itemTypes) {
|
private void dispatchBindingsPerItemType(String[] itemTypes) {
|
||||||
@@ -529,6 +518,47 @@ public class GenericItemProvider extends AbstractProvider<Item>
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new GroupItem based on the given ModelItem and item type segments.
|
||||||
|
*
|
||||||
|
* @param modelItem The ModelItem to create the GroupItem from.
|
||||||
|
* @param itemTypeSegments The segments of the item type.
|
||||||
|
* @return A new GroupItem or null if the item type is invalid.
|
||||||
|
*/
|
||||||
|
private @Nullable GroupItem createGroupItem(ModelItem modelItem, String[] itemTypeSegments) {
|
||||||
|
if (itemTypeSegments.length == 1) {
|
||||||
|
// Just plain "Group" with no base type
|
||||||
|
return new GroupItem(modelItem.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
String function = GroupFunction.DEFAULT;
|
||||||
|
|
||||||
|
String baseItemType = switch (itemTypeSegments.length) {
|
||||||
|
case 2 -> itemTypeSegments[1];
|
||||||
|
case 3 -> {
|
||||||
|
// 3 segments could either be Group:Type:Function, or Group:Number:Dimension -> Find out which one it is
|
||||||
|
if (!modelItem.getArgs().isEmpty() || GroupFunction.VALID_FUNCTIONS.contains(itemTypeSegments[2])) {
|
||||||
|
// It's Group:Type:Function because there are arguments or the third segment is a valid function
|
||||||
|
function = itemTypeSegments[2];
|
||||||
|
yield itemTypeSegments[1];
|
||||||
|
} else {
|
||||||
|
// Otherwise, it must be Group:Number:Dimension
|
||||||
|
yield itemTypeSegments[1] + ItemUtil.EXTENSION_SEPARATOR + itemTypeSegments[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 4 -> {
|
||||||
|
// 4 segments: "Group:Number:Dimension:Function"
|
||||||
|
function = itemTypeSegments[3];
|
||||||
|
yield itemTypeSegments[1] + ItemUtil.EXTENSION_SEPARATOR + itemTypeSegments[2];
|
||||||
|
}
|
||||||
|
default -> throw new IllegalArgumentException("Invalid group item type: " + modelItem.getType()
|
||||||
|
+ ". Expected formats are 'Group', 'Group:Type', 'Group:Type:Function', or 'Group:Number:Dimension:Function' with a maximum of 4 segments.");
|
||||||
|
};
|
||||||
|
|
||||||
|
Item baseItem = createItemOfType(baseItemType, modelItem.getName());
|
||||||
|
return applyGroupFunction(baseItem, modelItem, function);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new item of type {@code itemType} by utilizing an appropriate {@link ItemFactory}.
|
* Creates a new item of type {@code itemType} by utilizing an appropriate {@link ItemFactory}.
|
||||||
*
|
*
|
||||||
|
|||||||
+8
-11
@@ -35,6 +35,7 @@ import org.openhab.core.config.core.ConfigUtil;
|
|||||||
import org.openhab.core.items.GroupFunction;
|
import org.openhab.core.items.GroupFunction;
|
||||||
import org.openhab.core.items.GroupItem;
|
import org.openhab.core.items.GroupItem;
|
||||||
import org.openhab.core.items.Item;
|
import org.openhab.core.items.Item;
|
||||||
|
import org.openhab.core.items.ItemUtil;
|
||||||
import org.openhab.core.items.Metadata;
|
import org.openhab.core.items.Metadata;
|
||||||
import org.openhab.core.items.fileconverter.AbstractItemFileGenerator;
|
import org.openhab.core.items.fileconverter.AbstractItemFileGenerator;
|
||||||
import org.openhab.core.items.fileconverter.ItemFileGenerator;
|
import org.openhab.core.items.fileconverter.ItemFileGenerator;
|
||||||
@@ -45,8 +46,6 @@ import org.openhab.core.model.item.internal.GenericMetadataProvider;
|
|||||||
import org.openhab.core.model.items.ItemModel;
|
import org.openhab.core.model.items.ItemModel;
|
||||||
import org.openhab.core.model.items.ItemsFactory;
|
import org.openhab.core.model.items.ItemsFactory;
|
||||||
import org.openhab.core.model.items.ModelBinding;
|
import org.openhab.core.model.items.ModelBinding;
|
||||||
import org.openhab.core.model.items.ModelGroupFunction;
|
|
||||||
import org.openhab.core.model.items.ModelGroupItem;
|
|
||||||
import org.openhab.core.model.items.ModelItem;
|
import org.openhab.core.model.items.ModelItem;
|
||||||
import org.openhab.core.model.items.ModelProperty;
|
import org.openhab.core.model.items.ModelProperty;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
@@ -115,26 +114,24 @@ public class DslItemFileConverter extends AbstractItemFileGenerator implements I
|
|||||||
|
|
||||||
private ModelItem buildModelItem(Item item, List<Metadata> channelLinks, List<Metadata> metadata,
|
private ModelItem buildModelItem(Item item, List<Metadata> channelLinks, List<Metadata> metadata,
|
||||||
@Nullable String stateFormatter, boolean hideDefaultParameters) {
|
@Nullable String stateFormatter, boolean hideDefaultParameters) {
|
||||||
ModelItem model;
|
ModelItem model = ItemsFactory.eINSTANCE.createModelItem();
|
||||||
if (item instanceof GroupItem groupItem) {
|
if (item instanceof GroupItem groupItem) {
|
||||||
ModelGroupItem modelGroup = ItemsFactory.eINSTANCE.createModelGroupItem();
|
|
||||||
model = modelGroup;
|
|
||||||
Item baseItem = groupItem.getBaseItem();
|
Item baseItem = groupItem.getBaseItem();
|
||||||
|
List<String> groupType = new ArrayList<>();
|
||||||
|
groupType.add(groupItem.getType());
|
||||||
if (baseItem != null) {
|
if (baseItem != null) {
|
||||||
modelGroup.setType(baseItem.getType());
|
groupType.add(baseItem.getType());
|
||||||
GroupFunction function = groupItem.getFunction();
|
GroupFunction function = groupItem.getFunction();
|
||||||
if (function != null) {
|
if (function != null) {
|
||||||
ModelGroupFunction modelFunction = ModelGroupFunction
|
groupType.add(function.getClass().getSimpleName().toUpperCase());
|
||||||
.getByName(function.getClass().getSimpleName().toUpperCase());
|
|
||||||
modelGroup.setFunction(modelFunction);
|
|
||||||
State[] parameters = function.getParameters();
|
State[] parameters = function.getParameters();
|
||||||
for (int i = 0; i < parameters.length; i++) {
|
for (int i = 0; i < parameters.length; i++) {
|
||||||
modelGroup.getArgs().add(parameters[i].toString());
|
model.getArgs().add(parameters[i].toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
model.setType(groupType.stream().collect(Collectors.joining(ItemUtil.EXTENSION_SEPARATOR)));
|
||||||
} else {
|
} else {
|
||||||
model = ItemsFactory.eINSTANCE.createModelNormalItem();
|
|
||||||
model.setType(item.getType());
|
model.setType(item.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+94
-18
@@ -12,15 +12,18 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.core.model.validation
|
package org.openhab.core.model.validation
|
||||||
|
|
||||||
import org.openhab.core.model.items.ModelItem
|
|
||||||
import org.eclipse.xtext.validation.Check
|
import org.eclipse.xtext.validation.Check
|
||||||
|
import org.openhab.core.items.GroupFunction
|
||||||
|
import org.openhab.core.items.ItemUtil
|
||||||
|
import org.openhab.core.library.CoreItemFactory
|
||||||
import org.openhab.core.model.items.ItemsPackage
|
import org.openhab.core.model.items.ItemsPackage
|
||||||
|
import org.openhab.core.model.items.ModelItem
|
||||||
import org.openhab.core.types.util.UnitUtils
|
import org.openhab.core.types.util.UnitUtils
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom validation rules.
|
* Custom validation rules.
|
||||||
*
|
*
|
||||||
* see http://www.eclipse.org/Xtext/documentation.html#validation
|
* see https://eclipse.dev/Xtext/documentation/303_runtime_concepts.html#validation
|
||||||
*/
|
*/
|
||||||
class ItemsValidator extends AbstractItemsValidator {
|
class ItemsValidator extends AbstractItemsValidator {
|
||||||
|
|
||||||
@@ -29,23 +32,96 @@ class ItemsValidator extends AbstractItemsValidator {
|
|||||||
if (item === null || item.name === null) {
|
if (item === null || item.name === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (item.name.contains("-")) {
|
if (!ItemUtil.isValidItemName(item.name)) {
|
||||||
error('Item name must not contain dashes.', ItemsPackage.Literals.MODEL_ITEM__NAME)
|
error('Item name "' + item.name + '" is invalid. It must begin with a letter or underscore followed by alphanumeric characters and underscores, and must not contain any other symbols.', ItemsPackage.Literals.MODEL_ITEM__NAME)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Check
|
@Check
|
||||||
def checkDimension(ModelItem item) {
|
def checkValidItemType(ModelItem item) {
|
||||||
if (item === null || item.type === null) {
|
if (item === null || item.type === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (item.type.startsWith("Number:")) {
|
|
||||||
var dimension = item.type.substring(item.type.indexOf(":") + 1)
|
val segments = item.type.split(":")
|
||||||
try {
|
val mainType = segments.get(0)
|
||||||
UnitUtils.parseDimension(dimension)
|
switch mainType {
|
||||||
} catch (IllegalArgumentException e) {
|
case "Group": checkGroupType(item, segments)
|
||||||
warning("'" + dimension + "' is not a valid dimension.", ItemsPackage.Literals.MODEL_ITEM__TYPE)
|
case "Number": checkNumberType(item, segments)
|
||||||
}
|
default: checkBasicItemType(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def checkNumberType(ModelItem item, String[] segments) {
|
||||||
|
switch (segments.size) {
|
||||||
|
case 1: return // Just "Number", valid
|
||||||
|
case 2: checkDimension(item, segments.get(1)) // "Number:<dimension>"
|
||||||
|
default: error("Item: '" + item.name + "' has an invalid Number type, too many segments: " + item.type, ItemsPackage.Literals.MODEL_ITEM__TYPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkBasicItemType(ModelItem item) {
|
||||||
|
if (!CoreItemFactory.VALID_ITEM_TYPES.contains(item.type)) {
|
||||||
|
error("Item '" + item.name + "' has an invalid type: '" + item.type + "'", ItemsPackage.Literals.MODEL_ITEM__TYPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkGroupType(ModelItem item, String[] segments) {
|
||||||
|
switch (segments.size) {
|
||||||
|
case 1: return // Just "Group", valid
|
||||||
|
case 2: checkGroupBaseType(item, segments.get(1))
|
||||||
|
case 3: checkGroupWithOneParam(item, segments.get(1), segments.get(2))
|
||||||
|
case 4: checkGroupWithTwoParams(item, segments.get(1), segments.get(2), segments.get(3))
|
||||||
|
// The xtext grammar will not allow more than 4 segments for Group types, so no default case is needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkGroupBaseType(ModelItem item, String baseType) {
|
||||||
|
if (!CoreItemFactory.VALID_ITEM_TYPES.contains(baseType)) {
|
||||||
|
error("Item '" + item.name + "' has an invalid base item type: '" + baseType + "'", ItemsPackage.Literals.MODEL_ITEM__TYPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkGroupWithOneParam(ModelItem item, String baseType, String param) {
|
||||||
|
checkGroupBaseType(item, baseType)
|
||||||
|
|
||||||
|
if (item.args.size == 0 && baseType == "Number") {
|
||||||
|
if (!isValidGroupFunction(param)) {
|
||||||
|
// Group:Number:Dimension
|
||||||
|
checkDimension(item, param)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
checkGroupFunction(item, param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkGroupWithTwoParams(ModelItem item, String baseType, String dimension, String function) {
|
||||||
|
checkGroupBaseType(item, baseType)
|
||||||
|
|
||||||
|
if (baseType == "Number") {
|
||||||
|
checkDimension(item, dimension)
|
||||||
|
} else {
|
||||||
|
error("Item '" + item.name + "' with type '" + item.type + "' cannot have a dimension. Dimensions are only valid for Number base type. The dimension '" + dimension + "' is ignored.", ItemsPackage.Literals.MODEL_ITEM__TYPE)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkGroupFunction(item, function)
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkDimension(ModelItem item, String dimension) {
|
||||||
|
try {
|
||||||
|
UnitUtils.parseDimension(dimension)
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
error("Item '" + item.name + "' has an unknown dimension: '" + dimension + "'. The Item will not be created.", ItemsPackage.Literals.MODEL_ITEM__TYPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkGroupFunction(ModelItem item, String function) {
|
||||||
|
if (!isValidGroupFunction(function)) {
|
||||||
|
warning("Item '" + item.name + "' has an unknown group function: '" + function + "'. Using EQUALITY instead.", ItemsPackage.Literals.MODEL_ITEM__TYPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def isValidGroupFunction(String value) {
|
||||||
|
return GroupFunction.VALID_FUNCTIONS.contains(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-17
@@ -32,25 +32,25 @@ import org.openhab.core.model.sitemap.sitemap.Chart
|
|||||||
|
|
||||||
//import org.eclipse.xtext.validation.Check
|
//import org.eclipse.xtext.validation.Check
|
||||||
/**
|
/**
|
||||||
* Custom validation rules.
|
* Custom validation rules.
|
||||||
*
|
*
|
||||||
* see http://www.eclipse.org/Xtext/documentation.html#validation
|
* see http://www.eclipse.org/Xtext/documentation.html#validation
|
||||||
*/
|
*/
|
||||||
class SitemapValidator extends AbstractSitemapValidator {
|
class SitemapValidator extends AbstractSitemapValidator {
|
||||||
|
|
||||||
val ALLOWED_HINTS = #["text", "number", "date", "time", "datetime"]
|
val ALLOWED_HINTS = #["text", "number", "date", "time", "datetime"]
|
||||||
val ALLOWED_INTERPOLATION = #["linear", "step"]
|
val ALLOWED_INTERPOLATION = #["linear", "step"]
|
||||||
|
|
||||||
@Check
|
@Check
|
||||||
def void checkFramesInFrame(Frame frame) {
|
def void checkFramesInFrame(Frame frame) {
|
||||||
for (Widget w : frame.children) {
|
for (Widget w : frame.children) {
|
||||||
if (w instanceof Frame) {
|
if (w instanceof Frame) {
|
||||||
error("Frames must not contain other frames",
|
warning("Frames must not contain other frames",
|
||||||
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.FRAME__CHILDREN));
|
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.FRAME__CHILDREN));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (w instanceof Button) {
|
if (w instanceof Button) {
|
||||||
error("Frames should not contain Button, Button is allowed only in Buttongrid",
|
warning("Frames should not contain Button, Button is allowed only in Buttongrid",
|
||||||
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.FRAME__CHILDREN));
|
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.FRAME__CHILDREN));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ class SitemapValidator extends AbstractSitemapValidator {
|
|||||||
|
|
||||||
for (Widget w : sitemap.children) {
|
for (Widget w : sitemap.children) {
|
||||||
if (w instanceof Button) {
|
if (w instanceof Button) {
|
||||||
error("Sitemap should not contain Button, Button is allowed only in Buttongrid",
|
warning("Sitemap should not contain Button, Button is allowed only in Buttongrid",
|
||||||
SitemapPackage.Literals.SITEMAP.getEStructuralFeature(SitemapPackage.SITEMAP__NAME));
|
SitemapPackage.Literals.SITEMAP.getEStructuralFeature(SitemapPackage.SITEMAP__NAME));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ class SitemapValidator extends AbstractSitemapValidator {
|
|||||||
containsOtherWidgets = true
|
containsOtherWidgets = true
|
||||||
}
|
}
|
||||||
if (containsFrames && containsOtherWidgets) {
|
if (containsFrames && containsOtherWidgets) {
|
||||||
error("Sitemap should contain either only frames or none at all",
|
warning("Sitemap should contain either only frames or none at all",
|
||||||
SitemapPackage.Literals.SITEMAP.getEStructuralFeature(SitemapPackage.SITEMAP__NAME));
|
SitemapPackage.Literals.SITEMAP.getEStructuralFeature(SitemapPackage.SITEMAP__NAME));
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ class SitemapValidator extends AbstractSitemapValidator {
|
|||||||
var containsOtherWidgets = false
|
var containsOtherWidgets = false
|
||||||
for (Widget w : widget.children) {
|
for (Widget w : widget.children) {
|
||||||
if (w instanceof Button) {
|
if (w instanceof Button) {
|
||||||
error("Linkable widget should not contain Button, Button is allowed only in Buttongrid",
|
warning("Linkable widget should not contain Button, Button is allowed only in Buttongrid",
|
||||||
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.LINKABLE_WIDGET__CHILDREN));
|
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.LINKABLE_WIDGET__CHILDREN));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ class SitemapValidator extends AbstractSitemapValidator {
|
|||||||
containsOtherWidgets = true
|
containsOtherWidgets = true
|
||||||
}
|
}
|
||||||
if (containsFrames && containsOtherWidgets) {
|
if (containsFrames && containsOtherWidgets) {
|
||||||
error("Linkable widget should contain either only frames or none at all",
|
warning("Linkable widget should contain either only frames or none at all",
|
||||||
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.LINKABLE_WIDGET__CHILDREN));
|
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.LINKABLE_WIDGET__CHILDREN));
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -116,12 +116,12 @@ class SitemapValidator extends AbstractSitemapValidator {
|
|||||||
def void checkWidgetsInButtongrid(Buttongrid grid) {
|
def void checkWidgetsInButtongrid(Buttongrid grid) {
|
||||||
val nb = grid.getButtons.size()
|
val nb = grid.getButtons.size()
|
||||||
if (nb > 0 && grid.item === null) {
|
if (nb > 0 && grid.item === null) {
|
||||||
error("To use the \"buttons\" parameter in a Buttongrid, the \"item\" parameter is required",
|
warning("To use the \"buttons\" parameter in a Buttongrid, the \"item\" parameter is required",
|
||||||
SitemapPackage.Literals.BUTTONGRID.getEStructuralFeature(SitemapPackage.BUTTONGRID__ITEM));
|
SitemapPackage.Literals.BUTTONGRID.getEStructuralFeature(SitemapPackage.BUTTONGRID__ITEM));
|
||||||
}
|
}
|
||||||
for (Widget w : grid.children) {
|
for (Widget w : grid.children) {
|
||||||
if (!(w instanceof Button)) {
|
if (!(w instanceof Button)) {
|
||||||
error("Buttongrid must contain only Button",
|
warning("Buttongrid must contain only Button",
|
||||||
SitemapPackage.Literals.BUTTONGRID.getEStructuralFeature(SitemapPackage.BUTTONGRID__CHILDREN));
|
SitemapPackage.Literals.BUTTONGRID.getEStructuralFeature(SitemapPackage.BUTTONGRID__CHILDREN));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -131,17 +131,17 @@ class SitemapValidator extends AbstractSitemapValidator {
|
|||||||
@Check
|
@Check
|
||||||
def void checkSetpoints(Setpoint sp) {
|
def void checkSetpoints(Setpoint sp) {
|
||||||
if (BigDecimal.ZERO == sp.step) {
|
if (BigDecimal.ZERO == sp.step) {
|
||||||
error("Setpoint on item '" + sp.item + "' has step size of 0",
|
warning("Setpoint on item '" + sp.item + "' has step size of 0",
|
||||||
SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__STEP));
|
SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__STEP));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sp.step !== null && sp.step < BigDecimal.ZERO) {
|
if (sp.step !== null && sp.step < BigDecimal.ZERO) {
|
||||||
error("Setpoint on item '" + sp.item + "' has negative step size",
|
warning("Setpoint on item '" + sp.item + "' has negative step size",
|
||||||
SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__STEP));
|
SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__STEP));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sp.minValue !== null && sp.maxValue !== null && sp.minValue > sp.maxValue) {
|
if (sp.minValue !== null && sp.maxValue !== null && sp.minValue > sp.maxValue) {
|
||||||
error("Setpoint on item '" + sp.item + "' has larger minValue than maxValue",
|
warning("Setpoint on item '" + sp.item + "' has larger minValue than maxValue",
|
||||||
SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__MIN_VALUE));
|
SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__MIN_VALUE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ class SitemapValidator extends AbstractSitemapValidator {
|
|||||||
@Check
|
@Check
|
||||||
def void checkColortemperaturepicker(Colortemperaturepicker ctp) {
|
def void checkColortemperaturepicker(Colortemperaturepicker ctp) {
|
||||||
if (ctp.minValue !== null && ctp.maxValue !== null && ctp.minValue > ctp.maxValue) {
|
if (ctp.minValue !== null && ctp.maxValue !== null && ctp.minValue > ctp.maxValue) {
|
||||||
error("Colortemperaturepicker on item '" + ctp.item + "' has larger minValue than maxValue",
|
warning("Colortemperaturepicker on item '" + ctp.item + "' has larger minValue than maxValue",
|
||||||
SitemapPackage.Literals.COLORTEMPERATUREPICKER.getEStructuralFeature(SitemapPackage.COLORTEMPERATUREPICKER__MIN_VALUE));
|
SitemapPackage.Literals.COLORTEMPERATUREPICKER.getEStructuralFeature(SitemapPackage.COLORTEMPERATUREPICKER__MIN_VALUE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ class SitemapValidator extends AbstractSitemapValidator {
|
|||||||
if (i.inputHint !== null && !ALLOWED_HINTS.contains(i.inputHint)) {
|
if (i.inputHint !== null && !ALLOWED_HINTS.contains(i.inputHint)) {
|
||||||
val node = NodeModelUtils.getNode(i)
|
val node = NodeModelUtils.getNode(i)
|
||||||
val line = node.getStartLine()
|
val line = node.getStartLine()
|
||||||
error("Input on item '" + i.item + "' has invalid inputHint '" + i.inputHint + "' at line " + line,
|
warning("Input on item '" + i.item + "' has invalid inputHint '" + i.inputHint + "' at line " + line,
|
||||||
SitemapPackage.Literals.INPUT.getEStructuralFeature(SitemapPackage.INPUT__INPUT_HINT))
|
SitemapPackage.Literals.INPUT.getEStructuralFeature(SitemapPackage.INPUT__INPUT_HINT))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ class SitemapValidator extends AbstractSitemapValidator {
|
|||||||
if (i.interpolation !== null && !ALLOWED_INTERPOLATION.contains(i.interpolation)) {
|
if (i.interpolation !== null && !ALLOWED_INTERPOLATION.contains(i.interpolation)) {
|
||||||
val node = NodeModelUtils.getNode(i)
|
val node = NodeModelUtils.getNode(i)
|
||||||
val line = node.getStartLine()
|
val line = node.getStartLine()
|
||||||
error("Input on item '" + i.item + "' has invalid interpolation '" + i.interpolation + "' at line " + line,
|
warning("Input on item '" + i.item + "' has invalid interpolation '" + i.interpolation + "' at line " + line,
|
||||||
SitemapPackage.Literals.INPUT.getEStructuralFeature(SitemapPackage.CHART__INTERPOLATION))
|
SitemapPackage.Literals.INPUT.getEStructuralFeature(SitemapPackage.CHART__INTERPOLATION))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -22,7 +22,7 @@ import org.eclipse.xtext.nodemodel.util.NodeModelUtils
|
|||||||
import org.openhab.core.thing.ThingUID
|
import org.openhab.core.thing.ThingUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom validation rules.
|
* Custom validation rules.
|
||||||
*
|
*
|
||||||
* see http://www.eclipse.org/Xtext/documentation.html#validation
|
* see http://www.eclipse.org/Xtext/documentation.html#validation
|
||||||
*/
|
*/
|
||||||
@@ -36,24 +36,24 @@ class ThingValidator extends AbstractThingValidator {
|
|||||||
// We have to provide thingTypeId and a thingId
|
// We have to provide thingTypeId and a thingId
|
||||||
if (!thing.eIsSet(ThingPackage.Literals.MODEL_THING__THING_TYPE_ID)) {
|
if (!thing.eIsSet(ThingPackage.Literals.MODEL_THING__THING_TYPE_ID)) {
|
||||||
if (thing.eIsSet(ThingPackage.Literals.MODEL_PROPERTY_CONTAINER__ID)) {
|
if (thing.eIsSet(ThingPackage.Literals.MODEL_PROPERTY_CONTAINER__ID)) {
|
||||||
error("Provide a thing type ID and a thing ID in this format:\n <thingTypeId> <thingId>", ThingPackage.Literals.MODEL_PROPERTY_CONTAINER__ID)
|
warning("Provide a thing type ID and a thing ID in this format:\n <thingTypeId> <thingId>", ThingPackage.Literals.MODEL_PROPERTY_CONTAINER__ID)
|
||||||
} else {
|
} else {
|
||||||
if (thing.eIsSet(ThingPackage.Literals.MODEL_BRIDGE__BRIDGE)) {
|
if (thing.eIsSet(ThingPackage.Literals.MODEL_BRIDGE__BRIDGE)) {
|
||||||
error("Provide a thing type ID and a thing ID in this format:\n <thingTypeId> <thingId>", ThingPackage.Literals.MODEL_BRIDGE__BRIDGE)
|
warning("Provide a thing type ID and a thing ID in this format:\n <thingTypeId> <thingId>", ThingPackage.Literals.MODEL_BRIDGE__BRIDGE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!thing.eIsSet(ThingPackage.Literals.MODEL_THING__THING_ID)) {
|
if (!thing.eIsSet(ThingPackage.Literals.MODEL_THING__THING_ID)) {
|
||||||
error("Provide a thing type ID and a thing ID in this format:\n <thingTypeId> <thingId>", ThingPackage.Literals.MODEL_THING__THING_TYPE_ID)
|
warning("Provide a thing type ID and a thing ID in this format:\n <thingTypeId> <thingId>", ThingPackage.Literals.MODEL_THING__THING_TYPE_ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // thing in container
|
} else { // thing in container
|
||||||
if (thing.eIsSet(ThingPackage.Literals.MODEL_THING__THING_TYPE_ID) && thing.eIsSet(ThingPackage.Literals.MODEL_THING__THING_ID)) {
|
if (thing.eIsSet(ThingPackage.Literals.MODEL_THING__THING_TYPE_ID) && thing.eIsSet(ThingPackage.Literals.MODEL_THING__THING_ID)) {
|
||||||
val thingTypeIdFeature = NodeModelUtils.findNodesForFeature(thing, ThingPackage.Literals.MODEL_THING__THING_TYPE_ID).head
|
val thingTypeIdFeature = NodeModelUtils.findNodesForFeature(thing, ThingPackage.Literals.MODEL_THING__THING_TYPE_ID).head
|
||||||
val thingIdFeature = NodeModelUtils.findNodesForFeature(thing, ThingPackage.Literals.MODEL_THING__THING_ID).head
|
val thingIdFeature = NodeModelUtils.findNodesForFeature(thing, ThingPackage.Literals.MODEL_THING__THING_ID).head
|
||||||
val startOffset = thingTypeIdFeature.offset
|
val startOffset = thingTypeIdFeature.offset
|
||||||
val endOffset = thingIdFeature.endOffset
|
val endOffset = thingIdFeature.endOffset
|
||||||
getMessageAcceptor().acceptError("Provide a thing UID in this format:\n <bindingId>:<thingTypeId>:<thingId>", thing, startOffset, endOffset - startOffset, null, null)
|
getMessageAcceptor().acceptWarning("Provide a thing UID in this format:\n <bindingId>:<thingTypeId>:<thingId>", thing, startOffset, endOffset - startOffset, null, null)
|
||||||
} else {
|
} else {
|
||||||
if (thing.id !== null) {
|
if (thing.id !== null) {
|
||||||
try {
|
try {
|
||||||
@@ -66,7 +66,7 @@ class ThingValidator extends AbstractThingValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def private isNested(ModelThing thing) {
|
def private isNested(ModelThing thing) {
|
||||||
thing.eContainingFeature == ThingPackage.Literals.MODEL_BRIDGE__THINGS
|
thing.eContainingFeature == ThingPackage.Literals.MODEL_BRIDGE__THINGS
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-7
@@ -14,10 +14,10 @@ package org.openhab.core.model.yaml.internal.items;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNull;
|
import org.eclipse.jdt.annotation.NonNull;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.items.GroupFunction;
|
||||||
import org.openhab.core.model.yaml.internal.util.YamlElementUtils;
|
import org.openhab.core.model.yaml.internal.util.YamlElementUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,10 +28,6 @@ import org.openhab.core.model.yaml.internal.util.YamlElementUtils;
|
|||||||
*/
|
*/
|
||||||
public class YamlGroupDTO {
|
public class YamlGroupDTO {
|
||||||
|
|
||||||
private static final String DEFAULT_FUNCTION = "EQUALITY";
|
|
||||||
private static final Set<String> VALID_FUNCTIONS = Set.of("AND", "OR", "NAND", "NOR", "XOR", "COUNT", "AVG",
|
|
||||||
"MEDIAN", "SUM", "MIN", "MAX", "LATEST", "EARLIEST", DEFAULT_FUNCTION);
|
|
||||||
|
|
||||||
public String type;
|
public String type;
|
||||||
public String dimension;
|
public String dimension;
|
||||||
public String function;
|
public String function;
|
||||||
@@ -53,7 +49,7 @@ public class YamlGroupDTO {
|
|||||||
} else if (dimension != null) {
|
} else if (dimension != null) {
|
||||||
warnings.add("\"dimension\" field in group ignored as type is not Number");
|
warnings.add("\"dimension\" field in group ignored as type is not Number");
|
||||||
}
|
}
|
||||||
if (!VALID_FUNCTIONS.contains(getFunction())) {
|
if (!GroupFunction.VALID_FUNCTIONS.contains(getFunction())) {
|
||||||
errors.add("invalid value \"%s\" for \"function\" field".formatted(function));
|
errors.add("invalid value \"%s\" for \"function\" field".formatted(function));
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
@@ -65,7 +61,7 @@ public class YamlGroupDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getFunction() {
|
public String getFunction() {
|
||||||
return function != null ? function.toUpperCase() : DEFAULT_FUNCTION;
|
return function != null ? function.toUpperCase() : GroupFunction.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull List<@NonNull String> getParameters() {
|
public @NonNull List<@NonNull String> getParameters() {
|
||||||
|
|||||||
+1
-7
@@ -15,7 +15,6 @@ package org.openhab.core.model.yaml.internal.util;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
@@ -31,11 +30,6 @@ import org.openhab.core.util.StringUtils;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class YamlElementUtils {
|
public class YamlElementUtils {
|
||||||
|
|
||||||
private static final Set<String> VALID_ITEM_TYPES = Set.of(CoreItemFactory.SWITCH, CoreItemFactory.ROLLERSHUTTER,
|
|
||||||
CoreItemFactory.CONTACT, CoreItemFactory.STRING, CoreItemFactory.NUMBER, CoreItemFactory.DIMMER,
|
|
||||||
CoreItemFactory.DATETIME, CoreItemFactory.COLOR, CoreItemFactory.IMAGE, CoreItemFactory.PLAYER,
|
|
||||||
CoreItemFactory.LOCATION, CoreItemFactory.CALL);
|
|
||||||
|
|
||||||
public static boolean equalsConfig(@Nullable Map<String, Object> first, @Nullable Map<String, Object> second) {
|
public static boolean equalsConfig(@Nullable Map<String, Object> first, @Nullable Map<String, Object> second) {
|
||||||
if (first != null && second != null) {
|
if (first != null && second != null) {
|
||||||
return first.size() != second.size() ? false
|
return first.size() != second.size() ? false
|
||||||
@@ -57,7 +51,7 @@ public class YamlElementUtils {
|
|||||||
|
|
||||||
public static boolean isValidItemType(@Nullable String type) {
|
public static boolean isValidItemType(@Nullable String type) {
|
||||||
String adjustedType = getAdjustedItemType(type);
|
String adjustedType = getAdjustedItemType(type);
|
||||||
return adjustedType == null ? true : VALID_ITEM_TYPES.contains(adjustedType);
|
return adjustedType == null ? true : CoreItemFactory.VALID_ITEM_TYPES.contains(adjustedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isNumberItemType(@Nullable String type) {
|
public static boolean isNumberItemType(@Nullable String type) {
|
||||||
|
|||||||
+19
-19
@@ -82,15 +82,15 @@ public class GroupFunctionHelper {
|
|||||||
Unit<?> baseItemUnit = baseItem.getUnit();
|
Unit<?> baseItemUnit = baseItem.getUnit();
|
||||||
if (baseItemUnit != null) {
|
if (baseItemUnit != null) {
|
||||||
switch (functionName.toUpperCase()) {
|
switch (functionName.toUpperCase()) {
|
||||||
case "AVG":
|
case GroupFunction.AVG:
|
||||||
return new QuantityTypeArithmeticGroupFunction.Avg(baseItemUnit);
|
return new QuantityTypeArithmeticGroupFunction.Avg(baseItemUnit);
|
||||||
case "MEDIAN":
|
case GroupFunction.MEDIAN:
|
||||||
return new QuantityTypeArithmeticGroupFunction.Median(baseItemUnit);
|
return new QuantityTypeArithmeticGroupFunction.Median(baseItemUnit);
|
||||||
case "SUM":
|
case GroupFunction.SUM:
|
||||||
return new QuantityTypeArithmeticGroupFunction.Sum(baseItemUnit);
|
return new QuantityTypeArithmeticGroupFunction.Sum(baseItemUnit);
|
||||||
case "MIN":
|
case GroupFunction.MIN:
|
||||||
return new QuantityTypeArithmeticGroupFunction.Min(baseItemUnit);
|
return new QuantityTypeArithmeticGroupFunction.Min(baseItemUnit);
|
||||||
case "MAX":
|
case GroupFunction.MAX:
|
||||||
return new QuantityTypeArithmeticGroupFunction.Max(baseItemUnit);
|
return new QuantityTypeArithmeticGroupFunction.Max(baseItemUnit);
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ public class GroupFunctionHelper {
|
|||||||
final String functionName = function.name;
|
final String functionName = function.name;
|
||||||
final List<State> args;
|
final List<State> args;
|
||||||
switch (functionName.toUpperCase()) {
|
switch (functionName.toUpperCase()) {
|
||||||
case "AND":
|
case GroupFunction.AND:
|
||||||
args = parseStates(baseItem, function.params);
|
args = parseStates(baseItem, function.params);
|
||||||
if (args.size() == 2) {
|
if (args.size() == 2) {
|
||||||
return new ArithmeticGroupFunction.And(args.getFirst(), args.get(1));
|
return new ArithmeticGroupFunction.And(args.getFirst(), args.get(1));
|
||||||
@@ -110,7 +110,7 @@ public class GroupFunctionHelper {
|
|||||||
logger.error("Group function 'AND' requires two arguments. Using Equality instead.");
|
logger.error("Group function 'AND' requires two arguments. Using Equality instead.");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "OR":
|
case GroupFunction.OR:
|
||||||
args = parseStates(baseItem, function.params);
|
args = parseStates(baseItem, function.params);
|
||||||
if (args.size() == 2) {
|
if (args.size() == 2) {
|
||||||
return new ArithmeticGroupFunction.Or(args.getFirst(), args.get(1));
|
return new ArithmeticGroupFunction.Or(args.getFirst(), args.get(1));
|
||||||
@@ -118,7 +118,7 @@ public class GroupFunctionHelper {
|
|||||||
logger.error("Group function 'OR' requires two arguments. Using Equality instead.");
|
logger.error("Group function 'OR' requires two arguments. Using Equality instead.");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "NAND":
|
case GroupFunction.NAND:
|
||||||
args = parseStates(baseItem, function.params);
|
args = parseStates(baseItem, function.params);
|
||||||
if (args.size() == 2) {
|
if (args.size() == 2) {
|
||||||
return new ArithmeticGroupFunction.NAnd(args.getFirst(), args.get(1));
|
return new ArithmeticGroupFunction.NAnd(args.getFirst(), args.get(1));
|
||||||
@@ -126,7 +126,7 @@ public class GroupFunctionHelper {
|
|||||||
logger.error("Group function 'NOT AND' requires two arguments. Using Equality instead.");
|
logger.error("Group function 'NOT AND' requires two arguments. Using Equality instead.");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "NOR":
|
case GroupFunction.NOR:
|
||||||
args = parseStates(baseItem, function.params);
|
args = parseStates(baseItem, function.params);
|
||||||
if (args.size() == 2) {
|
if (args.size() == 2) {
|
||||||
return new ArithmeticGroupFunction.NOr(args.getFirst(), args.get(1));
|
return new ArithmeticGroupFunction.NOr(args.getFirst(), args.get(1));
|
||||||
@@ -134,7 +134,7 @@ public class GroupFunctionHelper {
|
|||||||
logger.error("Group function 'NOT OR' requires two arguments. Using Equality instead.");
|
logger.error("Group function 'NOT OR' requires two arguments. Using Equality instead.");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "XOR":
|
case GroupFunction.XOR:
|
||||||
args = parseStates(baseItem, function.params);
|
args = parseStates(baseItem, function.params);
|
||||||
if (args.size() == 2) {
|
if (args.size() == 2) {
|
||||||
return new ArithmeticGroupFunction.Xor(args.getFirst(), args.get(1));
|
return new ArithmeticGroupFunction.Xor(args.getFirst(), args.get(1));
|
||||||
@@ -142,7 +142,7 @@ public class GroupFunctionHelper {
|
|||||||
logger.error("Group function 'XOR' requires two arguments. Using Equality instead.");
|
logger.error("Group function 'XOR' requires two arguments. Using Equality instead.");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "COUNT":
|
case GroupFunction.COUNT:
|
||||||
if (function.params != null && function.params.length == 1) {
|
if (function.params != null && function.params.length == 1) {
|
||||||
State countParam = new StringType(function.params[0]);
|
State countParam = new StringType(function.params[0]);
|
||||||
return new ArithmeticGroupFunction.Count(countParam);
|
return new ArithmeticGroupFunction.Count(countParam);
|
||||||
@@ -150,21 +150,21 @@ public class GroupFunctionHelper {
|
|||||||
logger.error("Group function 'COUNT' requires one argument. Using Equality instead.");
|
logger.error("Group function 'COUNT' requires one argument. Using Equality instead.");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "AVG":
|
case GroupFunction.AVG:
|
||||||
return new ArithmeticGroupFunction.Avg();
|
return new ArithmeticGroupFunction.Avg();
|
||||||
case "MEDIAN":
|
case GroupFunction.MEDIAN:
|
||||||
return new ArithmeticGroupFunction.Median();
|
return new ArithmeticGroupFunction.Median();
|
||||||
case "SUM":
|
case GroupFunction.SUM:
|
||||||
return new ArithmeticGroupFunction.Sum();
|
return new ArithmeticGroupFunction.Sum();
|
||||||
case "MIN":
|
case GroupFunction.MIN:
|
||||||
return new ArithmeticGroupFunction.Min();
|
return new ArithmeticGroupFunction.Min();
|
||||||
case "MAX":
|
case GroupFunction.MAX:
|
||||||
return new ArithmeticGroupFunction.Max();
|
return new ArithmeticGroupFunction.Max();
|
||||||
case "LATEST":
|
case GroupFunction.LATEST:
|
||||||
return new DateTimeGroupFunction.Latest();
|
return new DateTimeGroupFunction.Latest();
|
||||||
case "EARLIEST":
|
case GroupFunction.EARLIEST:
|
||||||
return new DateTimeGroupFunction.Earliest();
|
return new DateTimeGroupFunction.Earliest();
|
||||||
case "EQUALITY":
|
case GroupFunction.EQUALITY:
|
||||||
return new GroupFunction.Equality();
|
return new GroupFunction.Equality();
|
||||||
default:
|
default:
|
||||||
logger.error("Unknown group function '{}'. Using Equality instead.", functionName);
|
logger.error("Unknown group function '{}'. Using Equality instead.", functionName);
|
||||||
|
|||||||
@@ -28,6 +28,26 @@ import org.openhab.core.types.UnDefType;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public interface GroupFunction {
|
public interface GroupFunction {
|
||||||
|
String EQUALITY = "EQUALITY";
|
||||||
|
String AND = "AND";
|
||||||
|
String OR = "OR";
|
||||||
|
String NAND = "NAND";
|
||||||
|
String NOR = "NOR";
|
||||||
|
String XOR = "XOR";
|
||||||
|
String COUNT = "COUNT";
|
||||||
|
String AVG = "AVG";
|
||||||
|
String MEDIAN = "MEDIAN";
|
||||||
|
String SUM = "SUM";
|
||||||
|
String MIN = "MIN";
|
||||||
|
String MAX = "MAX";
|
||||||
|
String LATEST = "LATEST";
|
||||||
|
String EARLIEST = "EARLIEST";
|
||||||
|
|
||||||
|
String DEFAULT = EQUALITY;
|
||||||
|
|
||||||
|
Set<String> VALID_FUNCTIONS = Set.of( //
|
||||||
|
EQUALITY, AND, OR, NAND, NOR, XOR, COUNT, AVG, MEDIAN, SUM, MIN, MAX, LATEST, EARLIEST //
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the current state of a group based on a list of items
|
* Determines the current state of a group based on a list of items
|
||||||
|
|||||||
+8
-2
@@ -12,6 +12,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.core.library;
|
package org.openhab.core.library;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.core.i18n.UnitProvider;
|
import org.openhab.core.i18n.UnitProvider;
|
||||||
@@ -57,6 +59,11 @@ public class CoreItemFactory implements ItemFactory {
|
|||||||
public static final String ROLLERSHUTTER = "Rollershutter";
|
public static final String ROLLERSHUTTER = "Rollershutter";
|
||||||
public static final String STRING = "String";
|
public static final String STRING = "String";
|
||||||
public static final String SWITCH = "Switch";
|
public static final String SWITCH = "Switch";
|
||||||
|
|
||||||
|
public static final Set<String> VALID_ITEM_TYPES = Set.of( //
|
||||||
|
CALL, COLOR, CONTACT, DATETIME, DIMMER, IMAGE, LOCATION, NUMBER, PLAYER, ROLLERSHUTTER, STRING, SWITCH //
|
||||||
|
);
|
||||||
|
|
||||||
private final UnitProvider unitProvider;
|
private final UnitProvider unitProvider;
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
@@ -90,7 +97,6 @@ public class CoreItemFactory implements ItemFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getSupportedItemTypes() {
|
public String[] getSupportedItemTypes() {
|
||||||
return new String[] { SWITCH, ROLLERSHUTTER, CONTACT, STRING, NUMBER, DIMMER, DATETIME, COLOR, IMAGE, PLAYER,
|
return VALID_ITEM_TYPES.toArray(new String[0]);
|
||||||
LOCATION, CALL };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ Fragment-Host: org.openhab.core.model.item
|
|||||||
org.eclipse.equinox.common;version='[3.19.0,3.19.1)',\
|
org.eclipse.equinox.common;version='[3.19.0,3.19.1)',\
|
||||||
junit-jupiter-api;version='[5.13.4,5.13.5)',\
|
junit-jupiter-api;version='[5.13.4,5.13.5)',\
|
||||||
junit-jupiter-engine;version='[5.13.4,5.13.5)',\
|
junit-jupiter-engine;version='[5.13.4,5.13.5)',\
|
||||||
|
junit-jupiter-params;version='[5.13.4,5.13.5)',\
|
||||||
junit-platform-commons;version='[1.13.4,1.13.5)',\
|
junit-platform-commons;version='[1.13.4,1.13.5)',\
|
||||||
junit-platform-engine;version='[1.13.4,1.13.5)',\
|
junit-platform-engine;version='[1.13.4,1.13.5)',\
|
||||||
junit-platform-launcher;version='[1.13.4,1.13.5)',\
|
junit-platform-launcher;version='[1.13.4,1.13.5)',\
|
||||||
|
|||||||
+73
@@ -27,11 +27,15 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.openhab.core.events.Event;
|
import org.openhab.core.events.Event;
|
||||||
import org.openhab.core.events.EventSubscriber;
|
import org.openhab.core.events.EventSubscriber;
|
||||||
import org.openhab.core.items.GenericItem;
|
import org.openhab.core.items.GenericItem;
|
||||||
@@ -47,10 +51,12 @@ import org.openhab.core.items.events.AbstractItemRegistryEvent;
|
|||||||
import org.openhab.core.items.events.ItemAddedEvent;
|
import org.openhab.core.items.events.ItemAddedEvent;
|
||||||
import org.openhab.core.items.events.ItemRemovedEvent;
|
import org.openhab.core.items.events.ItemRemovedEvent;
|
||||||
import org.openhab.core.items.events.ItemUpdatedEvent;
|
import org.openhab.core.items.events.ItemUpdatedEvent;
|
||||||
|
import org.openhab.core.library.CoreItemFactory;
|
||||||
import org.openhab.core.library.items.NumberItem;
|
import org.openhab.core.library.items.NumberItem;
|
||||||
import org.openhab.core.library.items.SwitchItem;
|
import org.openhab.core.library.items.SwitchItem;
|
||||||
import org.openhab.core.library.types.ArithmeticGroupFunction;
|
import org.openhab.core.library.types.ArithmeticGroupFunction;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.QuantityTypeArithmeticGroupFunction;
|
||||||
import org.openhab.core.model.core.EventType;
|
import org.openhab.core.model.core.EventType;
|
||||||
import org.openhab.core.model.core.ModelRepository;
|
import org.openhab.core.model.core.ModelRepository;
|
||||||
import org.openhab.core.model.core.ModelRepositoryChangeListener;
|
import org.openhab.core.model.core.ModelRepositoryChangeListener;
|
||||||
@@ -403,6 +409,34 @@ public class GenericItemProviderTest extends JavaOSGiTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> testGroupTypeSyntax() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("Group:Number Grp", "Number", GroupFunction.Equality.class),
|
||||||
|
Arguments.of("Group:Number:MAX Grp", "Number", ArithmeticGroupFunction.Max.class),
|
||||||
|
|
||||||
|
Arguments.of("Group:Number:Power Grp", "Number:Power", GroupFunction.Equality.class),
|
||||||
|
Arguments.of("Group:Number:Power:MAX Grp", "Number:Power",
|
||||||
|
QuantityTypeArithmeticGroupFunction.Max.class),
|
||||||
|
|
||||||
|
Arguments.of("Group:Switch:OR(ON,OFF) Grp", "Switch", ArithmeticGroupFunction.Or.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
public void testGroupTypeSyntax(String model, String expectedBaseType,
|
||||||
|
Class<? extends GroupFunction> expectedFunction) {
|
||||||
|
modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.getBytes()));
|
||||||
|
assertThat(itemRegistry.getAll(), hasSize(1));
|
||||||
|
|
||||||
|
if (itemRegistry.getAll().iterator().next() instanceof GroupItem groupItem) {
|
||||||
|
assertThat(groupItem.getBaseItem(), is(notNullValue()));
|
||||||
|
assertThat(groupItem.getBaseItem().getType(), is(expectedBaseType));
|
||||||
|
assertThat(groupItem.getFunction(), instanceOf(expectedFunction));
|
||||||
|
} else {
|
||||||
|
throw new AssertionError("Item is not a GroupItem");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStableOrder() {
|
public void testStableOrder() {
|
||||||
String model = "Group testGroup " + //
|
String model = "Group testGroup " + //
|
||||||
@@ -711,4 +745,43 @@ public class GenericItemProviderTest extends JavaOSGiTest {
|
|||||||
assertThat(stateDescription, is(notNullValue()));
|
assertThat(stateDescription, is(notNullValue()));
|
||||||
assertThat(stateDescription.getPattern(), is("%s"));
|
assertThat(stateDescription.getPattern(), is("%s"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKeywordsInOtherFields() {
|
||||||
|
String itemTypeList = CoreItemFactory.VALID_ITEM_TYPES.stream().sorted().collect(joining(", "));
|
||||||
|
String functionList = GroupFunction.VALID_FUNCTIONS.stream().sorted().collect(joining(", "));
|
||||||
|
String model = """
|
||||||
|
DateTime DateTime [%s]
|
||||||
|
Switch Switch "Switch" <Switch> [%s]
|
||||||
|
|
||||||
|
// Multiline item with keywords at the start of line
|
||||||
|
String
|
||||||
|
String
|
||||||
|
<
|
||||||
|
String
|
||||||
|
>
|
||||||
|
[
|
||||||
|
String
|
||||||
|
]
|
||||||
|
|
||||||
|
Number EQUALITY
|
||||||
|
""".formatted(itemTypeList, functionList);
|
||||||
|
modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.getBytes()));
|
||||||
|
|
||||||
|
assertThat(itemRegistry.getAll(), hasSize(4));
|
||||||
|
|
||||||
|
Item dateTimeItem = itemRegistry.get("DateTime");
|
||||||
|
assertThat(dateTimeItem.getTags().stream().sorted().collect(joining(", ")), is(equalTo(itemTypeList)));
|
||||||
|
|
||||||
|
Item switchItem = itemRegistry.get("Switch");
|
||||||
|
assertThat(switchItem.getTags().stream().sorted().collect(joining(", ")), is(equalTo(functionList)));
|
||||||
|
assertThat(switchItem.getLabel(), is("Switch"));
|
||||||
|
assertThat(switchItem.getCategory(), is("Switch"));
|
||||||
|
|
||||||
|
Item multilineItem = itemRegistry.get("String");
|
||||||
|
assertThat(multilineItem.getTags().stream().sorted().collect(joining(", ")), is(equalTo("String")));
|
||||||
|
assertThat(multilineItem.getCategory(), is("String"));
|
||||||
|
|
||||||
|
assertThat(itemRegistry.get("EQUALITY"), is(notNullValue()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user