/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * Copyright (c) 2014, MPL CodeInside http://codeinside.ru */ package ru.codeinside.gses.activiti.forms.definitions; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import org.activiti.engine.delegate.Expression; import org.activiti.engine.impl.bpmn.parser.BpmnParse; import org.activiti.engine.impl.bpmn.parser.BpmnParser; import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.el.ExpressionManager; import org.activiti.engine.impl.form.FormTypes; import org.activiti.engine.impl.persistence.entity.DeploymentEntity; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.impl.util.xml.Element; import org.apache.commons.lang.StringUtils; import ru.codeinside.gses.API; import ru.codeinside.gses.activiti.forms.api.definitions.NullAction; import ru.codeinside.gses.activiti.forms.api.definitions.PropertyNode; import ru.codeinside.gses.activiti.forms.api.definitions.PropertyTree; import ru.codeinside.gses.activiti.forms.api.definitions.PropertyType; import ru.codeinside.gses.activiti.forms.api.definitions.SandboxAware; import ru.codeinside.gses.activiti.forms.api.definitions.VariableType; import ru.codeinside.gses.activiti.forms.api.definitions.VariableTypes; import ru.codeinside.gses.activiti.forms.api.duration.DurationPreference; import ru.codeinside.gses.activiti.forms.api.duration.IllegalDurationExpression; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; public class FormParser { final static ImmutableSet<String> EXTRA_ATTRIBUTES = ImmutableSet.of( "#underline", "#tip", "#null", "#write", "#read" ); final static ImmutableSet<String> SMEV_TYPES = ImmutableSet.of("smevRequestEnclosure", "smevResponseEnclosure"); final static ImmutableSet<String> NO_VALUES = ImmutableSet.of("false", "no", "0", "n"); String deploymentId; String formKey; Map<String, FormProperty> formProperties; boolean startEvent; DurationPreference durationPreference = new DurationPreference(); Map<String, VariableType> variableTypes; BpmnParse bpmnParse; boolean signatureRequired; boolean isDataFlow; boolean isResultDataFlow; String consumerName; String requestType; //TODO сделать enum? String responseMessage; Map<String, Boolean> dataFlowParameters = new HashMap<String, Boolean>(); Map<String, Boolean> resultDataFlowParameters = new HashMap<String, Boolean>(); boolean sandbox; private PropertyTree buildTree() { if (bpmnParse.hasErrors()) { return null; } FormTypes formTypes = Context.getProcessEngineConfiguration().getFormTypes(); if (formTypes instanceof VariableTypes) { this.variableTypes = ((VariableTypes) formTypes).getTypes(); } else { throw new IllegalStateException("Конфигурация системы не поддерживает типы!"); } try { Map<String, PropertyParser> nodes = new LinkedHashMap<String, PropertyParser>(formProperties.size()); createNodes(nodes); for (PropertyParser propertyParser : nodes.values()) { propertyParser.process(nodes); } final List<PropertyParser> rootList = new ArrayList<PropertyParser>(); processBlocks(nodes, rootList); return convertNodes(nodes, rootList, durationPreference); } catch (BuildException e) { bpmnParse.addError(e.getMessage(), e.element); return null; } } PropertyTree convertNodes(Map<String, PropertyParser> nodes, List<PropertyParser> rootList, DurationPreference durationPreference) throws BuildException { final Collection<PropertyParser> values = nodes.values(); final Map<String, PropertyNode> global = new LinkedHashMap<String, PropertyNode>(); // сначала элементы которые не ссылаются for (final PropertyParser propertyParser : values) { propertyParser.convert1(global); } // затем ссылочные элементы for (final PropertyParser propertyParser : values) { propertyParser.convert2(global); } // затем индексы for (final PropertyParser propertyParser : values) { propertyParser.convert3(global); } final PropertyNode[] array = new PropertyNode[rootList.size()]; for (int i = 0; i < array.length; i++) { array[i] = global.get(rootList.get(i).property.id); } return new NTree( array, global, durationPreference, formKey, signatureRequired, isDataFlow, consumerName, dataFlowParameters, isResultDataFlow, requestType, responseMessage, resultDataFlowParameters); } void processBlocks(Map<String, PropertyParser> nodes, List<PropertyParser> rootList) throws BuildException { final ArrayList<PropertyParser> allPropertyParsers = new ArrayList<PropertyParser>(nodes.values()); final LinkedList<BlockStartParser> stack = new LinkedList<BlockStartParser>(); for (final PropertyParser propertyParser : allPropertyParsers) { final BlockStartParser block = stack.peekFirst(); propertyParser.block = block; final boolean end = (propertyParser instanceof EndBlockParser); if (!end) { if (block == null) { if (!(propertyParser instanceof SignatureParser) && !(propertyParser instanceof DataFlowParser) && !(propertyParser instanceof ResultDataFlowParser)) { rootList.add(propertyParser); } } else { block.items.add(propertyParser); } if (propertyParser instanceof BlockStartParser) { final BlockStartParser start = (BlockStartParser) propertyParser; start.items = new ArrayList<PropertyParser>(); stack.addFirst(start); } } else { if (block == null || !block.property.id.substring(1).equals(propertyParser.property.id.substring(1))) { throw new BuildException("Заверешение блока без начала", propertyParser); } stack.removeFirst(); } } PropertyParser badStart = stack.peekFirst(); if (badStart != null) { throw new BuildException("Начало блока без завершения", badStart); } for (PropertyParser propertyParser : allPropertyParsers) { if (propertyParser instanceof BlockStartParser) { final BlockStartParser start = (BlockStartParser) propertyParser; if (start.items.isEmpty()) { throw new BuildException("Пустой блок", propertyParser); } } } } void createNodes(Map<String, PropertyParser> nodes) throws BuildException { for (FormProperty handler : formProperties.values()) { String id = handler.id; if (!"!".equals(id)) { final PropertyParser propertyParser = createNode(handler); nodes.put(id, propertyParser); } else { try { durationPreference.parseWorkedDaysPreference(handler.name); if (startEvent) { if (handler.defaultExpression != null) { String defaultExpression = handler.defaultExpression.getExpressionText(); if (StringUtils.isNotBlank(defaultExpression)) { durationPreference.parseTaskDefaultPreference(defaultExpression); } } if (handler.variableExpression != null) { String periodExpression = handler.variableExpression.getExpressionText(); if (StringUtils.isNotBlank(periodExpression)) { durationPreference.parseProcessPreference(periodExpression); } } } else if (handler.variableExpression != null) { String expressionText = handler.variableExpression.getExpressionText(); if (StringUtils.isNotBlank(expressionText)) { durationPreference.parseTaskPreference(expressionText); } } } catch (IllegalDurationExpression err) { throw new BuildException(err.getMessage(), handler); } } } } PropertyParser createNode(FormProperty property) throws BuildException { final char firstChar = property.id.charAt(0); switch (firstChar) { case '^': return new TogglePropertyParser(property, PropertyType.TOGGLE); case '~': return new TogglePropertyParser(property, PropertyType.VISIBILITY_TOGGLE); case '+': return new BlockStartParser(property); case '-': return new EndBlockParser(property); } if ("signature".equals(property.type)) { signatureRequired = true; return new SignatureParser(property); } else if ("dataflow".equals(property.type)) { isDataFlow = true; if (property.values.containsKey("needSp") && property.values.get("needSp").equals("true")) { dataFlowParameters.put("needSp", true); } else { dataFlowParameters.put("needSp", false); } if (property.values.containsKey("needOv") && property.values.get("needOv").equals("true")) { dataFlowParameters.put("needOv", true); } else { dataFlowParameters.put("needOv", false); } if (property.values.containsKey("needTep") && property.values.get("needTep").equals("true")) { dataFlowParameters.put("needTep", true); } else { dataFlowParameters.put("needTep", false); } if (property.values.containsKey("needSend") && property.values.get("needSend").equals("true")) { dataFlowParameters.put("needSend", true); } else { dataFlowParameters.put("needSend", false); } if (property.values.containsKey("isLazyWriter") && property.values.get("isLazyWriter").equals("true")) { dataFlowParameters.put("isLazyWriter", true); } else { dataFlowParameters.put("isLazyWriter", false); } if (property.values.containsKey("consumerName")) { consumerName = property.values.get("consumerName"); } if (property.values.containsKey("appDataSignatureBlockPosition") && property.values.get("appDataSignatureBlockPosition").equals("last")) { dataFlowParameters.put("isAppDataSignatureBlockLast", true); } else { dataFlowParameters.put("isAppDataSignatureBlockLast", false); } return new DataFlowParser(property); } else if ("resultDataflow".equals(property.type)) { isResultDataFlow = true; if (property.values.containsKey("needSp") && property.values.get("needSp").equals("true")) { resultDataFlowParameters.put("needSp", true); } else { resultDataFlowParameters.put("needSp", false); } if (property.values.containsKey("needOv") && property.values.get("needOv").equals("true")) { resultDataFlowParameters.put("needOv", true); } else { resultDataFlowParameters.put("needOv", false); } if (property.values.containsKey("requestType")) { requestType = property.values.get("requestType"); } if (property.values.containsKey("responseMessage")) { responseMessage = property.values.get("responseMessage"); } if (property.values.containsKey("appDataSignatureBlockPosition") && property.values.get("appDataSignatureBlockPosition").equals("last")) { resultDataFlowParameters.put("isAppDataSignatureBlockLast", true); } else { resultDataFlowParameters.put("isAppDataSignatureBlockLast", false); } return new ResultDataFlowParser(property); } else if (property.type != null && SMEV_TYPES.contains(property.type)) { // вложения в запрос СМЭВ return new EnclosurePropertyParser(property); } else { return new GeneralPropertyParser(property); } } public PropertyTree parseProperties(Element activityElement, ProcessDefinitionEntity processDefinition, DeploymentEntity deployment, BpmnParse bpmnParse) { this.deploymentId = deployment.getId(); this.bpmnParse = bpmnParse; sandbox = ((SandboxAware) bpmnParse).isSandbox(); startEvent = activityElement.getTagName().equals("startEvent"); formKey = activityElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "formKey"); if (startEvent && formKey != null) { processDefinition.setStartFormKey(true); } Element extensionElement = activityElement.element("extensionElements"); if (extensionElement == null) { formProperties = Collections.emptyMap(); } else { ExpressionManager expressionManager = Context .getProcessEngineConfiguration() .getExpressionManager(); List<Element> formPropertyElements = extensionElement.elementsNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "formProperty"); formProperties = new LinkedHashMap<String, FormProperty>(formPropertyElements.size()); for (Element formPropertyElement : formPropertyElements) { FormProperty formProperty = new FormProperty(); formProperty.element = formPropertyElement; String id = StringUtils.trimToNull(formPropertyElement.attribute("id")); if (id == null) { bpmnParse.addError("attribute 'id' is required", formPropertyElement); } if (formProperties.containsKey(id)) { FormProperty dup = formProperties.get(id); bpmnParse.addError( "attribute 'id' is duplicated with (" + dup.element.getLine() + "," + dup.element.getColumn() + ")", formPropertyElement); } formProperties.put(id, formProperty); formProperty.id = id; formProperty.name = formPropertyElement.attribute("name"); formProperty.type = formPropertyElement.attribute("type"); formProperty.pattern = formPropertyElement.attribute("datePattern"); List<Element> valueElements = formPropertyElement.elementsNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "value"); if (!valueElements.isEmpty()) { Map<String, String> values = new LinkedHashMap<String, String>(valueElements.size()); for (Element valueElement : valueElements) { String valueId = valueElement.attribute("id"); String valueName = valueElement.attribute("name"); if (values.containsKey(valueId)) { if (sandbox) { bpmnParse.addError("Дублирование идентификатора '" + valueId + "'", valueElement); } } else { values.put(valueId, valueName); } } Set<String> valueIds = new HashSet<String>(values.keySet()); Map<String, String> extra = new LinkedHashMap<String, String>(valueElements.size()); for (String valueId : valueIds) { if (EXTRA_ATTRIBUTES.contains(valueId)) { extra.put(valueId, values.remove(valueId)); } } if (!values.isEmpty()) { formProperty.values = ImmutableMap.copyOf(values); } if (!extra.isEmpty()) { formProperty.extra = ImmutableMap.copyOf(extra); } } String requiredText = formPropertyElement.attribute("required", "false"); Boolean required = bpmnParse.parseBooleanAttribute(requiredText); if (required != null) { formProperty.isRequired = required; } else { bpmnParse.addError("attribute 'required' must be one of {on|yes|true|enabled|active|off|no|false|disabled|inactive}", formPropertyElement); } String readableText = formPropertyElement.attribute("readable", "true"); Boolean readable = bpmnParse.parseBooleanAttribute(readableText); if (readable != null) { formProperty.isReadable = readable; } else { bpmnParse.addError("attribute 'readable' must be one of {on|yes|true|enabled|active|off|no|false|disabled|inactive}", formPropertyElement); } String writableText = formPropertyElement.attribute("writable", "true"); Boolean writable = bpmnParse.parseBooleanAttribute(writableText); if (writable != null) { formProperty.isWritable = writable; } else { bpmnParse.addError("attribute 'writable' must be one of {on|yes|true|enabled|active|off|no|false|disabled|inactive}", formPropertyElement); } formProperty.variableName = formPropertyElement.attribute("variable"); String expressionText = formPropertyElement.attribute("expression"); if (expressionText != null) { formProperty.variableExpression = expressionManager.createExpression(expressionText); } String defaultExpressionText = formPropertyElement.attribute("default"); if (defaultExpressionText != null) { formProperty.defaultExpression = expressionManager.createExpression(defaultExpressionText); } } } return buildTree(); } // TODO: можно сразу привязывать узел к свойству final static class FormProperty { String id; String name; String type; boolean isReadable; boolean isWritable; boolean isRequired; String variableName; Expression variableExpression; Expression defaultExpression; Element element; String pattern; Map<String, String> values; Map<String, String> extra; } final static class BuildException extends Exception { final Element element; BuildException(String message, Element element) { super(message); this.element = element; } BuildException(String message, FormProperty formProperty) { super(message + ' ' + formProperty.id); this.element = formProperty.element; } BuildException(String message, PropertyParser parser) { this(message, parser.property); } } final class EnclosurePropertyParser extends PropertyParser { EnclosurePropertyParser(FormProperty property) { super(property); } @Override boolean acceptToggle() { return true; } @Override void process(Map<String, PropertyParser> global) throws BuildException { } @Override void convert1(Map<String, PropertyNode> global) throws BuildException { VariableType enclosureType = variableTypes.get("enclosure"); if (enclosureType == null) { throw new BuildException("Тип enclosure не поддерживается", this); } global.put(property.id, new EnclosureItem( property.id, property.name, property.variableName, getUnderline(), getTip(), getNullAction(), getType(), property.isReadable && property.isWritable, enclosureType )); } } final class EndBlockParser extends PropertyParser { EndBlockParser(FormProperty property) { super(property); } @Override void process(Map<String, PropertyParser> global) throws BuildException { if (property.name != null) { throw new BuildException("Атрибут 'Name' не применим к завершению блока", this); } if (property.variableExpression != null) { throw new BuildException("Атрибут 'Expression' не применим к завершению блока", this); } if (property.variableName != null) { throw new BuildException("Атрибут 'Variable' не применим к завершению блока", this); } if (property.defaultExpression != null) { throw new BuildException("Атрибут 'Default' не применим к завершению блока", this); } } @Override boolean acceptToggle() { return false; } } final class SignatureParser extends PropertyParser { SignatureParser(FormProperty property) { super(property); } @Override void process(Map<String, PropertyParser> global) throws BuildException { if (property.variableExpression != null) { throw new BuildException("Атрибут 'Expression' не применим к ЭЦП", this); } if (property.defaultExpression != null) { throw new BuildException("Атрибут 'Default' не применим к ЭЦП", this); } for (PropertyParser p : global.values()) { if (p != this && p instanceof SignatureParser) { throw new BuildException("Дублирование ЭЦП", this); } } } @Override boolean acceptToggle() { return false; } } final class DataFlowParser extends PropertyParser { DataFlowParser(FormProperty property) { super(property); } @Override void process(Map<String, PropertyParser> global) throws BuildException { if (!property.values.containsKey("consumerName")) { throw new BuildException("Не заполнено название потребителя (consumerName)", this); } for (PropertyParser p : global.values()) { if (p != this && p instanceof DataFlowParser) { throw new BuildException("Дублирование блока 'DataFlow'", this); } } } @Override boolean acceptToggle() { return false; } } final class ResultDataFlowParser extends PropertyParser { ResultDataFlowParser(FormProperty property) { super(property); } @Override void process(Map<String, PropertyParser> global) throws BuildException { if (!property.values.containsKey("requestType") || ( !"result".equals(property.values.get("requestType")) && !"status".equals(property.values.get("requestType")) ) ) { throw new BuildException("Не заполнен тип запроса (requestType)", this); } //TODO делать ли поле responseMessage обязательным? // if ("status".equals(property.values.get("requestType")) && !property.values.containsKey("responseMessage")) { // throw new BuildException("Для типа \"status\" поле \"responseMessage\" является обязательным", this); // } for (PropertyParser p : global.values()) { if (p != this && p instanceof ResultDataFlowParser) { throw new BuildException("Дублирование блока 'ResultDataFlow'", this); } } } @Override boolean acceptToggle() { return false; } } abstract class PropertyParser { final FormProperty property; /** * Блок-владелец */ PropertyParser block; PropertyParser(FormProperty property) { this.property = property; } boolean acceptToggle() { return true; } abstract void process(Map<String, PropertyParser> global) throws BuildException; VariableType getType() throws BuildException { String type = property.type == null ? "string" : property.type; if (!variableTypes.containsKey(type)) { throw new BuildException("Неизвестный тип " + property.type, this); } VariableType variableType = variableTypes.get(type); try { variableType.validateParams(property.pattern, property.values, sandbox); } catch (Exception e) { throw new BuildException(e.getMessage(), this); } return variableType; } void convert1(Map<String, PropertyNode> global) throws BuildException { } void convert2(Map<String, PropertyNode> global) { } void convert3(Map<String, PropertyNode> global) { } int getInteger(String key, int defaultValue) throws BuildException { if (property.values != null && property.values.containsKey(key)) { try { return Integer.parseInt(property.values.get(key)); } catch (NumberFormatException e) { throw new BuildException(key + " должно быть числом", this); } } return defaultValue; } boolean hasExtra(String key) { return property.extra != null && property.extra.containsKey(key); } String getExtra(final String key) { if (hasExtra(key)) { return StringUtils.trimToNull(property.extra.get(key)); } return null; } String getUnderline() { return getExtra("#underline"); } String getTip() { return getExtra("#tip"); } boolean isVarReadable() { String read = getExtra("#read"); return read == null || !NO_VALUES.contains(read.toLowerCase()); } boolean isVarWritable() { String read = getExtra("#write"); return read == null || !NO_VALUES.contains(read.toLowerCase()); } NullAction getNullAction() throws BuildException { // пропускать блоки подписи! if ("signature".equals(property.type)) { return NullAction.skip; } final String value = getExtra("#null"); if ("skip".equalsIgnoreCase(value)) { return NullAction.skip; } if ("remove".equalsIgnoreCase(value)) { return NullAction.remove; } if ("set".equalsIgnoreCase(value) || value == null) { return NullAction.set; } throw new BuildException("Неизвестное поведение для null: " + value, this); } } final class GeneralPropertyParser extends PropertyParser { Boolean readOverride = null; GeneralPropertyParser(FormProperty formProperty) { super(formProperty); } @Override boolean acceptToggle() { return true; } @Override boolean isVarReadable() { if (readOverride != null) { return readOverride; } return super.isVarReadable(); } @Override void process(Map<String, PropertyParser> global) throws BuildException { if (API.JSON_FORM.equals(property.id)) { Expression exp = property.defaultExpression; if (exp == null && StringUtils.isEmpty(property.variableName)) { throw new BuildException("Не указан url шаблона JSON формы", this); } if (exp != null && exp.getExpressionText() != null) { try { new URL(new URL("http://localhost:8080"), exp.getExpressionText()); } catch (MalformedURLException e) { throw new BuildException("Ошибка в url шаблона JSON формы", this); } } Set<String> keys = new HashSet<String>(global.keySet()); keys.remove(property.id); int count = 0; for (String key : keys) { PropertyParser propertyParser = global.get(key); if ( !(propertyParser instanceof GeneralPropertyParser || propertyParser instanceof SignatureParser || propertyParser instanceof DataFlowParser || propertyParser instanceof ResultDataFlowParser) ) { throw new BuildException("Недопустимый тип для JSON формы", propertyParser); } String type = propertyParser.property.type; if (type == null) { propertyParser.property.type = "json"; count++; } else if ("signature".equals(type)) { count += 0; } else if ("dataflow".equals(type)) { count += 0; } else if ("resultDataflow".equals(type)) { count += 0; } else if ("string".equals(type)) { count++; } else if ("json".equals(type)) { count++; } else { throw new BuildException("Недопустимый тип для переменной JSON", propertyParser); } if (count > 1) { throw new BuildException("Допустима лишь одна переменная JSON", propertyParser); } } if (count == 0) { throw new BuildException("Нет переменной для JSON формы", this); } if (!property.isReadable) { // fix readable property.isReadable = true; } // по умолчанию НЕ читать значение! if (!hasExtra("#read")) { readOverride = false; } } } @Override void convert1(Map<String, PropertyNode> global) throws BuildException { global.put(property.id, new NItem( property.id, getUnderline(), getTip(), getNullAction(), isVarReadable(), isVarWritable(), property.name, property.isReadable, property.isRequired, property.variableName, property.variableExpression, property.defaultExpression, getType(), property.isWritable, // readable & writable property.pattern, property.values )); } } final class TogglePropertyParser extends PropertyParser { PropertyType type; PropertyParser toggler; String toggleValue; boolean toggleTo; PropertyParser[] togglePropertyParsers; TogglePropertyParser(FormProperty property, PropertyType type) { super(property); this.type = type; } private boolean isWrongForToggle(final PropertyParser propertyParser) { return propertyParser == null || !propertyParser.acceptToggle(); } @Override void process(Map<String, PropertyParser> global) throws BuildException { final String name = property.name; if (name != null) { throw new BuildException("Атрибут 'Name' не применим к переключателю", this); } String expression = null; if (property.variableExpression != null) { expression = property.variableExpression.getExpressionText(); } if (expression == null) { throw new BuildException("Пропущен aтрибут 'Expression' для переключателя", this); } final Splitter varSplitter = Splitter.on(',').trimResults().omitEmptyStrings(); final Set<String> refs = Sets.newLinkedHashSet(varSplitter.split(expression)); if (refs.isEmpty()) { throw new BuildException("Пустой 'Expression' для переключателя", this); } final List<PropertyParser> togglePropertyParsers = new ArrayList<PropertyParser>(); for (final String ref : refs) { final PropertyParser depended = global.get(ref); if (isWrongForToggle(depended)) { throw new BuildException("Ошибка идентификтора " + ref + " в 'Expression' для переключателя", this); } togglePropertyParsers.add(depended); } this.togglePropertyParsers = togglePropertyParsers.toArray(new PropertyParser[togglePropertyParsers.size()]); String ref = property.variableName; if (ref == null) { ref = property.id.substring(1); } final PropertyParser var = global.get(ref); if (isWrongForToggle(var) || togglePropertyParsers.contains(var)) { throw new BuildException("Ошибка идентификтора " + ref + " в 'Variable' для переключателя", this); } toggler = var; String value = null; if (property.defaultExpression != null) { value = property.defaultExpression.getExpressionText(); } if (value == null) { value = "true"; } toggleValue = value; toggleTo = property.isRequired; } @Override void convert1(Map<String, PropertyNode> global) { // гарантируем порядок ключей global.put(property.id, null); } @Override void convert2(Map<String, PropertyNode> global) { final PropertyNode[] toggleNodes = new PropertyNode[this.togglePropertyParsers.length]; for (int i = 0; i < toggleNodes.length; i++) { toggleNodes[i] = global.get(this.togglePropertyParsers[i].property.id); } final NToggle nToggle = new NToggle(property.id, type, global.get(toggler.property.id), toggleValue, toggleTo, toggleNodes); global.put(property.id, nToggle); } } final class BlockStartParser extends PropertyParser { List<PropertyParser> items; int min = 0; int max = 999; BlockStartParser(FormProperty property) { super(property); } @Override void process(Map<String, PropertyParser> global) throws BuildException { if (property.type != null) { throw new BuildException("Атрибут 'Type' не применим для начала блока", this); } String variableName = property.variableName; if (variableName == null) { variableName = property.id.substring(1); property.variableName = variableName; } if (global.containsKey(variableName)) { throw new BuildException("Ошибка атрибута 'Variable', переменная уже используется", this); } if (property.isRequired) { min = 1; } if (property.variableExpression != null) { try { min = Integer.parseInt(property.variableExpression.getExpressionText()); } catch (NumberFormatException e) { throw new BuildException("'Expression' должо быть числом", this); } if (min < 0) { throw new BuildException("'Expression' не должно быть отрицательным", this); } property.variableExpression = null; } if (property.isRequired && min == 0) { throw new BuildException("'Expression' должен быть больше нуля при Required=true", this); } max = getInteger("max", max); if (min > max) { throw new BuildException("Минимум превышает максимум", this); } property.type = "long"; property.values = null; } @Override void convert1(Map<String, PropertyNode> global) throws BuildException { global.put(property.id, new NBlock( property.id, new PropertyNode[items.size()], min, max, getUnderline(), getTip(), getNullAction(), getType(), property.isWritable, property.variableName, property.isRequired, property.isReadable, property.name, property.defaultExpression, isVarReadable(), isVarWritable() )); } @Override void convert3(final Map<String, PropertyNode> global) { final PropertyNode[] array = ((NBlock) global.get(property.id)).getNodes(); for (int i = 0; i < array.length; i++) { array[i] = global.get(items.get(i).property.id); } } } }