/*
* Copyright 2013 eBuddy B.V.
*
* 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 com.ebuddy.cassandra.structure;
import static org.apache.commons.lang3.ObjectUtils.NULL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.ebuddy.cassandra.Path;
/**
* Support for decomposing complex objects into paths to simple objects.
* Only the basic JSON structures are currently supported, i.e. Maps, Lists, Strings, Numbers, Booleans, and null.
*
* @author Eric Zoerner <a href="mailto:ezoerner@ebuddy.com">ezoerner@ebuddy.com</a>
*/
public class Decomposer {
private static final Decomposer INSTANCE = new Decomposer();
private Decomposer() { }
public static Decomposer get() {
return INSTANCE;
}
/**
* Decompose a map of arbitrarily complex structured objects into a map of
* simple objects keyed by paths.
*
* @param structures the input map of paths to objects
* @return a map of simple object keyed by paths
* @throws IllegalArgumentException if there is an object of unsupported type in structures
* or if structures is null
*/
public Map<Path,Object> decompose(Map<Path,Object> structures) {
if (structures == null) {
throw new IllegalArgumentException("structures is null");
}
Map<Path,Object> decomposed = new HashMap<Path,Object>(structures.size());
for (Map.Entry<Path,Object> entry : structures.entrySet()) {
Object structure = entry.getValue();
Path path = entry.getKey();
// handle null specially by replacing with a Null token
if (structure == null) {
decomposed.put(path, NULL);
continue;
}
if (Types.isSimple(structure)) {
decomposed.put(path, structure);
continue;
}
Map<Path,Object> decomposedMap = decomposeStructure(structure);
for (Map.Entry<Path,Object> decomposedEntry : decomposedMap.entrySet()) {
decomposed.put(path.concat(decomposedEntry.getKey()), decomposedEntry.getValue());
}
}
return decomposed;
}
//////// Private Methods //////////
@SuppressWarnings("ChainOfInstanceofChecks")
private Map<Path,Object> decomposeStructure(Object structure) {
Map<Path,Object> decomposedMap;
if (structure instanceof Map) {
decomposedMap = normalizeMap((Map<?,?>)structure);
} else if (structure instanceof List) {
decomposedMap = normalizeList((List<?>)structure);
} else {
throw new IllegalArgumentException("Unsupported data type: " + structure.getClass().getSimpleName());
}
return decompose(decomposedMap);
}
private Map<Path,Object> normalizeMap(Map<?,?> map) {
Map<Path,Object> normalized = new HashMap<Path,Object>(map.size());
for (Map.Entry<?,?> entry : map.entrySet()) {
Object key = entry.getKey();
if (!Types.isSimple(key)) {
throw new IllegalArgumentException(String.format("map key of type %s not supported",
key.getClass().getSimpleName()));
}
Path keyPath = key instanceof Path ? (Path)key : DefaultPath.fromStrings(key.toString());
Object value = entry.getValue();
normalized.put(keyPath, value);
}
return normalized;
}
private Map<Path,Object> normalizeList(List<?> list) {
// get type info for list
// TODO: if this is a set of simple types, then encode the set into the keys using #
// String type = (String)list.get(0);
/// get list itself
List<?> listItself = (List<?>)list.get(1);
Map<Path,Object> normalized = new HashMap<Path,Object>(listItself.size());
for (int i = 0; i < listItself.size(); i++) {
normalized.put(DefaultPath.fromIndex(i), listItself.get(i));
}
// add terminator column, issue #20
normalized.put(DefaultPath.fromIndex(listItself.size()), Types.LIST_TERMINATOR_VALUE);
return normalized;
}
}