/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.brooklyn.util.core.xstream;

import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.core.ReferencingMarshallingContext;
import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;

/** equivalent to super, but cleaner methods, overridable, logging, and some retries */
public class MapConverter extends com.thoughtworks.xstream.converters.collections.MapConverter {

    private static final Logger log = LoggerFactory.getLogger(MapConverter.class);
    
    public MapConverter(Mapper mapper) {
        super(mapper);
    }

    @Override
    @SuppressWarnings({ "rawtypes" })
    public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
        Map map = (Map) source;
        try {
            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
                Map.Entry entry = (Map.Entry) iterator.next();
                marshalEntry(writer, context, entry);
            }
        } catch (ConcurrentModificationException e) {
            log.debug("Map "
                // seems there is no non-deprecated way to get the path...
                + (context instanceof ReferencingMarshallingContext ? "at "+((ReferencingMarshallingContext)context).currentPath() : "")
                + "["+source+"] modified while serializing; will fail, and retry may be attempted");
            throw e;
            // would be nice to attempt to re-serialize being slightly more defensive, as code below;
            // but the code above may have written partial data so that is dangerous, we could have corrupted output. 
            // if we could mark and restore in the output stream then we could do this below (but we don't have that in our stream),
            // or we could try this copying code in the first instance (but that's slow);
            // error is rare most of the time (e.g. attribute being updated) so we bail on this whole attempt
            // and simply try serializing the map-owner (e.g. an entity) again.
//            ImmutableList entries = ImmutableList.copyOf(map.entrySet());
//            for (Iterator iterator = entries.iterator(); iterator.hasNext();) {
//                Map.Entry entry = (Map.Entry) iterator.next();
//                marshalEntry(writer, context, entry);                
//            }
        }
    }

    protected String getEntryNodeName() { return mapper().serializedClass(Map.Entry.class); }
    
    protected void marshalEntry(HierarchicalStreamWriter writer, MarshallingContext context, Map.Entry entry) {
        ExtendedHierarchicalStreamWriterHelper.startNode(writer, getEntryNodeName(), Map.Entry.class);

        writeItem(entry.getKey(), context, writer);
        writeItem(entry.getValue(), context, writer);

        writer.endNode();
    }

    @Override
    protected void populateMap(HierarchicalStreamReader reader, UnmarshallingContext context, Map map) {
        while (reader.hasMoreChildren()) {
            reader.moveDown();
            unmarshalEntry(reader, context, map);
            reader.moveUp();
        }
    }

    protected void unmarshalEntry(HierarchicalStreamReader reader, UnmarshallingContext context, Map map) {
        reader.moveDown();
        Object key = readItem(reader, context, map);
        reader.moveUp();

        reader.moveDown();
        Object value = readItem(reader, context, map);
        reader.moveUp();

        map.put(key, value);
    }

}
