/* * 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; } }