/** * 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.cxf.aegis.type.collection; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.NavigableMap; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; import javax.xml.namespace.QName; import org.apache.cxf.aegis.Context; import org.apache.cxf.aegis.DatabindingException; import org.apache.cxf.aegis.type.AegisType; import org.apache.cxf.aegis.type.TypeUtil; import org.apache.cxf.aegis.xml.MessageReader; import org.apache.cxf.aegis.xml.MessageWriter; import org.apache.ws.commons.schema.XmlSchema; import org.apache.ws.commons.schema.XmlSchemaComplexType; import org.apache.ws.commons.schema.XmlSchemaElement; import org.apache.ws.commons.schema.XmlSchemaSequence; public class MapType extends AegisType { private AegisType keyType; private AegisType valueType; private QName keyName; private QName valueName; private QName entryName; public MapType(QName schemaType, AegisType keyType, AegisType valueType) { super(); this.keyType = keyType; this.valueType = valueType; setSchemaType(schemaType); keyName = new QName(schemaType.getNamespaceURI(), "key"); valueName = new QName(schemaType.getNamespaceURI(), "value"); entryName = new QName(schemaType.getNamespaceURI(), "entry"); } @Override public Object readObject(MessageReader reader, Context context) throws DatabindingException { Map<Object, Object> map = instantiateMap(); try { while (reader.hasMoreElementReaders()) { MessageReader entryReader = reader.getNextElementReader(); if (entryReader.getName().equals(getEntryName())) { Object key = null; Object value = null; while (entryReader.hasMoreElementReaders()) { MessageReader evReader = entryReader.getNextElementReader(); if (evReader.getName().equals(getKeyName())) { AegisType kType = TypeUtil.getReadType(evReader.getXMLStreamReader(), context.getGlobalContext(), getKeyType()); key = kType.readObject(evReader, context); } else if (evReader.getName().equals(getValueName())) { AegisType vType = TypeUtil.getReadType(evReader.getXMLStreamReader(), context.getGlobalContext(), getValueType()); value = vType.readObject(evReader, context); } else { readToEnd(evReader); } } map.put(key, value); } else { readToEnd(entryReader); } } return map; } catch (IllegalArgumentException e) { throw new DatabindingException("Illegal argument.", e); } } private void readToEnd(MessageReader childReader) { while (childReader.hasMoreElementReaders()) { readToEnd(childReader.getNextElementReader()); } } /** * Creates a map instance. If the type class is a <code>Map</code> or * extends the <code>Map</code> interface a <code>HashMap</code> is * created. Otherwise the map classs (i.e. LinkedHashMap) is instantiated * using the default constructor. * * @return */ @SuppressWarnings("unchecked") protected Map<Object, Object> instantiateMap() { Map<Object, Object> map = null; Class<?> cls = getTypeClass(); if (cls.equals(Map.class)) { map = new HashMap<>(); } else if (cls.equals(Hashtable.class)) { map = new Hashtable<Object, Object>(); } else if (cls.equals(ConcurrentMap.class)) { map = new ConcurrentHashMap<Object, Object>(); } else if (cls.equals(ConcurrentNavigableMap.class)) { map = new ConcurrentSkipListMap<Object, Object>(); } else if (cls.equals(SortedMap.class) || cls.equals(NavigableMap.class)) { map = new TreeMap<Object, Object>(); } else if (cls.isInterface()) { map = new HashMap<>(); } else { try { map = (Map<Object, Object>)cls.newInstance(); } catch (Exception e) { throw new DatabindingException("Could not create map implementation: " + getTypeClass().getName(), e); } } return map; } @Override public void writeObject(Object object, MessageWriter writer, Context context) throws DatabindingException { if (object == null) { return; } try { Map<?, ?> map = (Map<?, ?>)object; AegisType kType = getKeyType(); AegisType vType = getValueType(); for (Iterator<?> itr = map.entrySet().iterator(); itr.hasNext();) { Map.Entry<?, ?> entry = (Map.Entry<?, ?>)itr.next(); writeEntry(writer, context, kType, vType, entry); } } catch (IllegalArgumentException e) { throw new DatabindingException("Illegal argument.", e); } } private void writeEntry(MessageWriter writer, Context context, AegisType kType, AegisType vType, Map.Entry<?, ?> entry) throws DatabindingException { kType = TypeUtil.getWriteType(context.getGlobalContext(), entry.getKey(), kType); vType = TypeUtil.getWriteType(context.getGlobalContext(), entry.getValue(), vType); MessageWriter entryWriter = writer.getElementWriter(getEntryName()); MessageWriter keyWriter = entryWriter.getElementWriter(getKeyName()); kType.writeObject(entry.getKey(), keyWriter, context); keyWriter.close(); if (entry.getValue() != null) { MessageWriter valueWriter = entryWriter.getElementWriter(getValueName()); vType.writeObject(entry.getValue(), valueWriter, context); valueWriter.close(); } entryWriter.close(); } @Override public void writeSchema(XmlSchema root) { XmlSchemaComplexType complex = new XmlSchemaComplexType(root, true); complex.setName(getSchemaType().getLocalPart()); XmlSchemaSequence sequence = new XmlSchemaSequence(); complex.setParticle(sequence); AegisType kType = getKeyType(); AegisType vType = getValueType(); XmlSchemaElement element = new XmlSchemaElement(root, false); sequence.getItems().add(element); element.setName(getEntryName().getLocalPart()); element.setMinOccurs(0); element.setMaxOccurs(Long.MAX_VALUE); XmlSchemaComplexType evType = new XmlSchemaComplexType(root, false); element.setType(evType); XmlSchemaSequence evSequence = new XmlSchemaSequence(); evType.setParticle(evSequence); createElement(root, evSequence, getKeyName(), kType, false); createElement(root, evSequence, getValueName(), vType, true); } /** * Creates a element in a sequence for the key type and the value type. */ private void createElement(XmlSchema schema, XmlSchemaSequence seq, QName name, AegisType type, boolean optional) { XmlSchemaElement element = new XmlSchemaElement(schema, false); seq.getItems().add(element); element.setName(name.getLocalPart()); element.setSchemaTypeName(type.getSchemaType()); if (optional) { element.setMinOccurs(0); } else { element.setMinOccurs(1); } element.setMaxOccurs(1); } @Override public Set<AegisType> getDependencies() { Set<AegisType> deps = new HashSet<>(); deps.add(getKeyType()); deps.add(getValueType()); return deps; } public AegisType getKeyType() { return keyType; } public AegisType getValueType() { return valueType; } @Override public boolean isComplex() { return true; } public QName getKeyName() { return keyName; } public void setKeyName(QName keyName) { this.keyName = keyName; } public QName getValueName() { return valueName; } public void setValueName(QName valueName) { this.valueName = valueName; } public QName getEntryName() { return entryName; } public void setEntryName(QName entryName) { this.entryName = entryName; } }