Bug 575580 - Return a copy-on-first-write List in InternalUtils.asCopy()

Change-Id: Ie1e4f915bdf12b0a46d1567938b382eb5077b639
Signed-off-by: Hannes Wellmann <wellmann.hannes1@gmx.net>
Reviewed-on: https://git.eclipse.org/r/c/equinox/rt.equinox.framework/+/184339
Tested-by: Equinox Bot <equinox-bot@eclipse.org>
Reviewed-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/InternalUtils.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/InternalUtils.java
index e8ced17..9f3c7be 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/InternalUtils.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/InternalUtils.java
@@ -15,11 +15,13 @@
 
 import java.security.Permission;
 import java.security.SecureRandom;
+import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Random;
+import java.util.RandomAccess;
 import java.util.UUID;
 import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
 import org.osgi.framework.Bundle;
@@ -33,8 +35,71 @@
 
 public class InternalUtils {
 
+	/**
+	 * Returns a mutable wrapped around the given list, that creates a copy of the
+	 * given list only if the list is about to be modified.
+	 * <p>
+	 * This method assumes that the given List is immutable and a
+	 * {@link RandomAccess} list.
+	 * </p>
+	 * 
+	 * @param list the list to be copied.
+	 * @return an effectively mutable and lazy copy of the given list.
+	 */
 	public static <T> List<T> asCopy(List<? extends T> list) {
-		return new ArrayList<>(list);
+		if (!(list instanceof RandomAccess)) {
+			throw new IllegalArgumentException("Only RandomAccess lists are supported"); //$NON-NLS-1$
+		}
+		return new CopyOnFirstWriteList<>(list);
+	}
+
+	private static final class CopyOnFirstWriteList<T> extends AbstractList<T> implements RandomAccess {
+		private List<T> copy;
+		private boolean copied = false;
+
+		CopyOnFirstWriteList(List<? extends T> list) {
+			copy = asList(list);
+		}
+
+		@Override
+		public T get(int index) {
+			return copy.get(index);
+		}
+
+		@Override
+		public int size() {
+			return copy.size();
+		}
+
+		@Override
+		public void add(int index, T element) {
+			ensureCopied();
+			copy.add(index, element);
+			modCount++;
+		}
+
+		@Override
+		public T remove(int index) {
+			ensureCopied();
+			T removed = copy.remove(index);
+			modCount++;
+			return removed;
+		}
+
+		@Override
+		public T set(int index, T element) {
+			ensureCopied();
+			T set = copy.set(index, element);
+			modCount++;
+			return set;
+		}
+
+		private void ensureCopied() {
+			if (!copied) {
+				copy = new ArrayList<>(copy);
+				copied = true;
+			}
+		}
 	}
 
 	/**