/* * 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.geode.cache.operations.internal; import java.io.Serializable; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.Map.Entry; import org.apache.geode.internal.cache.CachedDeserializable; import org.apache.geode.internal.cache.Token; /** * This map only allows updates. No creates or removes. It was adapted from UnmodifiableMap in the * jdk's Collections class. It was added to fix bug 51604. It also make sure that customers do not * see Token.INVALID and CachedDeserializable to fix bug 51625. */ public class UpdateOnlyMap implements Map, Serializable { private static final long serialVersionUID = -1034234728574286014L; private final Map m; public UpdateOnlyMap(Map m) { if (m == null) { throw new NullPointerException(); } this.m = m; } /** * Only called by internal code to bypass exportValue() method * * @return internal map */ public Map getInternalMap() { return this.m; } public int size() { return m.size(); } public boolean isEmpty() { return m.isEmpty(); } public boolean containsKey(Object key) { return m.containsKey(key); } public boolean containsValue(Object val) { return values().contains(val); } public Object get(Object key) { return exportValue(m.get(key)); } private static Object exportValue(Object v) { Object result; if (v == Token.INVALID) { result = null; } else if (v instanceof CachedDeserializable) { result = ((CachedDeserializable) v).getDeserializedForReading(); } else { result = v; } return result; } public Object put(Object key, Object value) { if (containsKey(key)) { return m.put(key, value); } else { throw new UnsupportedOperationException("can not add the key \"" + key + "\""); } } public void putAll(Map m) { if (m != null) { for (Object i : m.entrySet()) { Map.Entry me = (Map.Entry) i; put(me.getKey(), me.getValue()); } } } public Object remove(Object key) { throw new UnsupportedOperationException(); } public void clear() { throw new UnsupportedOperationException(); } private transient Set keySet = null; private transient Set entrySet = null; private transient Collection values = null; public Set keySet() { if (keySet == null) { keySet = Collections.unmodifiableSet(m.keySet()); } return keySet; } public Set entrySet() { if (entrySet == null) { entrySet = Collections.unmodifiableSet(new EntrySet()); } return entrySet; } private final class EntrySet extends AbstractSet { public Iterator iterator() { return new EntryIterator(); } @Override public int size() { return m.size(); } } private class EntryIterator implements Iterator { private Iterator mIterator = m.entrySet().iterator(); @Override public boolean hasNext() { return this.mIterator.hasNext(); } @Override public Object next() { Entry me = (Entry) this.mIterator.next(); return new ExportableEntry(me); } @Override public void remove() { throw new UnsupportedOperationException(); } } private static class ExportableEntry implements Map.Entry { private final Map.Entry e; ExportableEntry(Map.Entry e) { this.e = e; } public Object getKey() { return this.e.getKey(); } public Object getValue() { return exportValue(this.e.getValue()); } public Object setValue(Object value) { return exportValue(this.e.setValue(value)); } public int hashCode() { return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Map.Entry)) { return false; } Entry other = (Entry) o; return eq(getKey(), other.getKey()) && eq(getValue(), other.getValue()); } public String toString() { return getKey() + "=" + getValue(); } } private static boolean eq(Object o1, Object o2) { return o1 == null ? o2 == null : o1.equals(o2); } public Collection values() { if (values == null) { values = Collections.unmodifiableCollection(new Values()); } return values; } private final class Values extends AbstractCollection { @Override public Iterator iterator() { return new ValueIterator(); } @Override public int size() { return m.size(); } } private class ValueIterator implements Iterator { private Iterator mIterator = m.values().iterator(); @Override public boolean hasNext() { return this.mIterator.hasNext(); } @Override public Object next() { return exportValue(this.mIterator.next()); } @Override public void remove() { throw new UnsupportedOperationException(); } } /** * equals is over-ridden to make sure it is based on the objects we expose and not the internal * CachedDeserializables. */ @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof Map)) { return false; } Map m = (Map) o; if (m.size() != size()) { return false; } try { Iterator<Entry> i = entrySet().iterator(); while (i.hasNext()) { Entry e = i.next(); Object key = e.getKey(); Object value = e.getValue(); if (value == null) { if (!(m.get(key) == null && m.containsKey(key))) { return false; } } else { if (!value.equals(m.get(key))) { return false; } } } } catch (ClassCastException unused) { return false; } catch (NullPointerException unused) { return false; } return true; } /** * hashCode is over-ridden to make sure it is based on the objects we expose and not the internal * CachedDeserializables. */ @Override public int hashCode() { int h = 0; Iterator<Entry> i = entrySet().iterator(); while (i.hasNext()) { h += i.next().hashCode(); } return h; } @Override public String toString() { Iterator<Entry> i = entrySet().iterator(); if (!i.hasNext()) { return "{}"; } StringBuilder sb = new StringBuilder(); sb.append('{'); for (;;) { Entry e = i.next(); Object key = e.getKey(); Object value = e.getValue(); sb.append(key == this ? "(this Map)" : key); sb.append('='); sb.append(value == this ? "(this Map)" : value); if (!i.hasNext()) { return sb.append('}').toString(); } sb.append(',').append(' '); } } }