/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2000-2013. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
package com.ericsson.otp.erlang;
import java.io.Serializable;
/**
* 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 implements Serializable,
Cloneable {
// don't change this!
private static final long serialVersionUID = -6410770117696198497L;
private static final OtpErlangObject[] NO_ELEMENTS = new OtpErlangObject[0];
private OtpErlangObject[] keys = NO_ELEMENTS;
private OtpErlangObject[] values = NO_ELEMENTS;
/**
* 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");
} else if (vcount < 1) {
this.keys = NO_ELEMENTS;
this.values = NO_ELEMENTS;
} else {
this.keys = new OtpErlangObject[vcount];
for (int i = 0; i < vcount; i++) {
if (keys[kstart + i] != null) {
this.keys[i] = keys[kstart + i];
} else {
throw new java.lang.IllegalArgumentException(
"Map key cannot be null (element" + (kstart + i)
+ ")");
}
}
this.values = new OtpErlangObject[vcount];
for (int i = 0; i < vcount; i++) {
if (values[vstart + i] != null) {
this.values[i] = values[vstart + i];
} else {
throw new java.lang.IllegalArgumentException(
"Map value cannot be null (element" + (vstart + i)
+ ")");
}
}
}
}
/**
* 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) {
keys = new OtpErlangObject[arity];
values = new OtpErlangObject[arity];
for (int i = 0; i < arity; i++) {
keys[i] = buf.read_any();
values[i] = buf.read_any();
}
} else {
keys = NO_ELEMENTS;
values = NO_ELEMENTS;
}
}
/**
* Get the arity of the map.
*
* @return the number of elements contained in the map.
*/
public int arity() {
return keys.length;
}
/**
* 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) {
if (key == null) {
return null;
}
for (int i = 0; i < keys.length; i++) {
if (key.equals(keys[i])) {
return values[i];
}
}
return null;
}
/**
* Get all the keys from the map as an array.
*
* @return an array containing all of the map's keys.
*/
public OtpErlangObject[] keys() {
final OtpErlangObject[] res = new OtpErlangObject[arity()];
System.arraycopy(keys, 0, res, 0, res.length);
return res;
}
/**
* Get all the values from the map as an array.
*
* @return an array containing all of the map's values.
*/
public OtpErlangObject[] values() {
final OtpErlangObject[] res = new OtpErlangObject[arity()];
System.arraycopy(values, 0, res, 0, res.length);
return res;
}
/**
* Get the string representation of the map.
*
* @return the string representation of the map.
*/
@Override
public String toString() {
int i;
final StringBuffer s = new StringBuffer();
final int arity = values.length;
s.append("#{");
for (i = 0; i < arity; i++) {
if (i > 0) {
s.append(",");
}
s.append(keys[i].toString());
s.append(" => ");
s.append(values[i].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 = values.length;
buf.write_map_head(arity);
for (int i = 0; i < arity; i++) {
buf.write_any(keys[i]);
buf.write_any(values[i]);
}
}
/**
* 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;
}
for (int i = 0; i < a; i++) {
if (!keys[i].equals(t.keys[i])) {
return false; // early exit
}
}
for (int i = 0; i < a; i++) {
if (!values[i].equals(t.values[i])) {
return false; // early exit
}
}
return true;
}
@Override
protected int doHashCode() {
final OtpErlangObject.Hash hash = new OtpErlangObject.Hash(9);
final int a = arity();
hash.combine(a);
for (int i = 0; i < a; i++) {
hash.combine(keys[i].hashCode());
}
for (int i = 0; i < a; i++) {
hash.combine(values[i].hashCode());
}
return hash.valueOf();
}
@Override
public Object clone() {
final OtpErlangMap newMap = (OtpErlangMap) super.clone();
newMap.values = values.clone();
return newMap;
}
}