Skip to content

Commit

Permalink
resource: Defer SHA-256 calculation into the future
Browse files Browse the repository at this point in the history
The osgi.content capability must hold the SHA-256 value of the
resource. But this value is rarely used. So, when building a resource
from a file, we defer the SHA-256 calculation until actually needed.
The can help performance of FileSetRepository used by bndtools m2e.

However, we do need the hashCode of the SHA-256 value early, so use
a substitute hashCode value based upon the File object. This is not
perfect since two files can hold identical content and the hashCode of
their SHA-256 values should be identical. But in practice, this should
work when creating a resource from a file since the early need for the
hashCode of the SHA-256 value is in the computing of the hashCode of
the capability so it can be inserted in a set. But creating a resource
from a file only creates a single osgi.content capability.

Fixes #5322

Signed-off-by: BJ Hargrave <bj@hargrave.dev>
  • Loading branch information
bjhargrave committed Jul 18, 2022
1 parent 86b426d commit 8d9ac47
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 14 deletions.
8 changes: 4 additions & 4 deletions biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReq.java
@@ -1,16 +1,16 @@
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;

import org.osgi.resource.Capability;
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;
Expand All @@ -21,8 +21,8 @@ abstract class CapReq {
CapReq(String namespace, Resource resource, Map<String, String> directives, Map<String, Object> 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() {
Expand Down
10 changes: 9 additions & 1 deletion biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReqBuilder.java
Expand Up @@ -150,7 +150,15 @@ private boolean isVersion(Object value, Class<?> versionClass) {
}

public CapReqBuilder addAttributes(Map<? extends String, ? extends Object> attributes) {
attributes.forEach(this::addAttribute);
for (Entry<? extends String, ? extends Object> entry : attributes.entrySet()) {
if (entry instanceof DeferredValueEntry) {
@SuppressWarnings("unchecked")
DeferredValueEntry<String, Object> deferred = (DeferredValueEntry<String, Object>) entry;
addAttribute(deferred.getKey(), deferred.getDeferredValue());
} else {
addAttribute(entry.getKey(), entry.getValue());
}
}
return this;
}

Expand Down
@@ -0,0 +1,19 @@
package aQute.bnd.osgi.resource;

import java.util.function.Supplier;

class DeferredComparableValue<T extends Comparable<T>> extends DeferredValue<T> implements Comparable<T> {

DeferredComparableValue(Class<T> type, Supplier<? extends T> supplier, int hashCode) {
super(type, supplier, hashCode);
}

@SuppressWarnings("unchecked")
@Override
public int compareTo(T o) {
if (o instanceof DeferredComparableValue) {
o = ((DeferredComparableValue<T>) o).get();
}
return get().compareTo(o);
}
}
50 changes: 50 additions & 0 deletions 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<T> implements Supplier<T> {
private final Class<T> type;
private final Supplier<? extends T> supplier;
private final int hashCode;
private T value;

DeferredValue(Class<T> type, Supplier<? extends T> 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<T> type() {
return type;
}

@Override
public int hashCode() {
return hashCode;
}

@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (obj instanceof DeferredValue) {
obj = ((DeferredValue<T>) obj).get();
}
return get().equals(obj);
}

@Override
public String toString() {
return String.valueOf(get());
}
}
@@ -0,0 +1,57 @@
package aQute.bnd.osgi.resource;

import java.util.Map.Entry;

class DeferredValueEntry<K, V> implements Entry<K, V> {
private final K key;
private final DeferredValue<V> value;

/**
* Constructor.
*
* @param key Must not be {@code null}.
* @param value Must not be {@code null}.
*/
DeferredValueEntry(K key, DeferredValue<V> value) {
this.key = key;
this.value = value;
}

@Override
public K getKey() {
return key;
}

@Override
public V getValue() {
return value.get();
}

DeferredValue<V> 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;
}
}
181 changes: 181 additions & 0 deletions 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<K, V> extends AbstractMap<K, V> implements Map<K, V> {
private final Map<K, V> map;

DeferredValueMap(Map<K, V> 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>) 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<Entry<K, V>> entrySet() {
return new EntrySet<>(map);
}

@Override
public Set<K> keySet() {
return map.keySet();
}

final static class EntrySet<K, V> extends AbstractSet<Entry<K, V>> {
private final Set<Entry<K, V>> entries;

EntrySet(Map<K, V> map) {
this.entries = map.entrySet();
}

@Override
public Iterator<Entry<K, V>> iterator() {
return new EntryIterator<>(entries);
}

@Override
public int size() {
return entries.size();
}
}

final static class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
private final Iterator<Entry<K, V>> iterator;

EntryIterator(Set<Entry<K, V>> entries) {
this.iterator = entries.iterator();
}

@Override
public boolean hasNext() {
return iterator.hasNext();
}

@Override
public Entry<K, V> next() {
Entry<K, V> entry = iterator.next();
V v = entry.getValue();
if (v instanceof DeferredValue) {
@SuppressWarnings("unchecked")
DeferredValue<V> deferred = (DeferredValue<V>) 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<? extends K, ? extends V> map) {
throw new UnsupportedOperationException();
}

@Override
public void clear() {
throw new UnsupportedOperationException();
}

@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();
}
}

0 comments on commit 8d9ac47

Please sign in to comment.