/*
* 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) 2013, MPL CodeInside http://codeinside.ru
*/
package ru.codeinside.gses.webui.form;
import com.google.common.base.Joiner;
import com.vaadin.terminal.Sizeable;
import com.vaadin.ui.Component;
import com.vaadin.ui.Field;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.Reindeer;
import org.apache.commons.lang.StringUtils;
import ru.codeinside.adm.AdminServiceProvider;
import ru.codeinside.adm.database.Employee;
import ru.codeinside.gses.activiti.forms.FormID;
import ru.codeinside.gses.activiti.forms.api.definitions.BlockNode;
import ru.codeinside.gses.activiti.forms.api.definitions.PropertyCollection;
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.ToggleNode;
import ru.codeinside.gses.activiti.forms.api.values.Audit;
import ru.codeinside.gses.activiti.forms.api.values.BlockValue;
import ru.codeinside.gses.activiti.forms.api.values.FormValue;
import ru.codeinside.gses.activiti.forms.api.values.PropertyValue;
import ru.codeinside.gses.activiti.forms.types.FieldType;
import ru.codeinside.gses.activiti.forms.types.FieldTypes;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
final public class FieldTree implements Serializable {
final public Entry root;
final FormID formID;
PropertyTree propertyTree;
boolean hasSignature;
int maxDepth;
public FieldTree(FormID formID) {
this.formID = formID;
Entry root = new Entry();
root.type = Type.ROOT;
this.root = root;
}
public void create(FormValue formValue) {
propertyTree = formValue.getFormDefinition();
hasSignature = new Builder(formID, formValue.getPropertyValues(), formValue.isArchiveMode()).createAll(root);
maxDepth = getDeep(propertyTree);
}
public List<Entry> getEntries(String id) {
List<Entry> list = new ArrayList<Entry>();
root.getEntries(id, list);
return list;
}
public int getCols() {
return maxDepth + 1 + (hasSignature ? 1 : 0);
}
void updateColumnIndex() {
root.updateIndex(new AtomicInteger(-1));
}
int getDeep(final PropertyCollection collection) {
int deep = 0;
for (final PropertyNode node : collection.getNodes()) {
if (node.getPropertyType() == PropertyType.BLOCK) {
deep = Math.max(deep, getDeep((BlockNode) node));
}
}
return 1 + deep;
}
public String dumpTree() {
StringBuilder sb = new StringBuilder();
dumpTree(root, sb, 0);
return sb.toString();
}
private void dumpTree(FieldTree.Entry entry, StringBuilder sb, int level) {
for (int i = 0; i < level; i++) {
sb.append(' ');
}
if (entry.node != null) {
sb.append(entry.node.getId());
} else {
sb.append("-1");
}
sb.append(' ');
sb.append(entry.type);
sb.append(' ');
sb.append(entry.index);
if (entry.hidden) {
sb.append(" hidden");
}
sb.append('\n');
if (entry.items != null) {
for (FieldTree.Entry child : entry.items) {
dumpTree(child, sb, level + 1);
}
}
}
public void update(List<PropertyValue<?>> clones, Entry block, int i) {
new Builder(formID, null, false).createClone(clones, block, i);
}
public List<FormField> getFormFields() {
final List<FormField> list = new ArrayList<FormField>();
root.fillFormFields(list);
return list;
}
public void collect(Map<String, Object> values) {
root.collect(values);
}
enum Type {
ROOT, // корневой
ITEM, // обычный
BLOCK, // блок
CLONE, // клон блока
CONTROLS // управление блока
}
final public static class Entry implements Serializable, FormField {
Type type; // тип
PropertyNode node; // идентификатор шаблона свойства
String path; // путь (можно расчитывать)
int index; // индекс в сетке
int cloneCount; // число клонированых
int cloneIndex; // индекс клона
int cloneMin;
int cloneMax;
VerticalLayout underline;// поле с подписью под полем
Field field; // поле
boolean readOnly;
String caption;
Component sign; // блок подписи
Entry parent; // владелец
List<Entry> items; // дочерние элементы
boolean hidden;
boolean readable; // давать ли просматривать значение поля
List<ToggleNode> togglers;
void add(Entry entry) {
if (items == null) {
items = new ArrayList<Entry>();
}
entry.parent = this;
items.add(entry);
}
public String calcSuffix() {
final LinkedList<String> suffix = new LinkedList<String>();
Entry p = parent;
while (p != null) {
if (p.cloneIndex > 0) {
suffix.addFirst(Integer.toString(p.cloneIndex));
}
p = p.parent;
}
if (suffix.isEmpty()) {
return "";
}
return "_" + Joiner.on('_').join(suffix);
}
public int getDeepCount() {
int size = 0;
if (items != null) {
size += items.size();
for (Entry child : items) {
size += child.getDeepCount();
}
}
return size;
}
public int getControlsCount() {
int size = isSyntetic() ? 0 : (hidden ? 0 : 1);
if (!hidden && items != null) {
for (Entry child : items) {
size += child.getControlsCount();
}
}
return size;
}
boolean isSyntetic() {
return (type == Type.ROOT || type == Type.CLONE);
}
public void updateIndex(final AtomicInteger n) {
if (type == Type.CLONE || hidden) {
index = n.get();
} else {
index = n.getAndIncrement();
}
if (!hidden && items != null) {
for (final Entry child : items) {
child.updateIndex(n);
}
}
}
public Entry getEntry(final Field field) {
if (this.field == field) {
return this;
}
if (items != null) {
for (Entry child : items) {
Entry e = child.getEntry(field);
if (e != null) {
return e;
}
}
}
return null;
}
public int getLevel() {
int level = 0;
FieldTree.Entry p = parent;
while (p != null) {
if (!p.isSyntetic()) {
level++;
}
p = p.parent;
}
return level;
}
public void removeFields(GridForm gridForm) {
if (field != null) {
gridForm.removeItemProperty(path);
}
if (items != null) {
for (final Entry child : items) {
child.removeFields(gridForm);
}
}
}
public void getEntries(String id, List<Entry> list) {
if (node != null && id.equals(node.getId())) {
list.add(this);
}
if (items != null) {
for (final Entry child : items) {
child.getEntries(id, list);
}
}
}
public String getCaption() {
String result = caption;
FieldTree.Entry p = parent;
while (p != null) {
if (p.cloneIndex > 0) {
result = p.cloneIndex + ") " + result;
} else {
if (p.caption != null) {
result = p.caption + " " + result;
}
}
p = p.parent;
}
return result;
}
public void fillFormFields(final List<FormField> list) {
if (field != null && !readOnly) {
list.add(this);
}
if (items != null) {
for (final Entry child : items) {
child.fillFormFields(list);
}
}
}
@Override
public String getPropId() {
return path;
}
@Override
public String getName() {
return getCaption();
}
@Override
public Object getValue() {
if (hidden || field.getParent() == null) {
return null;
}
return field.getValue();
}
void fillExtra(Field field, PropertyValue value, boolean writable) {
final String nodeTip = value.getNode().getTip();
final String nodeUnderline = value.getNode().getUnderline();
this.field = field;
if (StringUtils.isNotEmpty(nodeTip)) {
field.setDescription(nodeTip);
}
if (StringUtils.isNotEmpty(nodeUnderline)) {
final VerticalLayout layout = new VerticalLayout();
layout.addComponent(field);
final Label hint = new Label("<sub>" + nodeUnderline + "</sub>", Label.CONTENT_XHTML);
if (StringUtils.isNotEmpty(nodeTip)) {
hint.setDescription(nodeTip);
}
layout.addComponent(hint);
underline = layout;
}
readOnly = !writable;
}
public void collect(Map<String, Object> values) {
if (field != null && !readOnly && node.isFieldWritable()) {
values.put(path, field.getValue());
}
if (items != null) {
for (Entry child : items) {
child.collect(values);
}
}
}
}
static class Builder {
final List<PropertyValue<?>> propertyValues;
final FormID formID;
private final boolean archiveMode;
boolean hasAudit;
public Builder(FormID formID, List<PropertyValue<?>> propertyValues, boolean archiveMode) {
this.propertyValues = propertyValues;
this.formID = formID;
this.archiveMode = archiveMode;
}
boolean createAll(Entry root) {
for (PropertyValue<?> propertyValue : propertyValues) {
create(propertyValue, root);
}
return hasAudit;
}
void createClone(List<PropertyValue<?>> clones, Entry owner, int i) {
Entry iClone = new Entry();
iClone.type = Type.CLONE;
iClone.cloneIndex = i;
iClone.node = owner.node;
owner.add(iClone);
for (PropertyValue<?> child : clones) {
create(child, iClone);
}
}
Field createField(PropertyValue value) {
FieldType fieldType = FieldTypes.getType(value.getNode().getVariableType());
String name = value.getNode().getName();
//TODO: передавать PropertyValue
return fieldType.createField(formID.taskId, value.getId(), name != null ? name : value.getId(), value.getValue(), value.getNode(), archiveMode);
}
void create(PropertyValue propertyValue, Entry owner) {
final PropertyType propertyType = propertyValue.getNode().getPropertyType();
final Entry entry;
final boolean writable = propertyValue.getNode().isFieldWritable() && !archiveMode;
switch (propertyType) {
case TOGGLE:
case VISIBILITY_TOGGLE:
break;
case ITEM:
entry = createEntry(owner, propertyValue);
entry.type = Type.ITEM;
entry.fillExtra(createField(propertyValue), propertyValue, writable);
break;
case ENCLOSURE:
entry = createEntry(owner, propertyValue);
entry.fillExtra(createField(propertyValue), propertyValue, writable);
entry.type = Type.ITEM;
entry.readable = false;
break;
case BLOCK:
entry = createEntry(owner, propertyValue);
entry.type = Type.BLOCK;
BlockValue blockValue = (BlockValue) propertyValue;
Long value = blockValue.getValue();
entry.fillExtra(new BlockField(formID.taskId, blockValue.getId(), value, entry.readOnly), propertyValue, writable);
if (value == null) {
entry.cloneCount = 0;
} else {
entry.cloneCount = value.intValue();
}
BlockNode block = (BlockNode) blockValue.getNode();
entry.cloneMin = block.getMinimum();
entry.cloneMax = block.getMaximum();
int i = 1;
for (List<PropertyValue<?>> childBlock : blockValue.getClones()) {
createClone(childBlock, entry, i++);
}
if (writable && entry.cloneMax > entry.cloneMin) {
Entry controls = new Entry();
controls.type = Type.CONTROLS;
owner.add(controls);
}
break;
default:
throw new IllegalArgumentException("Встретился неподдерживаем PropertyType " + propertyType);
}
}
private Entry createEntry(Entry owner, PropertyValue propertyValue) {
Entry entry = new Entry();
owner.add(entry);
entry.node = propertyValue.getNode();
entry.path = propertyValue.getId();
entry.caption = propertyValue.getNode().getName();
if (entry.caption == null) {
entry.caption = propertyValue.getId();
}
entry.sign = createSignInfo(propertyValue);
entry.readable = true;
return entry;
}
private Component createSignInfo(PropertyValue p) {
Audit audit = p.getAudit();
if (audit != null) {
String author;
{
String owner = audit.getOwner();
String organization = audit.getOrganization();
if (owner == null && organization == null) {
author = null;
} else {
author = "" + owner + " (" + organization + ")";
}
}
if (author == null) {
String login = audit.getLogin();
if (login != null) {
Employee employee = AdminServiceProvider.get().findEmployeeByLogin(login);
author = employee != null ? employee.getFio() : ("'" + login + "'");
}
}
if (author == null) {
return null;
}
hasAudit = true;
Label info = new Label(author);
if (audit.isVerified()) {
info.setCaption("Подписано:");
}
info.setStyleName("left");
info.addStyleName(Reindeer.LABEL_SMALL);
info.setWidth(150, Sizeable.UNITS_PIXELS);
info.setHeight(100, Sizeable.UNITS_PERCENTAGE);
return info;
}
return null;
}
}
}