/* * %CopyrightBegin% * * Copyright Ericsson AB 2000-2013. All Rights Reserved. * * 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. * * %CopyrightEnd% */ package com.ericsson.otp.erlang; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Provides a Java representation of Erlang maps. Maps are created from one or more * arbitrary Erlang terms. * * <p> * The arity of the map is the number of elements it contains. The keys and values can be * retrieved as arrays and the value for a key can be queried. * */ public class OtpErlangMap extends OtpErlangObject { // don't change this! private static final long serialVersionUID = -6410770117696198497L; private OtpMap map; private static class OtpMap extends LinkedHashMap<OtpErlangObject, OtpErlangObject> { private static final long serialVersionUID = -2666505810905455082L; public OtpMap() { super(); } } /** * Create an empty map. */ public OtpErlangMap() { map = new OtpMap(); } /** * Create a map from an array of keys and an array of values. * * @param keys * the array of terms to create the map keys from. * @param values * the array of terms to create the map values from. * * @exception java.lang.IllegalArgumentException * if any array is empty (null) or contains null elements. */ public OtpErlangMap(final OtpErlangObject[] keys, final OtpErlangObject[] values) { this(keys, 0, keys.length, values, 0, values.length); } /** * Create a map from an array of terms. * * @param keys * the array of terms to create the map from. * @param kstart * the offset of the first key to insert. * @param kcount * the number of keys to insert. * @param values * the array of values to create the map from. * @param vstart * the offset of the first value to insert. * @param vcount * the number of values to insert. * * @exception java.lang.IllegalArgumentException * if any array is empty (null) or contains null elements. * @exception java.lang.IllegalArgumentException * if kcount and vcount differ. */ public OtpErlangMap(final OtpErlangObject[] keys, final int kstart, final int kcount, final OtpErlangObject[] values, final int vstart, final int vcount) { if (keys == null || values == null) { throw new java.lang.IllegalArgumentException("Map content can't be null"); } else if (kcount != vcount) { throw new java.lang.IllegalArgumentException( "Map keys and values must have same arity"); } map = new OtpMap(); OtpErlangObject key, val; for (int i = 0; i < vcount; i++) { if ((key = keys[kstart + i]) == null) { throw new java.lang.IllegalArgumentException( "Map key cannot be null (element" + (kstart + i) + ")"); } if ((val = values[vstart + i]) == null) { throw new java.lang.IllegalArgumentException( "Map value cannot be null (element" + (vstart + i) + ")"); } put(key, val); } } /** * Create a map from a stream containing a map encoded in Erlang external format. * * @param buf * the stream containing the encoded map. * * @exception OtpErlangDecodeException * if the buffer does not contain a valid external representation of an * Erlang map. */ public OtpErlangMap(final OtpInputStream buf) throws OtpErlangDecodeException { final int arity = buf.read_map_head(); if (arity > 0) { map = new OtpMap(); for (int i = 0; i < arity; i++) { OtpErlangObject key, val; key = buf.read_any(); val = buf.read_any(); put(key, val); } } else { map = new OtpMap(); } } /** * Get the arity of the map. * * @return the number of elements contained in the map. */ public int arity() { return map.size(); } /** * Put value corresponding to key into the map. For detailed behavior description see * {@link Map#put(Object, Object)}. * * @param key * key to associate value with * @param value * value to associate with key * @return previous value associated with key or null */ public OtpErlangObject put(final OtpErlangObject key, final OtpErlangObject value) { return map.put(key, value); } /** * removes mapping for the key if present. * * @param key * key for which mapping is to be remove * @return value associated with key or null */ public OtpErlangObject remove(final OtpErlangObject key) { return map.remove(key); } /** * Get the specified value from the map. * * @param key * the key of the requested value. * * @return the requested value, of null if key is not a valid key. */ public OtpErlangObject get(final OtpErlangObject key) { return map.get(key); } /** * Get all the keys from the map as an array. * * @return an array containing all of the map's keys. */ public OtpErlangObject[] keys() { return map.keySet().toArray(new OtpErlangObject[arity()]); } /** * Get all the values from the map as an array. * * @return an array containing all of the map's values. */ public OtpErlangObject[] values() { return map.values().toArray(new OtpErlangObject[arity()]); } /** * make Set view of the map key-value pairs * * @return a set containing key-value pairs */ public Set<Entry<OtpErlangObject, OtpErlangObject>> entrySet() { return map.entrySet(); } /** * Get the string representation of the map. * * @return the string representation of the map. */ @Override public String toString() { final StringBuffer s = new StringBuffer(); s.append("#{"); boolean first = true; for (final Map.Entry<OtpErlangObject, OtpErlangObject> e : entrySet()) { if (first) { first = false; } else { s.append(","); } s.append(e.getKey().toString()); s.append(" => "); s.append(e.getValue().toString()); } s.append("}"); return s.toString(); } /** * Convert this map to the equivalent Erlang external representation. * * @param buf * an output stream to which the encoded map should be written. */ @Override public void encode(final OtpOutputStream buf) { final int arity = arity(); buf.write_map_head(arity); for (final Map.Entry<OtpErlangObject, OtpErlangObject> e : entrySet()) { buf.write_any(e.getKey()); buf.write_any(e.getValue()); } } /** * Determine if two maps are equal. Maps are equal if they have the same arity and all * of the elements are equal. * * @param o * the map to compare to. * * @return true if the maps have the same arity and all the elements are equal. */ @Override public boolean equals(final Object o) { if (!(o instanceof OtpErlangMap)) { return false; } final OtpErlangMap t = (OtpErlangMap) o; final int a = arity(); if (a != t.arity()) { return false; } if (a == 0) { return true; } OtpErlangObject key, val; for (final Map.Entry<OtpErlangObject, OtpErlangObject> e : entrySet()) { key = e.getKey(); val = e.getValue(); final OtpErlangObject v = t.get(key); if (v == null || !val.equals(v)) { return false; } } return true; } @Override public <T> boolean match(final OtpErlangObject term, final T binds) { if (!(term instanceof OtpErlangMap)) { return false; } final OtpErlangMap t = (OtpErlangMap) term; final int a = arity(); if (a > t.arity()) { return false; } if (a == 0) { return true; } OtpErlangObject key, val; for (final Map.Entry<OtpErlangObject, OtpErlangObject> e : entrySet()) { key = e.getKey(); val = e.getValue(); final OtpErlangObject v = t.get(key); if (v == null || !val.match(v, binds)) { return false; } } return true; } @Override public <T> OtpErlangObject bind(final T binds) throws OtpErlangException { final OtpErlangMap ret = new OtpErlangMap(); OtpErlangObject key, val; for (final Map.Entry<OtpErlangObject, OtpErlangObject> e : entrySet()) { key = e.getKey(); val = e.getValue(); ret.put(key, val.bind(binds)); } return ret; } @Override protected int doHashCode() { final OtpErlangObject.Hash hash = new OtpErlangObject.Hash(9); hash.combine(map.hashCode()); return hash.valueOf(); } @Override @SuppressWarnings("unchecked") public Object clone() { final OtpErlangMap newMap = (OtpErlangMap) super.clone(); newMap.map = (OtpMap) map.clone(); return newMap; } }