/*
* Copyright 2014-2015 CyberVision, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kaaproject.avro.ui.shared;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class FormContext implements Serializable {
private static final long serialVersionUID = -3706030853085644429L;
private int idSequence = 0;
private Map<Fqn, RecordField> recordsMetadata = new HashMap<>();
private RecordField rootRecord;
private Map<FqnKey, Fqn> declaredTypes = new HashMap<>();
private Map<Fqn, FqnKey> fqnToKeyMap = new HashMap<>();
private Map<Integer, RecordField> typeHolders = new HashMap<>();
private Map<Integer, RecordField> typeConsumers = new HashMap<>();
private Map<Fqn, List<Integer>> ctlTypes = new HashMap<>();
private Map<Fqn, FqnVersion> ctlDependencies = new HashMap<>();
private List<FqnVersion> ctlDependenciesList = new ArrayList<>();
private boolean isCtlSchema = false;
private transient List<DeclaredTypesListener> declaredTypesListeners = new ArrayList<>();
private transient List<CtlDependenciesListener> ctlDependenciesListeners = new ArrayList<>();
public FormContext() {
}
public FormContext(Map<Fqn, List<Integer>> ctlTypes) {
this.ctlTypes.putAll(ctlTypes);
for (List<Integer> versions : this.ctlTypes.values()) {
Collections.sort(versions);
}
this.isCtlSchema = true;
for (Fqn fqn : this.ctlTypes.keySet()) {
FqnKey key = new FqnKey(fqn);
declaredTypes.put(key, fqn);
fqnToKeyMap.put(fqn, key);
}
}
protected int nextFieldId() {
return idSequence++;
}
public void putRecordMetadata(String namespace, String name, RecordField field) {
putRecordMetadata(new Fqn(namespace, name), field);
}
public void putRecordMetadata(Fqn fqn, RecordField field) {
recordsMetadata.put(fqn, field);
}
public boolean containsRecordMetadata(String namespace, String name) {
return containsRecordMetadata(new Fqn(namespace, name));
}
public boolean containsRecordMetadata(Fqn fqn) {
return recordsMetadata.containsKey(fqn);
}
public RecordField getRecordMetadata(String namespace, String name) {
return getRecordMetadata(new Fqn(namespace, name));
}
public RecordField getRecordMetadata(Fqn fqn) {
return recordsMetadata.get(fqn);
}
public void setRootRecord(RecordField rootRecord) {
this.rootRecord = rootRecord;
}
public RecordField getRootRecord() {
return this.rootRecord;
}
public boolean isCtlSchema() {
return isCtlSchema;
}
private FqnKey overridenCtlKey = null;
public void updateTypeHolder(RecordField typeHolder) {
FqnKey key = new FqnKey(typeHolder.getId());
Fqn fqn = typeHolder.getDeclaredFqn();
Map<FqnKey, List<RecordField>> affectedConsumers = new HashMap<>();
if (isCtlSchema && typeHolder.isRoot()) {
if (overridenCtlKey != null && !overridenCtlKey.getFqn().equals(fqn)) {
declaredTypes.put(overridenCtlKey, overridenCtlKey.getFqn());
fqnToKeyMap.put(overridenCtlKey.getFqn(), overridenCtlKey);
List<RecordField> matchedConsumers = new ArrayList<>();
for (RecordField consumer : typeConsumers.values()) {
if (key.equals(consumer.getConsumedFqnKey())) {
matchedConsumers.add(consumer);
}
}
affectedConsumers.put(overridenCtlKey, matchedConsumers);
overridenCtlKey = null;
}
if (ctlTypes.containsKey(fqn)) {
overridenCtlKey = new FqnKey(fqn);
declaredTypes.remove(overridenCtlKey);
fqnToKeyMap.remove(fqn);
List<RecordField> matchedConsumers = new ArrayList<>();
for (RecordField consumer : typeConsumers.values()) {
if (overridenCtlKey.equals(consumer.getConsumedFqnKey())) {
matchedConsumers.add(consumer);
}
}
affectedConsumers.put(key, matchedConsumers);
}
}
typeHolders.put(typeHolder.getId(), typeHolder);
Fqn oldFqn = declaredTypes.put(key, fqn);
if (oldFqn != null) {
fqnToKeyMap.remove(oldFqn);
}
if (fqn != null) {
fqnToKeyMap.put(fqn, key);
}
fireDeclaredTypesChanged();
for (FqnKey keyToSet : affectedConsumers.keySet()) {
List<RecordField> consumers = affectedConsumers.get(keyToSet);
for (RecordField consumer : consumers) {
consumer.updateConsumedFqnKey(keyToSet);
}
}
if (typeHolder.isRoot()) {
for (RecordField holder : typeHolders.values()) {
if (!holder.isRoot()) {
updateTypeHolder(holder);
}
}
}
}
public Map<Integer, RecordField> getTypeHolders() {
return typeHolders;
}
public boolean isFqnAlreadyDeclared(int id, Fqn fqn, boolean localOnly) {
if (fqn != null) {
FqnKey declaredKey = fqnToKeyMap.get(fqn);
if (declaredKey != null) {
if ((!declaredKey.isLocalFqn() && !localOnly) ||
(declaredKey.isLocalFqn() && declaredKey.getId().intValue() != id)) {
return true;
}
}
}
return false;
}
public boolean checkIsVersionAvailable(Fqn fqn, Integer version) {
if (fqn != null) {
List<Integer> versions = ctlTypes.get(fqn);
if (versions != null) {
return !versions.contains(version);
}
}
return true;
}
private FormField getRootParent(FormField formField) {
FormField parent = formField;
while (true) {
if (parent.getParentField() != null) {
parent = parent.getParentField();
} else {
return parent;
}
}
}
private boolean isAttachedToRoot(FormField formField) {
FormField rootParent = getRootParent(formField);
return rootParent.getId() == rootRecord.getId();
}
private List<RecordField> getAttachedTypeConsumers() {
List<RecordField> attachedTypeConsumers = new ArrayList<>();
for (RecordField typeConsumer : typeConsumers.values()) {
if (isAttachedToRoot(typeConsumer)) {
attachedTypeConsumers.add(typeConsumer);
}
}
return attachedTypeConsumers;
}
private Map<FqnKey, RecordField> getDetachedTypeHolders() {
Map<FqnKey, RecordField> detachedTypeHolders = new HashMap<>();
for (RecordField typeHolder : typeHolders.values()) {
if (!isAttachedToRoot(typeHolder)) {
detachedTypeHolders.put(new FqnKey(typeHolder.getId()), typeHolder);
}
}
return detachedTypeHolders;
}
public boolean removeTypeHolder(RecordField typeHolder) {
if (!typeHolders.containsKey(typeHolder.getId())) {
return true;
}
FormField parent = typeHolder.getParentField();
if (parent != null && parent instanceof UnionField) {
((UnionField)parent).setValue(null, true, false);
parent.setParentField(null);
}
boolean foundDetachedReference = false;
do {
foundDetachedReference = false;
List<RecordField> attachedTypeConsumers = getAttachedTypeConsumers();
Map<FqnKey, RecordField> detachedTypeHolders = getDetachedTypeHolders();
for (RecordField attachedTypeConsumer : attachedTypeConsumers) {
FqnKey fqnKey = attachedTypeConsumer.getConsumedFqnKey();
if (fqnKey != null) {
RecordField detachedTypeHolder = detachedTypeHolders.get(fqnKey);
if (detachedTypeHolder != null) {
foundDetachedReference = true;
switchTypeReference(detachedTypeHolder, attachedTypeConsumer);
}
}
}
} while (foundDetachedReference);
boolean removed = false;
if (typeHolder.getParentField() == null || typeHolder.getParentField().getParentField() == null) {
FqnKey key = new FqnKey(typeHolder.getId());
typeHolders.remove(typeHolder.getId());
Fqn fqn = declaredTypes.remove(key);
if (fqn != null) {
fqnToKeyMap.remove(fqn);
}
fireDeclaredTypesChanged();
removed = true;
}
orderSchemaTypes();
return removed;
}
private void switchTypeReference(RecordField typeHolder, RecordField typeConsumer) {
UnionField parentUnion = (UnionField)typeHolder.getParentField();
if (parentUnion != null) {
FormField reference = typeConsumer.clone(true);
parentUnion.setValue(reference, true, false);
}
parentUnion = (UnionField)typeConsumer.getParentField();
parentUnion.setValue(typeHolder);
}
public void orderSchemaTypes() {
boolean typesSwitched = false;
do {
Set<Integer> checkedTypeHolders = new HashSet<>();
checkedTypeHolders.addAll(typeHolders.keySet());
Map<FqnKey, RecordField> consumers = new HashMap<>();
for (FormField field : rootRecord) {
if (field instanceof RecordField) {
RecordField recordField = (RecordField)field;
if (recordField.isTypeHolder()) {
checkedTypeHolders.remove(recordField.getId());
} else if (recordField.isTypeConsumer()) {
FqnKey key = recordField.getConsumedFqnKey();
if (key != null && key.isLocalFqn()) {
if (checkedTypeHolders.remove(key.getId())) {
consumers.put(key, recordField);
}
}
}
}
}
typesSwitched = !consumers.isEmpty();
for (FqnKey key : consumers.keySet()) {
RecordField consumer = consumers.get(key);
RecordField holder = typeHolders.get(key.getId());
switchTypeReference(holder, consumer);
}
} while (typesSwitched);
}
public void registerTypeConsumer(RecordField consumer) {
typeConsumers.put(consumer.getId(), consumer);
updateCtlDependencies();
}
public void unregisterTypeConsumer(Integer id) {
typeConsumers.remove(id);
updateCtlDependencies();
}
public void updateCtlDependencies() {
Set<Fqn> consumedFqns = new HashSet<>();
for (RecordField typeConsumer : typeConsumers.values()) {
FqnKey key = typeConsumer.getConsumedFqnKey();
if (key != null && !key.isLocalFqn()) {
Fqn fqn = key.getFqn();
consumedFqns.add(fqn);
}
}
Set<Fqn> toRemove = new HashSet<>();
for (Fqn key : ctlDependencies.keySet()) {
if (!consumedFqns.contains(key)) {
toRemove.add(key);
}
}
Set<Fqn> toAdd = new HashSet<>();
for (Fqn key : consumedFqns) {
if (!ctlDependencies.containsKey(key)) {
toAdd.add(key);
}
}
boolean changed = !toRemove.isEmpty() || !toAdd.isEmpty();
for (Fqn key : toRemove) {
ctlDependencies.remove(key);
}
for (Fqn key : toAdd) {
FqnVersion fqnVersion = new FqnVersion(key, getMaxVersion(key));
ctlDependencies.put(key, fqnVersion);
}
if (changed) {
ctlDependenciesList.clear();
ctlDependenciesList.addAll(ctlDependencies.values());
Collections.sort(ctlDependenciesList);
}
fireCtlDependenciesChanged();
}
public List<FqnVersion> getCtlDependenciesList() {
return ctlDependenciesList;
}
public void setCtlDependenciesList(List<FqnVersion> fqnVersions) {
ctlDependencies.clear();
ctlDependenciesList.clear();
for (FqnVersion fqnVersion : fqnVersions) {
List<Integer> versions = getAvailableVersions(fqnVersion.getFqn());
if (versions != null) {
if (!versions.contains(fqnVersion.getVersion())) {
fqnVersion.setVersion(getMaxVersion(fqnVersion.getFqn()));
}
ctlDependencies.put(fqnVersion.getFqn(), fqnVersion);
}
}
ctlDependenciesList.addAll(ctlDependencies.values());
Collections.sort(ctlDependenciesList);
}
public Integer getMaxVersion(Fqn fqn) {
List<Integer> versions = ctlTypes.get(fqn);
if (versions != null) {
return Collections.max(versions);
}
return null;
}
public List<Integer> getAvailableVersions(Fqn fqn) {
return ctlTypes.get(fqn);
}
public FqnKey fqnToFqnKey(Fqn fqn) {
return fqnToKeyMap.get(fqn);
}
public Map<FqnKey, Fqn> getDeclaredTypes() {
return declaredTypes;
}
public boolean containsDeclaredType(FqnKey key) {
return declaredTypes.containsKey(key);
}
public void addDeclaredTypesListener(DeclaredTypesListener listener) {
declaredTypesListeners.add(listener);
}
public void removeDeclaredTypesListener(DeclaredTypesListener listener) {
declaredTypesListeners.remove(listener);
}
public void addCtlDependenciesListener(CtlDependenciesListener listener) {
ctlDependenciesListeners.add(listener);
}
public void removeCtlDependenciesListener(CtlDependenciesListener listener) {
ctlDependenciesListeners.remove(listener);
}
private void fireDeclaredTypesChanged() {
for (DeclaredTypesListener listener : declaredTypesListeners) {
listener.onDeclaredTypesUpdated(declaredTypes);
}
}
public void fireCtlDependenciesChanged() {
for (CtlDependenciesListener listener : ctlDependenciesListeners) {
listener.onCtlDependenciesUpdated(ctlDependenciesList);
}
}
public static interface DeclaredTypesListener {
public void onDeclaredTypesUpdated (Map<FqnKey, Fqn> declaredTypes);
}
public static interface CtlDependenciesListener {
public void onCtlDependenciesUpdated (List<FqnVersion> ctlDependenciesList);
}
}