mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 01:51:47 +00:00
chore: replace calls to Collections
with backport impls instead when Android SDK < 26 (Android 8.0)
This commit is contained in:
parent
3d54da75cc
commit
aaa750bbc6
@ -9,6 +9,7 @@ import com.github.jk1.license.render.TextReportRenderer
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes.CHECKCAST
|
||||
import org.objectweb.asm.Opcodes.INVOKESTATIC
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
@ -139,6 +140,9 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix PosixFilePermission class type check issue.
|
||||
*/
|
||||
abstract class FixPosixFilePermissionClassVisitorFactory :
|
||||
AsmClassVisitorFactory<FixPosixFilePermissionClassVisitorFactory.Params> {
|
||||
|
||||
@ -155,7 +159,10 @@ abstract class FixPosixFilePermissionClassVisitorFactory :
|
||||
exceptions: Array<out String>?
|
||||
): MethodVisitor {
|
||||
if (name == "attributesToPermissions") { // org.apache.sshd.sftp.common.SftpHelper.attributesToPermissions
|
||||
return object : MethodVisitor(instrumentationContext.apiVersion.get(), super.visitMethod(access, name, descriptor, signature, exceptions)) {
|
||||
return object : MethodVisitor(
|
||||
instrumentationContext.apiVersion.get(),
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions)
|
||||
) {
|
||||
override fun visitTypeInsn(opcode: Int, type: String?) {
|
||||
// We need to prevent Android Desugar modifying the `PosixFilePermission` classname.
|
||||
//
|
||||
@ -187,12 +194,74 @@ abstract class FixPosixFilePermissionClassVisitorFactory :
|
||||
interface Params : InstrumentationParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* Collections.unmodifiableXXX is not exist when Android API level is lower than 26.
|
||||
* So we replace the call to Collections.unmodifiableXXX with the original collection by removing the call.
|
||||
*/
|
||||
abstract class FixCollectionsClassVisitorFactory :
|
||||
AsmClassVisitorFactory<FixCollectionsClassVisitorFactory.Params> {
|
||||
override fun createClassVisitor(
|
||||
classContext: ClassContext,
|
||||
nextClassVisitor: ClassVisitor
|
||||
): ClassVisitor {
|
||||
return object : ClassVisitor(instrumentationContext.apiVersion.get(), nextClassVisitor) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String?,
|
||||
descriptor: String?,
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?
|
||||
): MethodVisitor {
|
||||
return object : MethodVisitor(
|
||||
instrumentationContext.apiVersion.get(),
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions)
|
||||
) {
|
||||
override fun visitMethodInsn(
|
||||
opcode: Int,
|
||||
type: String?,
|
||||
name: String?,
|
||||
descriptor: String?,
|
||||
isInterface: Boolean
|
||||
) {
|
||||
val backportClass = "org/kde/kdeconnect/Helpers/CollectionsBackport"
|
||||
|
||||
if (opcode == INVOKESTATIC && type == "java/util/Collections") {
|
||||
val replaceRules = mapOf(
|
||||
"unmodifiableNavigableSet" to "(Ljava/util/NavigableSet;)Ljava/util/NavigableSet;",
|
||||
"unmodifiableSet" to "(Ljava/util/Set;)Ljava/util/Set;",
|
||||
"unmodifiableNavigableMap" to "(Ljava/util/NavigableMap;)Ljava/util/NavigableMap;",
|
||||
"emptyNavigableMap" to "()Ljava/util/NavigableMap;")
|
||||
if (name in replaceRules && descriptor == replaceRules[name]) {
|
||||
super.visitMethodInsn(opcode, backportClass, name, descriptor, isInterface)
|
||||
val calleeClass = classContext.currentClassData.className
|
||||
println("Replace Collections.$name call with CollectionsBackport.$name from $calleeClass success.")
|
||||
return
|
||||
}
|
||||
}
|
||||
super.visitMethodInsn(opcode, type, name, descriptor, isInterface)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInstrumentable(classData: ClassData): Boolean {
|
||||
return classData.className.startsWith("org.apache.sshd") // We only need to fix the Apache SSHD library
|
||||
}
|
||||
|
||||
interface Params : InstrumentationParameters
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
onVariants { variant ->
|
||||
variant.instrumentation.transformClassesWith(
|
||||
FixPosixFilePermissionClassVisitorFactory::class.java,
|
||||
InstrumentationScope.ALL
|
||||
) { }
|
||||
variant.instrumentation.transformClassesWith(
|
||||
FixCollectionsClassVisitorFactory::class.java,
|
||||
InstrumentationScope.ALL
|
||||
) { }
|
||||
}
|
||||
}
|
||||
|
||||
|
869
src/org/kde/kdeconnect/Helpers/CollectionsBackport.java
Normal file
869
src/org/kde/kdeconnect/Helpers/CollectionsBackport.java
Normal file
@ -0,0 +1,869 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2014 The Android Open Source Project
|
||||
* SPDX-FileCopyrightText: 1997, 2021, Oracle and/or its affiliates. All rights reserved
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 ShellWen Chen <me@shellwen.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.SortedSet;
|
||||
import java.util.Spliterator;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/** @noinspection unused*/
|
||||
public final class CollectionsBackport {
|
||||
public static <T> NavigableSet<T> unmodifiableNavigableSet(NavigableSet<T> s) {
|
||||
if (VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return Collections.unmodifiableNavigableSet(s);
|
||||
} else {
|
||||
return new UnmodifiableNavigableSetBackport<T>(s);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Set<T> unmodifiableSet(Set<T> s) {
|
||||
if (VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return Collections.unmodifiableSet(s);
|
||||
} else {
|
||||
return new UnmodifiableSetBackport(s);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Collection<T> unmodifiableCollection(Collection<T> c) {
|
||||
if (VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return Collections.unmodifiableCollection(c);
|
||||
} else {
|
||||
return new UnmodifiableCollectionBackport(c);
|
||||
}
|
||||
}
|
||||
|
||||
public static <K, V> NavigableMap<K, V> unmodifiableNavigableMap(NavigableMap<K, V> m) {
|
||||
if (VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return Collections.unmodifiableNavigableMap(m);
|
||||
} else {
|
||||
return new UnmodifiableNavigableMapBackport<>(m);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map unmodifiableMap(Map m) {
|
||||
if (VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return Collections.unmodifiableMap(m);
|
||||
} else {
|
||||
return new UnmodifiableMapBackport(m);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> NavigableSet<T> emptyNavigableSet() {
|
||||
return (NavigableSet<T>) UnmodifiableNavigableSetBackport.EMPTY_NAVIGABLE_SET;
|
||||
}
|
||||
|
||||
public static <K, V> NavigableMap<K, V> emptyNavigableMap() {
|
||||
return (NavigableMap<K, V>) UnmodifiableNavigableMapBackport.EMPTY_NAVIGABLE_MAP;
|
||||
}
|
||||
|
||||
static boolean eq(Object o1, Object o2) {
|
||||
return o1 == null ? o2 == null : o1.equals(o2);
|
||||
}
|
||||
|
||||
static class UnmodifiableNavigableSetBackport<E>
|
||||
extends UnmodifiableSortedSetBackport<E>
|
||||
implements NavigableSet<E>, Serializable {
|
||||
|
||||
/**
|
||||
* A singleton empty unmodifiable navigable set used for
|
||||
* {@link #emptyNavigableSet()}.
|
||||
*
|
||||
* @param <E> type of elements, if there were any, and bounds
|
||||
*/
|
||||
private static class EmptyNavigableSet<E> extends UnmodifiableNavigableSetBackport<E>
|
||||
implements Serializable {
|
||||
public EmptyNavigableSet() {
|
||||
super(new TreeSet<>());
|
||||
}
|
||||
|
||||
@java.io.Serial
|
||||
private Object readResolve() {
|
||||
return EMPTY_NAVIGABLE_SET;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final NavigableSet<?> EMPTY_NAVIGABLE_SET =
|
||||
new EmptyNavigableSet<>();
|
||||
|
||||
/**
|
||||
* The instance we are protecting.
|
||||
*/
|
||||
@SuppressWarnings("serial") // Conditionally serializable
|
||||
private final NavigableSet<E> ns;
|
||||
|
||||
UnmodifiableNavigableSetBackport(NavigableSet<E> s) {
|
||||
super(s);
|
||||
ns = s;
|
||||
}
|
||||
|
||||
public E lower(E e) {
|
||||
return ns.lower(e);
|
||||
}
|
||||
|
||||
public E floor(E e) {
|
||||
return ns.floor(e);
|
||||
}
|
||||
|
||||
public E ceiling(E e) {
|
||||
return ns.ceiling(e);
|
||||
}
|
||||
|
||||
public E higher(E e) {
|
||||
return ns.higher(e);
|
||||
}
|
||||
|
||||
public E pollFirst() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public E pollLast() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public NavigableSet<E> descendingSet() {
|
||||
return new UnmodifiableNavigableSetBackport<>(ns.descendingSet());
|
||||
}
|
||||
|
||||
public Iterator<E> descendingIterator() {
|
||||
return descendingSet().iterator();
|
||||
}
|
||||
|
||||
public NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) {
|
||||
return new UnmodifiableNavigableSetBackport<>(
|
||||
ns.subSet(fromElement, fromInclusive, toElement, toInclusive));
|
||||
}
|
||||
|
||||
public NavigableSet<E> headSet(E toElement, boolean inclusive) {
|
||||
return new UnmodifiableNavigableSetBackport<>(
|
||||
ns.headSet(toElement, inclusive));
|
||||
}
|
||||
|
||||
public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
|
||||
return new UnmodifiableNavigableSetBackport<>(
|
||||
ns.tailSet(fromElement, inclusive));
|
||||
}
|
||||
}
|
||||
|
||||
static class UnmodifiableSortedSetBackport<E>
|
||||
extends UnmodifiableSetBackport<E>
|
||||
implements SortedSet<E>, Serializable {
|
||||
@SuppressWarnings("serial") // Conditionally serializable
|
||||
private final SortedSet<E> ss;
|
||||
|
||||
UnmodifiableSortedSetBackport(SortedSet<E> s) {
|
||||
super(s);
|
||||
ss = s;
|
||||
}
|
||||
|
||||
public Comparator<? super E> comparator() {
|
||||
return ss.comparator();
|
||||
}
|
||||
|
||||
public SortedSet<E> subSet(E fromElement, E toElement) {
|
||||
return new UnmodifiableSortedSetBackport<>(ss.subSet(fromElement, toElement));
|
||||
}
|
||||
|
||||
public SortedSet<E> headSet(E toElement) {
|
||||
return new UnmodifiableSortedSetBackport<>(ss.headSet(toElement));
|
||||
}
|
||||
|
||||
public SortedSet<E> tailSet(E fromElement) {
|
||||
return new UnmodifiableSortedSetBackport<>(ss.tailSet(fromElement));
|
||||
}
|
||||
|
||||
public E first() {
|
||||
return ss.first();
|
||||
}
|
||||
|
||||
public E last() {
|
||||
return ss.last();
|
||||
}
|
||||
}
|
||||
|
||||
static class UnmodifiableSetBackport<E> extends UnmodifiableCollectionBackport<E>
|
||||
implements Set<E>, Serializable {
|
||||
|
||||
UnmodifiableSetBackport(Set<? extends E> s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
return o == this || c.equals(o);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return c.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
static class UnmodifiableCollectionBackport<E> implements Collection<E>, Serializable {
|
||||
|
||||
@SuppressWarnings("serial") // Conditionally serializable
|
||||
final Collection<? extends E> c;
|
||||
|
||||
UnmodifiableCollectionBackport(Collection<? extends E> c) {
|
||||
if (c == null)
|
||||
throw new NullPointerException();
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return c.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return c.isEmpty();
|
||||
}
|
||||
|
||||
public boolean contains(Object o) {
|
||||
return c.contains(o);
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
return c.toArray();
|
||||
}
|
||||
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return c.toArray(a);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
||||
public <T> T[] toArray(IntFunction<T[]> f) {
|
||||
return c.toArray(f);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return c.toString();
|
||||
}
|
||||
|
||||
public Iterator<E> iterator() {
|
||||
return new Iterator<E>() {
|
||||
private final Iterator<? extends E> i = c.iterator();
|
||||
|
||||
public boolean hasNext() {
|
||||
return i.hasNext();
|
||||
}
|
||||
|
||||
public E next() {
|
||||
return i.next();
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachRemaining(Consumer<? super E> action) {
|
||||
// Use backing collection version
|
||||
i.forEachRemaining(action);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean add(E e) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean remove(Object o) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean containsAll(Collection<?> coll) {
|
||||
return c.containsAll(coll);
|
||||
}
|
||||
|
||||
public boolean addAll(Collection<? extends E> coll) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean removeAll(Collection<?> coll) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean retainAll(Collection<?> coll) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
// Override default methods in Collection
|
||||
@Override
|
||||
public void forEach(Consumer<? super E> action) {
|
||||
c.forEach(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super E> filter) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Spliterator<E> spliterator() {
|
||||
return (Spliterator<E>) c.spliterator();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<E> stream() {
|
||||
return (Stream<E>) c.stream();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<E> parallelStream() {
|
||||
return (Stream<E>) c.parallelStream();
|
||||
}
|
||||
}
|
||||
|
||||
static class UnmodifiableNavigableMapBackport<K, V> extends UnmodifiableSortedMapBackport<K, V> implements NavigableMap<K, V>, Serializable {
|
||||
private static final long serialVersionUID = -4858195264774772197L;
|
||||
private static final EmptyNavigableMapBackport<?, ?> EMPTY_NAVIGABLE_MAP = new EmptyNavigableMapBackport();
|
||||
private final NavigableMap<K, ? extends V> nm;
|
||||
|
||||
UnmodifiableNavigableMapBackport(NavigableMap<K, ? extends V> m) {
|
||||
super(m);
|
||||
this.nm = m;
|
||||
}
|
||||
|
||||
public K lowerKey(K key) {
|
||||
return this.nm.lowerKey(key);
|
||||
}
|
||||
|
||||
public K floorKey(K key) {
|
||||
return this.nm.floorKey(key);
|
||||
}
|
||||
|
||||
public K ceilingKey(K key) {
|
||||
return this.nm.ceilingKey(key);
|
||||
}
|
||||
|
||||
public K higherKey(K key) {
|
||||
return this.nm.higherKey(key);
|
||||
}
|
||||
|
||||
public Map.Entry<K, V> lowerEntry(K key) {
|
||||
Map.Entry<K, V> lower = (Entry<K, V>) this.nm.lowerEntry(key);
|
||||
return null != lower ? new UnmodifiableMapBackport.UnmodifiableEntrySetBackport.UnmodifiableEntry(lower) : null;
|
||||
}
|
||||
|
||||
public Map.Entry<K, V> floorEntry(K key) {
|
||||
Map.Entry<K, V> floor = (Entry<K, V>) this.nm.floorEntry(key);
|
||||
return null != floor ? new UnmodifiableMapBackport.UnmodifiableEntrySetBackport.UnmodifiableEntry(floor) : null;
|
||||
}
|
||||
|
||||
public Map.Entry<K, V> ceilingEntry(K key) {
|
||||
Map.Entry<K, V> ceiling = (Entry<K, V>) this.nm.ceilingEntry(key);
|
||||
return null != ceiling ? new UnmodifiableMapBackport.UnmodifiableEntrySetBackport.UnmodifiableEntry(ceiling) : null;
|
||||
}
|
||||
|
||||
public Map.Entry<K, V> higherEntry(K key) {
|
||||
Map.Entry<K, V> higher = (Entry<K, V>) this.nm.higherEntry(key);
|
||||
return null != higher ? new UnmodifiableMapBackport.UnmodifiableEntrySetBackport.UnmodifiableEntry(higher) : null;
|
||||
}
|
||||
|
||||
public Map.Entry<K, V> firstEntry() {
|
||||
Map.Entry<K, V> first = (Entry<K, V>) this.nm.firstEntry();
|
||||
return null != first ? new UnmodifiableMapBackport.UnmodifiableEntrySetBackport.UnmodifiableEntry(first) : null;
|
||||
}
|
||||
|
||||
public Map.Entry<K, V> lastEntry() {
|
||||
Map.Entry<K, V> last = (Entry<K, V>) this.nm.lastEntry();
|
||||
return null != last ? new UnmodifiableMapBackport.UnmodifiableEntrySetBackport.UnmodifiableEntry(last) : null;
|
||||
}
|
||||
|
||||
public Map.Entry<K, V> pollFirstEntry() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Map.Entry<K, V> pollLastEntry() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public NavigableMap<K, V> descendingMap() {
|
||||
return (NavigableMap<K, V>) CollectionsBackport.unmodifiableNavigableMap(this.nm.descendingMap());
|
||||
}
|
||||
|
||||
public NavigableSet<K> navigableKeySet() {
|
||||
return CollectionsBackport.unmodifiableNavigableSet(this.nm.navigableKeySet());
|
||||
}
|
||||
|
||||
public NavigableSet<K> descendingKeySet() {
|
||||
return CollectionsBackport.unmodifiableNavigableSet(this.nm.descendingKeySet());
|
||||
}
|
||||
|
||||
public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
|
||||
return (NavigableMap<K, V>) CollectionsBackport.unmodifiableNavigableMap(this.nm.subMap(fromKey, fromInclusive, toKey, toInclusive));
|
||||
}
|
||||
|
||||
public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
|
||||
return (NavigableMap<K, V>) CollectionsBackport.unmodifiableNavigableMap(this.nm.headMap(toKey, inclusive));
|
||||
}
|
||||
|
||||
public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
|
||||
return (NavigableMap<K, V>) CollectionsBackport.unmodifiableNavigableMap(this.nm.tailMap(fromKey, inclusive));
|
||||
}
|
||||
|
||||
private static class EmptyNavigableMapBackport<K, V> extends UnmodifiableNavigableMapBackport<K, V> implements Serializable {
|
||||
private static final long serialVersionUID = -2239321462712562324L;
|
||||
|
||||
EmptyNavigableMapBackport() {
|
||||
super(new TreeMap());
|
||||
}
|
||||
|
||||
public NavigableSet<K> navigableKeySet() {
|
||||
return CollectionsBackport.emptyNavigableSet();
|
||||
}
|
||||
|
||||
private Object readResolve() {
|
||||
return UnmodifiableNavigableMapBackport.EMPTY_NAVIGABLE_MAP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class UnmodifiableSortedMapBackport<K,V>
|
||||
extends UnmodifiableMapBackport<K,V>
|
||||
implements SortedMap<K,V>, Serializable {
|
||||
@SuppressWarnings("serial") // Conditionally serializable
|
||||
private final SortedMap<K, ? extends V> sm;
|
||||
|
||||
UnmodifiableSortedMapBackport(SortedMap<K, ? extends V> m) {super(m); sm = m; }
|
||||
public Comparator<? super K> comparator() { return sm.comparator(); }
|
||||
public SortedMap<K,V> subMap(K fromKey, K toKey)
|
||||
{ return new UnmodifiableSortedMapBackport<>(sm.subMap(fromKey, toKey)); }
|
||||
public SortedMap<K,V> headMap(K toKey)
|
||||
{ return new UnmodifiableSortedMapBackport<>(sm.headMap(toKey)); }
|
||||
public SortedMap<K,V> tailMap(K fromKey)
|
||||
{ return new UnmodifiableSortedMapBackport<>(sm.tailMap(fromKey)); }
|
||||
public K firstKey() { return sm.firstKey(); }
|
||||
public K lastKey() { return sm.lastKey(); }
|
||||
}
|
||||
|
||||
private static class UnmodifiableMapBackport<K, V> implements Map<K, V>, Serializable {
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = -1034234728574286014L;
|
||||
|
||||
@SuppressWarnings("serial") // Conditionally serializable
|
||||
private final Map<? extends K, ? extends V> m;
|
||||
|
||||
UnmodifiableMapBackport(Map<? extends K, ? extends V> m) {
|
||||
if (m == null)
|
||||
throw new NullPointerException();
|
||||
this.m = 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 m.containsValue(val);
|
||||
}
|
||||
|
||||
public V get(Object key) {
|
||||
return m.get(key);
|
||||
}
|
||||
|
||||
public V put(K key, V value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public V remove(Object key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private transient Set<K> keySet;
|
||||
private transient Set<Map.Entry<K, V>> entrySet;
|
||||
private transient Collection<V> values;
|
||||
|
||||
public Set<K> keySet() {
|
||||
if (keySet == null)
|
||||
keySet = (Set<K>) unmodifiableSet(m.keySet());
|
||||
return keySet;
|
||||
}
|
||||
|
||||
public Set<Map.Entry<K, V>> entrySet() {
|
||||
if (entrySet == null)
|
||||
entrySet = new UnmodifiableEntrySetBackport<>(m.entrySet());
|
||||
return entrySet;
|
||||
}
|
||||
|
||||
public Collection<V> values() {
|
||||
if (values == null)
|
||||
values = (Collection<V>) unmodifiableCollection(m.values());
|
||||
return values;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
return o == this || m.equals(o);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return m.hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return m.toString();
|
||||
}
|
||||
|
||||
// Override default methods in Map
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public V getOrDefault(Object k, V defaultValue) {
|
||||
// Safe cast as we don't change the value
|
||||
return ((Map<K, V>) m).getOrDefault(k, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super K, ? super V> action) {
|
||||
m.forEach(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V putIfAbsent(K key, V value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(K key, V oldValue, V newValue) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V replace(K key, V value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V computeIfPresent(K key,
|
||||
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V compute(K key,
|
||||
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V merge(K key, V value,
|
||||
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* We need this class in addition to UnmodifiableSet as
|
||||
* Map.Entries themselves permit modification of the backing Map
|
||||
* via their setValue operation. This class is subtle: there are
|
||||
* many possible attacks that must be thwarted.
|
||||
*
|
||||
* @serial include
|
||||
*/
|
||||
static class UnmodifiableEntrySetBackport<K, V>
|
||||
extends UnmodifiableSetBackport<Map.Entry<K, V>> {
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = 7854390611657943733L;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
UnmodifiableEntrySetBackport(Set<? extends Map.Entry<? extends K, ? extends V>> s) {
|
||||
// Need to cast to raw in order to work around a limitation in the type system
|
||||
super((Set) s);
|
||||
}
|
||||
|
||||
static <K, V> Consumer<Map.Entry<? extends K, ? extends V>> entryConsumer(
|
||||
Consumer<? super Entry<K, V>> action) {
|
||||
return e -> action.accept(new UnmodifiableEntry<>(e));
|
||||
}
|
||||
|
||||
public void forEach(Consumer<? super Entry<K, V>> action) {
|
||||
Objects.requireNonNull(action);
|
||||
c.forEach(entryConsumer(action));
|
||||
}
|
||||
|
||||
static final class UnmodifiableEntrySetSpliterator<K, V>
|
||||
implements Spliterator<Entry<K, V>> {
|
||||
final Spliterator<Map.Entry<K, V>> s;
|
||||
|
||||
UnmodifiableEntrySetSpliterator(Spliterator<Entry<K, V>> s) {
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
|
||||
Objects.requireNonNull(action);
|
||||
return s.tryAdvance(entryConsumer(action));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
|
||||
Objects.requireNonNull(action);
|
||||
s.forEachRemaining(entryConsumer(action));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<Entry<K, V>> trySplit() {
|
||||
Spliterator<Entry<K, V>> split = s.trySplit();
|
||||
return split == null
|
||||
? null
|
||||
: new UnmodifiableEntrySetSpliterator<>(split);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long estimateSize() {
|
||||
return s.estimateSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getExactSizeIfKnown() {
|
||||
return s.getExactSizeIfKnown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int characteristics() {
|
||||
return s.characteristics();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCharacteristics(int characteristics) {
|
||||
return s.hasCharacteristics(characteristics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<? super Entry<K, V>> getComparator() {
|
||||
return s.getComparator();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Spliterator<Entry<K, V>> spliterator() {
|
||||
return new UnmodifiableEntrySetSpliterator<>(
|
||||
(Spliterator<Map.Entry<K, V>>) c.spliterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Entry<K, V>> stream() {
|
||||
return StreamSupport.stream(spliterator(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Entry<K, V>> parallelStream() {
|
||||
return StreamSupport.stream(spliterator(), true);
|
||||
}
|
||||
|
||||
public Iterator<Map.Entry<K, V>> iterator() {
|
||||
return new Iterator<Map.Entry<K, V>>() {
|
||||
private final Iterator<? extends Map.Entry<? extends K, ? extends V>> i = c.iterator();
|
||||
|
||||
public boolean hasNext() {
|
||||
return i.hasNext();
|
||||
}
|
||||
|
||||
public Map.Entry<K, V> next() {
|
||||
return new UnmodifiableEntry<>(i.next());
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
// Seems like an oversight. http://b/110351017
|
||||
public void forEachRemaining(Consumer<? super Map.Entry<K, V>> action) {
|
||||
i.forEachRemaining(entryConsumer(action));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object[] toArray() {
|
||||
Object[] a = c.toArray();
|
||||
for (int i = 0; i < a.length; i++)
|
||||
a[i] = new UnmodifiableEntry<>((Map.Entry<? extends K, ? extends V>) a[i]);
|
||||
return a;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T[] toArray(T[] a) {
|
||||
// We don't pass a to c.toArray, to avoid window of
|
||||
// vulnerability wherein an unscrupulous multithreaded client
|
||||
// could get his hands on raw (unwrapped) Entries from c.
|
||||
Object[] arr = c.toArray(a.length == 0 ? a : Arrays.copyOf(a, 0));
|
||||
|
||||
for (int i = 0; i < arr.length; i++)
|
||||
arr[i] = new UnmodifiableEntry<>((Map.Entry<? extends K, ? extends V>) arr[i]);
|
||||
|
||||
if (arr.length > a.length)
|
||||
return (T[]) arr;
|
||||
|
||||
System.arraycopy(arr, 0, a, 0, arr.length);
|
||||
if (a.length > arr.length)
|
||||
a[arr.length] = null;
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is overridden to protect the backing set against
|
||||
* an object with a nefarious equals function that senses
|
||||
* that the equality-candidate is Map.Entry and calls its
|
||||
* setValue method.
|
||||
*/
|
||||
public boolean contains(Object o) {
|
||||
if (!(o instanceof Map.Entry))
|
||||
return false;
|
||||
return c.contains(
|
||||
new UnmodifiableEntry<>((Map.Entry<?, ?>) o));
|
||||
}
|
||||
|
||||
/**
|
||||
* The next two methods are overridden to protect against
|
||||
* an unscrupulous List whose contains(Object o) method senses
|
||||
* when o is a Map.Entry, and calls o.setValue.
|
||||
*/
|
||||
public boolean containsAll(Collection<?> coll) {
|
||||
for (Object e : coll) {
|
||||
if (!contains(e)) // Invokes safe contains() above
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
|
||||
// Android-changed: (b/247094511) instanceof pattern variable is not yet supported.
|
||||
/*
|
||||
return o instanceof Set<?> s
|
||||
&& s.size() == c.size()
|
||||
&& containsAll(s); // Invokes safe containsAll() above
|
||||
*/
|
||||
if (!(o instanceof Set))
|
||||
return false;
|
||||
Set<?> s = (Set<?>) o;
|
||||
if (s.size() != c.size())
|
||||
return false;
|
||||
return containsAll(s); // Invokes safe containsAll() above
|
||||
}
|
||||
|
||||
/**
|
||||
* This "wrapper class" serves two purposes: it prevents
|
||||
* the client from modifying the backing Map, by short-circuiting
|
||||
* the setValue method, and it protects the backing Map against
|
||||
* an ill-behaved Map.Entry that attempts to modify another
|
||||
* Map Entry when asked to perform an equality check.
|
||||
*/
|
||||
private static class UnmodifiableEntry<K, V> implements Map.Entry<K, V> {
|
||||
private Map.Entry<? extends K, ? extends V> e;
|
||||
|
||||
UnmodifiableEntry(Map.Entry<? extends K, ? extends V> e) {
|
||||
this.e = Objects.requireNonNull(e);
|
||||
}
|
||||
|
||||
public K getKey() {
|
||||
return e.getKey();
|
||||
}
|
||||
|
||||
public V getValue() {
|
||||
return e.getValue();
|
||||
}
|
||||
|
||||
public V setValue(V value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return e.hashCode();
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
// Android-changed: (b/247094511) instanceof pattern variable is not yet
|
||||
// supported.
|
||||
/*
|
||||
return o instanceof Map.Entry<?, ?> t
|
||||
&& eq(e.getKey(), t.getKey())
|
||||
&& eq(e.getValue(), t.getValue());
|
||||
*/
|
||||
if (!(o instanceof Map.Entry))
|
||||
return false;
|
||||
Map.Entry<?, ?> t = (Map.Entry<?, ?>) o;
|
||||
return eq(e.getKey(), t.getKey()) &&
|
||||
eq(e.getValue(), t.getValue());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user