/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.mongodb.language.update;
import com.torodb.core.exceptions.SystemException;
import com.torodb.core.exceptions.user.UpdateException;
import com.torodb.core.language.AttributeReference;
import com.torodb.kvdocument.values.KvValue;
import java.util.List;
import javax.annotation.Nullable;
/**
*
*/
class AttributeReferenceToBuilderCallback {
/**
* Descends through the value tree associated to the given builder as following keys and return
* the {@linkplain BuilderCallback callback} associated with it.
* <p>
* @param <K> the type of the keys of given builder
* @param builder the builder that will be the root of the given keys
* @param keys the keys path that will be followed
* @param createIfNecessary if true, new child values will be created if there is no value with
* the given keys in the given builder value tree. If false, null will be
* returned in that case.
* <p>
* @throws UpdateException if keys is invalid in the given builder. That is: if there is an index
* i where resolve(builder, keys.subList(0,i), createIfNecessary) is a
* builder of type K1 and keys.get(i+1) is not of type K1 OR if
* resolve(builder, keys.subList(0,i-1), createIfNecessary) has a scalar
* value as child indexed with keys.get(i-1)
*/
@Nullable
public static <R, K> R resolve(
BuilderCallback<K> builder,
List<AttributeReference.Key<?>> keys,
boolean createIfNecessary,
ResolvedCallback<R> callback
) throws UpdateException {
return resolve(builder, keys, 0, keys.size(), createIfNecessary, callback);
}
@Nullable
private static <R, K> R resolve(
BuilderCallback<K> builder,
List<AttributeReference.Key<?>> keys,
int fromIndex,
int toIndex,
boolean createIfNecessary,
ResolvedCallback<R> callback
) throws UpdateException {
Object uncastedKey = keys.get(fromIndex).getKeyValue();
if (!builder.getKeyClass().isInstance(uncastedKey)) {
throw new UpdateException(
"Cannot use the part ("
+ keys.subList(fromIndex, fromIndex + 1) + " of "
+ keys.subList(fromIndex, toIndex) + ") to transverse "
+ "the element " + builder);
}
K key = builder.getKeyClass().cast(uncastedKey);
UpdatedToroDocumentArrayBuilder nextArrayBuilder;
UpdatedToroDocumentBuilder nextObjectBuilder;
KvValue<?> nextValue;
if (!builder.contains(key)) {
return nonExistingKeyCase(
builder,
keys,
fromIndex,
toIndex,
createIfNecessary,
key,
callback
);
} else {
if (builder.isObjectBuilder(key)) {
nextObjectBuilder = builder.getObjectBuilder(key);
nextArrayBuilder = null;
nextValue = null;
} else if (builder.isArrayBuilder(key)) {
nextObjectBuilder = null;
nextArrayBuilder = builder.getArrayBuilder(key);
nextValue = null;
} else {
nextObjectBuilder = null;
nextArrayBuilder = null;
nextValue = builder.getValue(key);
}
}
if (fromIndex == toIndex - 1) {
if (nextObjectBuilder != null) {
return callback.objectReferenced(builder, key, nextObjectBuilder);
}
if (nextArrayBuilder != null) {
return callback.arrayReferenced(builder, key, nextArrayBuilder);
}
if (nextValue != null) {
return callback.valueReferenced(builder, key, nextValue);
}
throw new AssertionError();
} else {
BuilderCallback<?> nextBuilder = null;
if (nextObjectBuilder != null) {
nextBuilder = new ObjectBuilderCallback(nextObjectBuilder);
}
if (nextArrayBuilder != null) {
nextBuilder = new ArrayBuilderCallback(nextArrayBuilder);
}
if (nextValue != null) {
throw new UpdateException(
"Cannot use the part ("
+ keys.subList(fromIndex, fromIndex + 1) + " of "
+ keys.subList(fromIndex, toIndex) + ") to "
+ "transverse the element " + builder);
}
if (nextBuilder == null) {
throw new AssertionError();
}
return resolve(
nextBuilder,
keys,
fromIndex + 1,
toIndex,
createIfNecessary,
callback);
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static <R, K> R nonExistingKeyCase(
BuilderCallback<K> builder,
List<AttributeReference.Key<?>> keys,
int fromIndex,
int toIndex,
boolean createIfNecessary,
K key,
ResolvedCallback<R> callback
) throws UpdateException {
if (!createIfNecessary) {
return null;
}
BuilderCallback newBuilder = builder;
for (int i = fromIndex; i < toIndex - 1; i++) {
AttributeReference.Key<?> iestKey = keys.get(i);
AttributeReference.Key<?> nextKey = keys.get(i + 1);
if (nextKey instanceof AttributeReference.ObjectKey) {
newBuilder = new ObjectBuilderCallback(
newBuilder.newObject(iestKey.getKeyValue())
);
} else if (nextKey instanceof AttributeReference.ArrayKey) {
newBuilder = new ArrayBuilderCallback(
newBuilder.newArray(iestKey.getKeyValue())
);
} else {
throw new SystemException("Unexpected key");
}
}
AttributeReference.Key<?> lastKey = keys.get(toIndex - 1);
if (lastKey instanceof AttributeReference.ObjectKey) {
assert newBuilder instanceof ObjectBuilderCallback;
String castedLastKey = ((AttributeReference.ObjectKey) lastKey).getKeyValue();
return callback.newElementReferenced(
(ObjectBuilderCallback) newBuilder,
castedLastKey
);
} else if (lastKey instanceof AttributeReference.ArrayKey) {
assert newBuilder instanceof ArrayBuilderCallback;
Integer castedLastKey = ((AttributeReference.ArrayKey) lastKey).getKeyValue();
return callback.newElementReferenced(
(ArrayBuilderCallback) newBuilder,
castedLastKey
);
} else {
throw new SystemException("Unexpected key");
}
}
}