/******************************************************************************* * Copyright (c) 2015 Sierra Wireless and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.html. * * Contributors: * Sierra Wireless - initial API and implementation *******************************************************************************/ package org.eclipse.leshan.core.node; import java.util.AbstractMap; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Objects; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.util.Validate; /** * A resource which contains several resource instances. * * A resource instance is defined by a numeric identifier and a value. There are accessible via {@link #getValues()} */ public class LwM2mMultipleResource implements LwM2mResource { private final int id; private final Map<Integer, ?> values; private final Type type; protected LwM2mMultipleResource(int id, Map<Integer, ?> values, Type type) { this.id = id; this.values = Collections.unmodifiableMap(new HashMap<>(values)); this.type = type; } public static LwM2mMultipleResource newResource(int id, Map<Integer, ?> values, Type type) { switch (type) { case INTEGER: Validate.allElementsOfType(values.values(), Long.class); break; case FLOAT: Validate.allElementsOfType(values.values(), Double.class); break; case BOOLEAN: Validate.allElementsOfType(values.values(), Boolean.class); break; case OPAQUE: Validate.allElementsOfType(values.values(), byte[].class); break; case STRING: Validate.allElementsOfType(values.values(), String.class); break; case TIME: Validate.allElementsOfType(values.values(), Date.class); break; case OBJLNK: Validate.allElementsOfType(values.values(), ObjectLink.class); break; default: throw new IllegalArgumentException(String.format("Type %s is not supported", type.name())); } return new LwM2mMultipleResource(id, values, type); } public static LwM2mMultipleResource newStringResource(int id, Map<Integer, String> values) { Validate.noNullElements(values.values()); return new LwM2mMultipleResource(id, values, Type.STRING); } public static LwM2mMultipleResource newIntegerResource(int id, Map<Integer, Long> values) { Validate.noNullElements(values.values()); return new LwM2mMultipleResource(id, values, Type.INTEGER); } public static LwM2mMultipleResource newBooleanResource(int id, Map<Integer, Boolean> values) { Validate.noNullElements(values.values()); return new LwM2mMultipleResource(id, values, Type.BOOLEAN); } public static LwM2mMultipleResource newFloatResource(int id, Map<Integer, Double> values) { Validate.noNullElements(values.values()); return new LwM2mMultipleResource(id, values, Type.FLOAT); } public static LwM2mMultipleResource newDateResource(int id, Map<Integer, Date> values) { Validate.noNullElements(values.values()); return new LwM2mMultipleResource(id, values, Type.TIME); } public static LwM2mMultipleResource newObjectLinkResource(int id, Map<Integer, ObjectLink> values) { Validate.noNullElements(values.values()); return new LwM2mMultipleResource(id, values, Type.OBJLNK); } public static LwM2mMultipleResource newBinaryResource(int id, Map<Integer, byte[]> values) { Validate.noNullElements(values.values()); return new LwM2mMultipleResource(id, values, Type.OPAQUE); } /** * {@inheritDoc} */ @Override public int getId() { return id; } /** * {@inheritDoc} */ @Override public Type getType() { return type; } /** * @exception NoSuchElementException */ @Override public Object getValue() { throw new NoSuchElementException("There is no 'value' on multiple resources, use getValues() instead."); } /** * {@inheritDoc} */ @Override public Map<Integer, ?> getValues() { return values; } /** * {@inheritDoc} */ @Override public Object getValue(int id) { return values.get(id); } /** * {@inheritDoc} */ @Override public boolean isMultiInstances() { return true; } @Override public void accept(LwM2mNodeVisitor visitor) { visitor.visit(this); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; result = prime * result + ((type == null) ? 0 : type.hashCode()); result = prime * result + ((values == null) ? 0 : internalHashCode(values)); return result; } /** * This is a copy of {@link AbstractMap#hashCode()} with custom code to handle byte array equality */ private int internalHashCode(Map<?, ?> m) { int h = 0; Iterator<?> i = m.entrySet().iterator(); if (type == Type.OPAQUE) { // Custom hashcode to handle byte arrays while (i.hasNext()) { Entry<?, ?> e = (Entry<?, ?>) i.next(); h += Objects.hashCode(e.getKey()) ^ Arrays.hashCode((byte[]) e.getValue()); } } else { while (i.hasNext()) { Entry<?, ?> e = (Entry<?, ?>) i.next(); h += Objects.hashCode(e.getKey()) ^ Objects.hashCode(e.getValue()); } } return h; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; LwM2mMultipleResource other = (LwM2mMultipleResource) obj; if (id != other.id) return false; if (type != other.type) return false; if (values == null) { if (other.values != null) return false; // Custom equals to handle byte arrays } else if (!internalMapEquals(values, other.values)) return false; return true; } /** * This is a copy of {@link AbstractMap#equals(Object)} with custom code to handle byte array equality */ private boolean internalMapEquals(Map<?, ?> m1, Object o2) { if (o2 == this) return true; if (!(o2 instanceof Map)) return false; Map<?, ?> m2 = (Map<?, ?>) o2; if (m2.size() != m1.size()) return false; try { for (Object o : m1.entrySet()) { Entry<?, ?> e = (Entry<?, ?>) o; Object key = e.getKey(); Object value = e.getValue(); if (value == null) { if (!(m2.get(key) == null && m2.containsKey(key))) return false; } else { // Custom equals to handle byte arrays return type == Type.OPAQUE ? Arrays.equals((byte[]) value, (byte[]) m2.get(key)) : value.equals(m2 .get(key)); } } } catch (ClassCastException | NullPointerException unused) { return false; } return true; } @Override public String toString() { return String.format("LwM2mMultipleResource [id=%s, values=%s, type=%s]", id, values, type); } }