diff --git a/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/Lists.java b/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/Lists.java index 4dcf00dbe8..f5a1143919 100644 --- a/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/Lists.java +++ b/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/Lists.java @@ -1,8 +1,11 @@ package aQute.bnd.unmodifiable; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collector; @SuppressWarnings("unchecked") public class Lists { @@ -71,4 +74,11 @@ public static List copyOf(Collection collection) { } return new ImmutableList(collection.toArray()); } + + public static Collector> toList() { + return Collector.of((Supplier>) ArrayList::new, List::add, (l, r) -> { + l.addAll(r); + return l; + }, Lists::copyOf); + } } diff --git a/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/Sets.java b/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/Sets.java index fbba34f301..0718cea219 100644 --- a/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/Sets.java +++ b/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/Sets.java @@ -2,7 +2,10 @@ import java.util.Arrays; import java.util.Collection; +import java.util.LinkedHashSet; import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collector; @SuppressWarnings("unchecked") public class Sets { @@ -69,8 +72,18 @@ public static Set copyOf(Collection collection) { if (collection.isEmpty()) { return of(); } + if (collection instanceof Set) { + return new ImmutableSet(collection.toArray()); + } return new ImmutableSet(collection.stream() .distinct() .toArray()); } + + public static Collector> toSet() { + return Collector.of((Supplier>) LinkedHashSet::new, Set::add, (l, r) -> { + l.addAll(r); + return l; + }, Sets::copyOf); + } } diff --git a/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/package-info.java b/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/package-info.java index 3adc7df466..934610213e 100644 --- a/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/package-info.java +++ b/biz.aQute.bnd.util/src/aQute/bnd/unmodifiable/package-info.java @@ -1,4 +1,4 @@ -@Version("2.0.0") +@Version("2.1.0") package aQute.bnd.unmodifiable; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.bnd.util/test/aQute/bnd/unmodifiable/ListsTest.java b/biz.aQute.bnd.util/test/aQute/bnd/unmodifiable/ListsTest.java index 364d1d4db2..04d8ef4373 100644 --- a/biz.aQute.bnd.util/test/aQute/bnd/unmodifiable/ListsTest.java +++ b/biz.aQute.bnd.util/test/aQute/bnd/unmodifiable/ListsTest.java @@ -550,4 +550,23 @@ public void spliterator() throws Exception { Spliterator.SUBSIZED, Spliterator.NONNULL); } + @Test + public void collector() { + List source = new ArrayList<>(); + source.add("e1"); + source.add("e2"); + source.add("e1"); + List list = source.stream() + .collect(Lists.toList()); + source.set(0, "changed"); + assertThat(list).hasSize(3) + .containsExactly("e1", "e2", "e1"); + assertThat(list.stream()).hasSize(3) + .containsExactly("e1", "e2", "e1"); + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> list.add("a")); + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> list.remove("a")); + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> list.remove("e1")); + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> list.clear()); + } + } diff --git a/biz.aQute.bnd.util/test/aQute/bnd/unmodifiable/SetsTest.java b/biz.aQute.bnd.util/test/aQute/bnd/unmodifiable/SetsTest.java index ca6aa9027e..4bc817d399 100644 --- a/biz.aQute.bnd.util/test/aQute/bnd/unmodifiable/SetsTest.java +++ b/biz.aQute.bnd.util/test/aQute/bnd/unmodifiable/SetsTest.java @@ -40,7 +40,7 @@ public void zero() { public void one() { Set set = Sets.of("e1"); assertThat(set).hasSize(1) - .containsExactlyInAnyOrder("e1"); + .containsExactly("e1"); assertThat(set.stream()).hasSize(1) .containsExactly("e1"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -53,7 +53,7 @@ public void one() { public void two() { Set set = Sets.of("e1", "e2"); assertThat(set).hasSize(2) - .containsExactlyInAnyOrder("e1", "e2"); + .containsExactly("e1", "e2"); assertThat(set.stream()).hasSize(2) .containsExactly("e1", "e2"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -66,7 +66,7 @@ public void two() { public void three() { Set set = Sets.of("e1", "e2", "e3"); assertThat(set).hasSize(3) - .containsExactlyInAnyOrder("e1", "e2", "e3"); + .containsExactly("e1", "e2", "e3"); assertThat(set.stream()).hasSize(3) .containsExactly("e1", "e2", "e3"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -79,7 +79,7 @@ public void three() { public void four() { Set set = Sets.of("e1", "e2", "e3", "e4"); assertThat(set).hasSize(4) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4"); + .containsExactly("e1", "e2", "e3", "e4"); assertThat(set.stream()).hasSize(4) .containsExactly("e1", "e2", "e3", "e4"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -92,7 +92,7 @@ public void four() { public void five() { Set set = Sets.of("e1", "e2", "e3", "e4", "e5"); assertThat(set).hasSize(5) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4", "e5"); + .containsExactly("e1", "e2", "e3", "e4", "e5"); assertThat(set.stream()).hasSize(5) .containsExactly("e1", "e2", "e3", "e4", "e5"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -105,7 +105,7 @@ public void five() { public void six() { Set set = Sets.of("e1", "e2", "e3", "e4", "e5", "e6"); assertThat(set).hasSize(6) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4", "e5", "e6"); + .containsExactly("e1", "e2", "e3", "e4", "e5", "e6"); assertThat(set.stream()).hasSize(6) .containsExactly("e1", "e2", "e3", "e4", "e5", "e6"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -118,7 +118,7 @@ public void six() { public void seven() { Set set = Sets.of("e1", "e2", "e3", "e4", "e5", "e6", "e7"); assertThat(set).hasSize(7) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4", "e5", "e6", "e7"); + .containsExactly("e1", "e2", "e3", "e4", "e5", "e6", "e7"); assertThat(set.stream()).hasSize(7) .containsExactly("e1", "e2", "e3", "e4", "e5", "e6", "e7"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -131,7 +131,7 @@ public void seven() { public void eight() { Set set = Sets.of("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8"); assertThat(set).hasSize(8) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8"); + .containsExactly("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8"); assertThat(set.stream()).hasSize(8) .containsExactly("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -144,7 +144,7 @@ public void eight() { public void nine() { Set set = Sets.of("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9"); assertThat(set).hasSize(9) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9"); + .containsExactly("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9"); assertThat(set.stream()).hasSize(9) .containsExactly("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -157,7 +157,7 @@ public void nine() { public void ten() { Set set = Sets.of("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "e10"); assertThat(set).hasSize(10) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "e10"); + .containsExactly("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "e10"); assertThat(set.stream()).hasSize(10) .containsExactly("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "e10"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -174,7 +174,7 @@ public void entries() { Set set = Sets.of(entries); entries[0] = "changed"; assertThat(set).hasSize(11) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "e10", "e11"); + .containsExactly("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "e10", "e11"); assertThat(set.stream()).hasSize(11) .containsExactly("e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "e10", "e11"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -209,7 +209,7 @@ public void copy() { Set set = Sets.copyOf(source); source.set(0, "changed"); assertThat(set).hasSize(2) - .containsExactlyInAnyOrder("e1", "e2"); + .containsExactly("e1", "e2"); assertThat(set.stream()).hasSize(2) .containsExactly("e1", "e2"); assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); @@ -230,7 +230,7 @@ public void array() { Set source = Sets.of("e1", "e2", "e3", "e4", "e5"); Object[] array = source.toArray(); assertThat(array).hasSize(5) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4", "e5"); + .containsExactly("e1", "e2", "e3", "e4", "e5"); } @Test @@ -240,12 +240,12 @@ public void array_string() { String[] array = source.toArray(target); assertThat(array).isNotSameAs(target) .hasSize(5) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4", "e5"); + .containsExactly("e1", "e2", "e3", "e4", "e5"); target = new String[source.size() + 1]; array = source.toArray(target); assertThat(array).isSameAs(target) - .containsExactlyInAnyOrder("e1", "e2", "e3", "e4", "e5", null); + .containsExactly("e1", "e2", "e3", "e4", "e5", null); assertThat(array[target.length - 1]).isNull(); } @@ -295,7 +295,7 @@ public void hash_min_value() { Set set = Sets.of("e1", "polygenelubricants", "GydZG_", "DESIGNING WORKHOUSES", "e5"); - assertThat(set).containsExactlyInAnyOrder("e5", "polygenelubricants", "GydZG_", "DESIGNING WORKHOUSES", "e1"); + assertThat(set).containsExactly("e1", "polygenelubricants", "GydZG_", "DESIGNING WORKHOUSES", "e5"); } @Test @@ -393,4 +393,23 @@ public void iterator_empty() { assertThat(holder.set).isFalse(); } + @Test + public void collector() { + List source = new ArrayList<>(); + source.add("e1"); + source.add("e2"); + source.add("e1"); + Set set = source.stream() + .collect(Sets.toSet()); + source.set(0, "changed"); + assertThat(set).hasSize(2) + .containsExactly("e1", "e2"); + assertThat(set.stream()).hasSize(2) + .containsExactly("e1", "e2"); + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.add("a")); + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.remove("a")); + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.remove("e1")); + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> set.clear()); + } + } diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReq.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReq.java index 53c415f981..acb8614c20 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReq.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReq.java @@ -1,9 +1,7 @@ package aQute.bnd.osgi.resource; -import static java.util.Collections.unmodifiableMap; import static java.util.Objects.requireNonNull; -import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -11,6 +9,8 @@ import org.osgi.resource.Requirement; import org.osgi.resource.Resource; +import aQute.bnd.unmodifiable.Maps; + abstract class CapReq { private final String namespace; private final Resource resource; @@ -21,8 +21,8 @@ abstract class CapReq { CapReq(String namespace, Resource resource, Map directives, Map attributes) { this.namespace = requireNonNull(namespace); this.resource = resource; - this.directives = unmodifiableMap(new HashMap<>(directives)); - this.attributes = unmodifiableMap(new HashMap<>(attributes)); + this.directives = Maps.copyOf(directives); + this.attributes = new DeferredValueMap<>(Maps.copyOf(attributes)); } public String getNamespace() { diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReqBuilder.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReqBuilder.java index 54b0cde80f..ba26640bf4 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReqBuilder.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReqBuilder.java @@ -150,7 +150,14 @@ private boolean isVersion(Object value, Class versionClass) { } public CapReqBuilder addAttributes(Map attributes) { - attributes.forEach(this::addAttribute); + for (Entry entry : attributes.entrySet()) { + if (entry instanceof DeferredValueEntry) { + DeferredValueEntry deferred = (DeferredValueEntry) entry; + addAttribute(deferred.getKey(), deferred.getDeferredValue()); + } else { + addAttribute(entry.getKey(), entry.getValue()); + } + } return this; } diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredComparableValue.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredComparableValue.java new file mode 100644 index 0000000000..2345cf3be7 --- /dev/null +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredComparableValue.java @@ -0,0 +1,19 @@ +package aQute.bnd.osgi.resource; + +import java.util.function.Supplier; + +class DeferredComparableValue> extends DeferredValue implements Comparable { + + DeferredComparableValue(Class type, Supplier supplier, int hashCode) { + super(type, supplier, hashCode); + } + + @SuppressWarnings("unchecked") + @Override + public int compareTo(T o) { + if (o instanceof DeferredComparableValue) { + o = ((DeferredComparableValue) o).get(); + } + return get().compareTo(o); + } +} diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredValue.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredValue.java new file mode 100644 index 0000000000..9ba943d5de --- /dev/null +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredValue.java @@ -0,0 +1,50 @@ +package aQute.bnd.osgi.resource; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Supplier; + +class DeferredValue implements Supplier { + private final Class type; + private final Supplier supplier; + private final int hashCode; + private T value; + + DeferredValue(Class type, Supplier supplier, int hashCode) { + this.type = requireNonNull(type); + this.supplier = requireNonNull(supplier); + this.hashCode = hashCode; + } + + @Override + public T get() { + T v = value; + if (v == null) { + return value = supplier.get(); + } + return v; + } + + Class type() { + return type; + } + + @Override + public int hashCode() { + return hashCode; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (obj instanceof DeferredValue) { + obj = ((DeferredValue) obj).get(); + } + return get().equals(obj); + } + + @Override + public String toString() { + return String.valueOf(get()); + } +} diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredValueEntry.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredValueEntry.java new file mode 100644 index 0000000000..3a4ef8dd8c --- /dev/null +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredValueEntry.java @@ -0,0 +1,57 @@ +package aQute.bnd.osgi.resource; + +import java.util.Map.Entry; + +class DeferredValueEntry implements Entry { + private final K key; + private final DeferredValue value; + + /** + * Constructor. + * + * @param key Must not be {@code null}. + * @param value Must not be {@code null}. + */ + DeferredValueEntry(K key, DeferredValue value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value.get(); + } + + DeferredValue getDeferredValue() { + return value; + } + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public int hashCode() { + return key.hashCode() ^ value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Entry)) { + return false; + } + Entry entry = (Entry) obj; + return key.equals(entry.getKey()) && value.equals(entry.getValue()); + } + + @Override + public String toString() { + return key + "=" + value; + } +} diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredValueMap.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredValueMap.java new file mode 100644 index 0000000000..fa38f8b3e0 --- /dev/null +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/DeferredValueMap.java @@ -0,0 +1,181 @@ +package aQute.bnd.osgi.resource; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; + +class DeferredValueMap extends AbstractMap implements Map { + private final Map map; + + DeferredValueMap(Map map) { + this.map = map; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @SuppressWarnings("unchecked") + @Override + public V get(Object key) { + V v = map.get(key); + if (v instanceof DeferredValue) { + v = ((DeferredValue) v).get(); + } + return v; + } + + /** + * This get method will not unwrap a DeferredValue. + * + * @param key The map key. + * @return The DeferredValue or value. + */ + Object getDeferred(Object key) { + Object v = map.get(key); + return v; + } + + @Override + public boolean equals(Object o) { + return map.equals(o); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public Set> entrySet() { + return new EntrySet<>(map); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + final static class EntrySet extends AbstractSet> { + private final Set> entries; + + EntrySet(Map map) { + this.entries = map.entrySet(); + } + + @Override + public Iterator> iterator() { + return new EntryIterator<>(entries); + } + + @Override + public int size() { + return entries.size(); + } + } + + final static class EntryIterator implements Iterator> { + private final Iterator> iterator; + + EntryIterator(Set> entries) { + this.iterator = entries.iterator(); + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Entry next() { + Entry entry = iterator.next(); + V v = entry.getValue(); + if (v instanceof DeferredValue) { + @SuppressWarnings("unchecked") + DeferredValue deferred = (DeferredValue) v; + return new DeferredValueEntry<>(entry.getKey(), deferred); + } + return entry; + } + } + + @Override + public V put(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public void replaceAll(BiFunction 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 mappingFunction) { + throw new UnsupportedOperationException(); + } + + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } +} diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/FilterImpl.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/FilterImpl.java index 790ec2e234..0ef166a4d6 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/FilterImpl.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/FilterImpl.java @@ -27,7 +27,6 @@ import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; import java.util.List; @@ -40,6 +39,8 @@ import org.osgi.framework.ServiceReference; import org.osgi.framework.Version; +import aQute.bnd.unmodifiable.Maps; + /** * RFC 1960-based Filter. Filter objects can be created by calling the * constructor with the desired filter string. A Filter object can be called @@ -191,7 +192,7 @@ static FilterImpl createFilter(String filterString) throws InvalidSyntaxExceptio */ @Override public boolean match(ServiceReference reference) { - return matches0((reference != null) ? new ServiceReferenceMap(reference) : Collections.emptyMap()); + return matches0((reference != null) ? new ServiceReferenceMap(reference) : Maps.of()); } /** @@ -208,7 +209,7 @@ public boolean match(ServiceReference reference) { */ @Override public boolean match(Dictionary dictionary) { - return matches0((dictionary != null) ? new CaseInsensitiveMap(dictionary) : Collections.emptyMap()); + return matches0((dictionary != null) ? new CaseInsensitiveMap(dictionary) : Maps.of()); } /** @@ -224,7 +225,7 @@ public boolean match(Dictionary dictionary) { */ @Override public boolean matchCase(Dictionary dictionary) { - return matches0((dictionary != null) ? DictionaryMap.asMap(dictionary) : Collections.emptyMap()); + return matches0((dictionary != null) ? DictionaryMap.asMap(dictionary) : Maps.of()); } /** @@ -241,7 +242,7 @@ public boolean matchCase(Dictionary dictionary) { */ @Override public boolean matches(Map map) { - return matches0((map != null) ? map : Collections.emptyMap()); + return matches0((map != null) ? map : Maps.of()); } abstract boolean matches0(Map map); diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceBuilder.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceBuilder.java index 427de83128..412b119f94 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceBuilder.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceBuilder.java @@ -1,5 +1,6 @@ package aQute.bnd.osgi.resource; +import static aQute.bnd.exceptions.SupplierWithException.asSupplierOrElse; import static aQute.bnd.osgi.Constants.DUPLICATE_MARKER; import static aQute.bnd.osgi.Constants.MIME_TYPE_BUNDLE; import static aQute.bnd.osgi.Constants.MIME_TYPE_JAR; @@ -51,6 +52,7 @@ import aQute.bnd.osgi.Jar; import aQute.bnd.osgi.Processor; import aQute.bnd.osgi.Verifier; +import aQute.bnd.unmodifiable.Lists; import aQute.bnd.version.VersionRange; import aQute.lib.converter.Converter; import aQute.lib.filter.Filter; @@ -652,7 +654,7 @@ public void addRequirements(List requires) { public List findCapabilities(String ns, String filter) { if (filter == null || capabilities.isEmpty()) - return Collections.emptyList(); + return Lists.of(); List capabilities = new ArrayList<>(); Filter f = new Filter(filter); @@ -705,6 +707,19 @@ public void addContentCapability(URI uri, String sha256, long length, String mim addCapability(c); } + void addContentCapability(URI uri, DeferredValue sha256, long length, String mime) { + assert uri != null; + assert sha256 != null; + assert length >= 0; + + CapabilityBuilder c = new CapabilityBuilder(ContentNamespace.CONTENT_NAMESPACE); + c.addAttribute(ContentNamespace.CONTENT_NAMESPACE, sha256); + c.addAttribute(ContentNamespace.CAPABILITY_URL_ATTRIBUTE, uri.toString()); + c.addAttribute(ContentNamespace.CAPABILITY_SIZE_ATTRIBUTE, Long.valueOf(length)); + c.addAttribute(ContentNamespace.CAPABILITY_MIME_ATTRIBUTE, mime != null ? mime : MIME_TYPE_BUNDLE); + addCapability(c); + } + public boolean addFile(File file, URI uri) throws Exception { if (uri == null) uri = file.toURI(); @@ -715,8 +730,11 @@ public boolean addFile(File file, URI uri) throws Exception { hasIdentity = addManifest(manifest); } String mime = hasIdentity ? MIME_TYPE_BUNDLE : MIME_TYPE_JAR; - String sha256 = SHA256.digest(file) - .asHex(); + int deferredHashCode = hashCode(file); + DeferredValue sha256 = new DeferredComparableValue<>(String.class, + asSupplierOrElse(() -> SHA256.digest(file) + .asHex(), null), + deferredHashCode); addContentCapability(uri, sha256, file.length(), mime); if (hasIdentity) { @@ -725,6 +743,11 @@ public boolean addFile(File file, URI uri) throws Exception { return hasIdentity; } + private static int hashCode(File file) { + return file.getAbsoluteFile() + .hashCode(); + } + /** * Add simple class name hashes to the exported packages. This should not be * called before any package capabilities are set since we only hash class diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceImpl.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceImpl.java index 255c4f95b6..ad9dc74d69 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceImpl.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceImpl.java @@ -1,15 +1,11 @@ package aQute.bnd.osgi.resource; import static aQute.lib.collections.Logic.retain; -import static java.util.Collections.unmodifiableList; -import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; import java.io.InputStream; import java.net.URI; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -22,6 +18,7 @@ import aQute.bnd.osgi.Constants; import aQute.bnd.osgi.resource.ResourceUtils.ContentCapability; +import aQute.bnd.unmodifiable.Lists; class ResourceImpl implements Resource, Comparable, RepositoryContent { @@ -33,9 +30,9 @@ class ResourceImpl implements Resource, Comparable, RepositoryContent private volatile transient Map locations; void setCapabilities(List capabilities) { - allCapabilities = unmodifiableList(capabilities); + allCapabilities = Lists.copyOf(capabilities); capabilityMap = capabilities.stream() - .collect(groupingBy(Capability::getNamespace, collectingAndThen(toList(), Collections::unmodifiableList))); + .collect(groupingBy(Capability::getNamespace, Lists.toList())); locations = null; // clear so equals/hashCode can recompute } @@ -45,13 +42,13 @@ public List getCapabilities(String namespace) { List caps = (namespace != null) ? ((capabilityMap != null) ? capabilityMap.get(namespace) : null) : allCapabilities; - return (caps != null) ? caps : Collections.emptyList(); + return (caps != null) ? caps : Lists.of(); } void setRequirements(List requirements) { - allRequirements = unmodifiableList(requirements); + allRequirements = Lists.copyOf(requirements); requirementMap = requirements.stream() - .collect(groupingBy(Requirement::getNamespace, collectingAndThen(toList(), Collections::unmodifiableList))); + .collect(groupingBy(Requirement::getNamespace, Lists.toList())); } @Override @@ -59,7 +56,7 @@ public List getRequirements(String namespace) { List reqs = (namespace != null) ? ((requirementMap != null) ? requirementMap.get(namespace) : null) : allRequirements; - return (reqs != null) ? reqs : Collections.emptyList(); + return (reqs != null) ? reqs : Lists.of(); } @Override diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceUtils.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceUtils.java index e3b363fd32..8e7a68d909 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceUtils.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceUtils.java @@ -1,5 +1,6 @@ package aQute.bnd.osgi.resource; +import static aQute.bnd.exceptions.FunctionWithException.asFunction; import static java.lang.invoke.MethodHandles.publicLookup; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toCollection; @@ -16,7 +17,6 @@ import java.net.URL; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -61,6 +61,10 @@ import aQute.bnd.osgi.Macro; import aQute.bnd.osgi.Processor; import aQute.bnd.service.library.LibraryNamespace; +import aQute.bnd.stream.MapStream; +import aQute.bnd.unmodifiable.Lists; +import aQute.bnd.unmodifiable.Maps; +import aQute.bnd.unmodifiable.Sets; import aQute.bnd.version.Version; import aQute.lib.converter.Converter; import aQute.lib.strings.Strings; @@ -440,14 +444,14 @@ private static Object getValue(Map attrs, String name) { public static Set getResources(Collection providers) { if (providers == null || providers.isEmpty()) - return Collections.emptySet(); + return Sets.of(); return getResources(providers.stream()); } public static Map> getIndexedByResource(Collection providers) { if (providers == null || providers.isEmpty()) - return Collections.emptyMap(); + return Maps.of(); return providers.stream() .collect(groupingBy(Capability::getResource, toCapabilities())); } @@ -598,16 +602,47 @@ public static String toProvideCapability(Capability capability) throws Exception } public static Map getLocations(Resource resource) { - return capabilityStream(resource, ContentNamespace.CONTENT_NAMESPACE, ContentCapability.class) - .filter(c -> Objects.nonNull(c.url())) - // We can't use Collectors.toMap since we must handle null - // ContentCapability::osgi_content values. - // .collect(toMap(ContentCapability::url, - // ContentCapability::osgi_content)); - .collect(Collector.of(HashMap::new, (m, c) -> m.put(c.url(), c.osgi_content()), (m1, m2) -> { + Map locations = MapStream.of( + capabilityStream(resource, ContentNamespace.CONTENT_NAMESPACE).map(asFunction(c -> { + Map attributes = c.getAttributes(); + Object u = attributes.get(ContentNamespace.CAPABILITY_URL_ATTRIBUTE); + if (u == null) { + return null; + } + URI uri = URI.create(u.toString()); + Object osgi_content; + if (attributes instanceof DeferredValueMap) { + DeferredValueMap deferredMap = (DeferredValueMap) attributes; + Object value = deferredMap.getDeferred(ContentNamespace.CONTENT_NAMESPACE); + if (value instanceof DeferredValue) { + @SuppressWarnings("unchecked") + DeferredValue deferred = (DeferredValue) value; + if (String.class.isAssignableFrom(deferred.type())) { + osgi_content = deferred; + } else { + osgi_content = cnv.convert(String.class, deferred.get()); + } + } else { + osgi_content = cnv.convert(String.class, value); + } + } else { + osgi_content = cnv.convert(String.class, attributes.get(ContentNamespace.CONTENT_NAMESPACE)); + } + return MapStream.entry(uri, osgi_content); + })) + .filter(Objects::nonNull)) + // We can't use MapStream.toMap since we must handle null + // osgi_content values. + // .collect(MapStream.toMap()); + .collect(Collector.of(HashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), (m1, m2) -> { m1.putAll(m2); return m1; })); + @SuppressWarnings({ + "rawtypes", "unchecked" + }) + Map result = new DeferredValueMap(locations); + return result; } public static List findProviders(Requirement requirement, @@ -685,7 +720,7 @@ public static List toVersionedClauses(Collection reso return runBundles; } - private final static Collection all = Collections.singleton(createWildcardRequirement()); + private final static Collection all = Lists.of(createWildcardRequirement()); /** * Return all resources from a repository as returned by the wildcard diff --git a/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/MavenBndRepository.java b/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/MavenBndRepository.java index 0800262c9f..cf8d6d90a3 100644 --- a/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/MavenBndRepository.java +++ b/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/MavenBndRepository.java @@ -1,8 +1,6 @@ package aQute.bnd.repository.maven.provider; import static aQute.bnd.osgi.Constants.BSN_SOURCE_SUFFIX; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toSet; import java.io.Closeable; import java.io.File; @@ -594,7 +592,7 @@ synchronized boolean init() { source = source.replaceAll("(\\s|,|;|\n|\r)+", "\n"); } Set multi = Strings.splitAsStream(configuration.multi()) - .collect(collectingAndThen(toSet(), Sets::copyOf)); + .collect(Sets.toSet()); this.index = new IndexFile(domain, reporter, indexFile, source, storage, client.promiseFactory(), multi); this.index.open();