/*
 * 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 freemarker.template;

import java.io.Serializable;
import java.util.Map;
import java.util.SortedMap;

import freemarker.core._DelayedJQuote;
import freemarker.core._TemplateModelException;
import freemarker.ext.util.WrapperTemplateModel;
import freemarker.template.utility.ObjectWrapperWithAPISupport;

Adapts a Map to the corresponding TemplateModel interface(s), most importantly to TemplateHashModelEx. If you aren't wrapping an already existing Map, but build a hash specifically to be used from a template, also consider using SimpleHash (see comparison there).

Thread safety: A DefaultMapAdapter is as thread-safe as the Map that it wraps is. Normally you only have to consider read-only access, as the FreeMarker template language doesn't allow writing these hashes (though of course, Java methods called from the template can violate this rule).

This adapter is used by DefaultObjectWrapper if its useAdaptersForCollections property is true, which is the default when its incompatibleImprovements property is 2.3.22 or higher.

Since:2.3.22
/** * Adapts a {@link Map} to the corresponding {@link TemplateModel} interface(s), most importantly to * {@link TemplateHashModelEx}. If you aren't wrapping an already existing {@link Map}, but build a hash specifically to * be used from a template, also consider using {@link SimpleHash} (see comparison there). * * <p> * Thread safety: A {@link DefaultMapAdapter} is as thread-safe as the {@link Map} that it wraps is. Normally you only * have to consider read-only access, as the FreeMarker template language doesn't allow writing these hashes (though of * course, Java methods called from the template can violate this rule). * * <p> * This adapter is used by {@link DefaultObjectWrapper} if its {@code useAdaptersForCollections} property is * {@code true}, which is the default when its {@code incompatibleImprovements} property is 2.3.22 or higher. * * @since 2.3.22 */
public class DefaultMapAdapter extends WrappingTemplateModel implements TemplateHashModelEx2, AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport, Serializable { private final Map map;
Factory method for creating new adapter instances.
Params:
  • map – The map to adapt; can't be null.
  • wrapper – The ObjectWrapper used to wrap the items in the array.
/** * Factory method for creating new adapter instances. * * @param map * The map to adapt; can't be {@code null}. * @param wrapper * The {@link ObjectWrapper} used to wrap the items in the array. */
public static DefaultMapAdapter adapt(Map map, ObjectWrapperWithAPISupport wrapper) { return new DefaultMapAdapter(map, wrapper); } private DefaultMapAdapter(Map map, ObjectWrapper wrapper) { super(wrapper); this.map = map; } public TemplateModel get(String key) throws TemplateModelException { Object val; try { val = map.get(key); } catch (ClassCastException e) { throw new _TemplateModelException(e, "ClassCastException while getting Map entry with String key ", new _DelayedJQuote(key)); } catch (NullPointerException e) { throw new _TemplateModelException(e, "NullPointerException while getting Map entry with String key ", new _DelayedJQuote(key)); } if (val == null) { // Check for Character key if this is a single-character string. // In SortedMap-s, however, we can't do that safely, as it can cause ClassCastException. if (key.length() == 1 && !(map instanceof SortedMap)) { Character charKey = Character.valueOf(key.charAt(0)); try { val = map.get(charKey); if (val == null) { TemplateModel wrappedNull = wrap(null); if (wrappedNull == null || !(map.containsKey(key) || map.containsKey(charKey))) { return null; } else { return wrappedNull; } } } catch (ClassCastException e) { throw new _TemplateModelException(e, "Class casting exception while getting Map entry with Character key ", new _DelayedJQuote(charKey)); } catch (NullPointerException e) { throw new _TemplateModelException(e, "NullPointerException while getting Map entry with Character key ", new _DelayedJQuote(charKey)); } } else { // No char key fallback was possible TemplateModel wrappedNull = wrap(null); if (wrappedNull == null || !map.containsKey(key)) { return null; } else { return wrappedNull; } } } return wrap(val); } public boolean isEmpty() { return map.isEmpty(); } public int size() { return map.size(); } public TemplateCollectionModel keys() { return new SimpleCollection(map.keySet(), getObjectWrapper()); } public TemplateCollectionModel values() { return new SimpleCollection(map.values(), getObjectWrapper()); } public KeyValuePairIterator keyValuePairIterator() { return new MapKeyValuePairIterator(map, getObjectWrapper()); } public Object getAdaptedObject(Class hint) { return map; } public Object getWrappedObject() { return map; } public TemplateModel getAPI() throws TemplateModelException { return ((ObjectWrapperWithAPISupport) getObjectWrapper()).wrapAsAPI(map); } }