/*
*
* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
* *
* * 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.
* *
* * For more information: http://www.orientechnologies.com
*
*/
package com.orientechnologies.orient.core.db.document;
import com.orientechnologies.common.collection.OMultiValue;
import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.ODocument;
import java.util.*;
/**
* This class allows to walk through all fields of single document using instance of {@link ODocumentFieldVisitor} class.
*
* Only current document and embedded documents will be walked. Which means that all embedded collections will be visited too and
* all embedded documents which are contained in this collections also will be visited.
*
* Fields values can be updated/converted too. If method {@link ODocumentFieldVisitor#visitField(OType, OType, Object)} will return
* new value original value will be updated but returned result will not be visited by {@link ODocumentFieldVisitor} instance.
*
* If currently processed value is collection or map of embedded documents or embedded document itself then method
* {@link ODocumentFieldVisitor#goDeeper(OType, OType, Object)} is called, if it returns false then this collection will not be
* visited by {@link ODocumentFieldVisitor} instance.
*
* Fields will be visited till method {@link ODocumentFieldVisitor#goFurther(OType, OType, Object, Object)} returns true.
*/
public class ODocumentFieldWalker {
public void walkDocument(ODocument document, ODocumentFieldVisitor fieldWalker) {
final Set<ODocument> walked = Collections.newSetFromMap(new IdentityHashMap<ODocument, Boolean>());
walkDocument(document, fieldWalker, walked);
walked.clear();
}
private void walkDocument(ODocument document, ODocumentFieldVisitor fieldWalker, Set<ODocument> walked) {
if (walked.contains(document))
return;
walked.add(document);
boolean oldLazyLoad = document.isLazyLoad();
document.setLazyLoad(false);
final boolean updateMode = fieldWalker.updateMode();
final OClass clazz = document.getSchemaClass();
for (String fieldName : document.fieldNames()) {
final OType concreteType = document.fieldType(fieldName);
OType fieldType = concreteType;
OType linkedType = null;
if (fieldType == null && clazz != null) {
OProperty property = clazz.getProperty(fieldName);
if (property != null) {
fieldType = property.getType();
linkedType = property.getLinkedType();
}
}
Object fieldValue = document.field(fieldName, fieldType);
Object newValue = fieldWalker.visitField(fieldType, linkedType, fieldValue);
boolean updated;
if (updateMode)
updated = updateFieldValueIfChanged(document, fieldName, fieldValue, newValue, concreteType);
else
updated = false;
// exclude cases when:
// 1. value was updated.
// 2. we use link types.
// 3. document is not not embedded.
if (!updated
&& fieldValue != null
&& !(OType.LINK.equals(fieldType) || OType.LINKBAG.equals(fieldType) || OType.LINKLIST.equals(fieldType)
|| OType.LINKSET.equals(fieldType) || (fieldValue instanceof ORecordLazyMultiValue))) {
if (fieldWalker.goDeeper(fieldType, linkedType, fieldValue)) {
if (fieldValue instanceof Map)
walkMap((Map) fieldValue, fieldType, fieldWalker, walked);
else if (OMultiValue.isIterable(fieldValue))
walkIterable(OMultiValue.getMultiValueIterable(fieldValue), fieldType, fieldWalker, walked);
else if (fieldValue instanceof ODocument) {
final ODocument doc = (ODocument) fieldValue;
if (OType.EMBEDDED.equals(fieldType) || doc.isEmbedded())
walkDocument((ODocument) fieldValue, fieldWalker);
}
}
}
if (!fieldWalker.goFurther(fieldType, linkedType, fieldValue, newValue)) {
document.setLazyLoad(oldLazyLoad);
return;
}
}
document.setLazyLoad(oldLazyLoad);
}
private void walkMap(Map map, OType fieldType, ODocumentFieldVisitor fieldWalker, Set<ODocument> walked) {
for (Object value : map.values()) {
if (value instanceof ODocument) {
final ODocument doc = (ODocument) value;
// only embedded documents are walked
if (OType.EMBEDDEDMAP.equals(fieldType) || doc.isEmbedded())
walkDocument((ODocument) value, fieldWalker, walked);
}
}
}
private void walkIterable(Iterable iterable, OType fieldType, ODocumentFieldVisitor fieldWalker, Set<ODocument> walked) {
for (Object value : iterable) {
if (value instanceof ODocument) {
final ODocument doc = (ODocument) value;
// only embedded documents are walked
if (OType.EMBEDDEDLIST.equals(fieldType) || OType.EMBEDDEDSET.equals(fieldType) || doc.isEmbedded())
walkDocument((ODocument) value, fieldWalker, walked);
}
}
}
private boolean updateFieldValueIfChanged(ODocument document, String fieldName, Object fieldValue, Object newValue,
OType concreteType) {
if (fieldValue != newValue) {
document.field(fieldName, newValue, concreteType);
return true;
}
return false;
}
}