All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.yetamine.osgi.jdbc.internal.OrderedSequence Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2016 Yetamine
 *
 * Licensed 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 net.yetamine.osgi.jdbc.internal;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * Represents a sequence of items ordered by given criteria.
 *
 * 

* The represented sequence can be adjusted. The implementation is thread-safe, * hence it can be used conveniently with various trackers and listeners to get * reasonably accurate snapshots of recent state, e.g., which services are * available. * * @param * the type of the item keys * @param * the type of the item values */ final class OrderedSequence { /** * Represents an item of the sequence consisting of the value and its key. * * @param * the type of the item keys * @param * the type of the item values */ public static final class Item { /** Value of the item. */ private final V value; /** Key of the item. */ private final K key; /** * Creates a new instance. * * @param itemKey * the key of the item. It must not be {@code null}. * @param itemValue * the value of the item. It must not be {@code null}. */ public Item(K itemKey, V itemValue) { value = Objects.requireNonNull(itemValue); key = Objects.requireNonNull(itemKey); } /** * @see java.lang.Object#toString() */ @Override public String toString() { return String.format("[%s=%s]", key, value); } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof Item) { final Item o = (Item) obj; return key.equals(o.key) && value.equals(o.value); } return false; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return key.hashCode() ^ value.hashCode(); } /** * Returns the key of the item. * * @return the key of the item */ public K key() { return key; } /** * Returns the value of the item. * * @return the value of the item */ public V value() { return value; } } /* Implementation notes: * * A map to keep the values unique by some key is maintained, which allows * to distinguish even significant duplicates of a value. The map acts as * the primary source of the data for making the ordered list which is made * on demand and cached only until a change of the primary data. The map is * used as the lock for all non-atomic operations and for its own safety. */ /** Comparator to establish the order of the items. */ private final Comparator> comparator; /** Map for looking up the values and keeping them unique. */ private final Map> mapping = new HashMap<>(); /** Currently valid snapshot of the items in the desired order. */ private volatile List> items = Collections.emptyList(); /** Currently valid snapshot of the values in the desired order. */ private volatile List values = Collections.emptyList(); /** * Creates a new instance. * * @param ordering * the comparator defining the item ordering. It must not be * {@code null}. */ public OrderedSequence(Comparator> ordering) { comparator = Objects.requireNonNull(ordering); } /** * @see java.lang.Object#toString() */ @Override public String toString() { return items().toString(); } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } return (obj instanceof OrderedSequence) && items().equals(((OrderedSequence) obj).items()); } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return items().hashCode(); } /** * Adds an item. * * @param key * the key of the item. It must not be {@code null}. * @param value * the value of the item. It must not be {@code null}. * * @return {@code true} if the item was added by this call, {@code false} if * an item with an equal key already exists */ public boolean add(K key, V value) { final Item item = new Item<>(key, value); synchronized (mapping) { if (mapping.putIfAbsent(key, item) == null) { invalidate(); return true; } } return false; } /** * Adds an item or updates an existing one. * * @param key * the key of the item. It must not be {@code null}. * @param value * the value of the item. It must not be {@code null}. * * @return the previous item if any */ public Optional> set(K key, V value) { final Item item = new Item<>(key, value); final Item result; synchronized (mapping) { result = mapping.put(key, item); invalidate(); } return Optional.ofNullable(result); } /** * Removes an item. * * @param key * the key of the item * * @return the removed value if any */ public Optional> remove(Object key) { final Item result; synchronized (mapping) { result = mapping.remove(key); } return Optional.ofNullable(result); } /** * Removes an item if matching the given predicate. * *

* This method may be used to remove an existing item atomically with this * pattern: * *

     * sequence.get(key).ifPresent(item -> sequence.remove(item.key(), item::equals))
     * 
* * @param key * the key of the item * @param condition * the condition for removal. It must not be {@code null}. * * @return the removed value if any */ public Optional> remove(Object key, Predicate> condition) { final Item result; synchronized (mapping) { result = mapping.get(key); if ((result == null) || !condition.test(result)) { return Optional.empty(); } mapping.remove(key); } return Optional.of(result); } /** * Gets an item. * * @param key * the key of the item * * @return the item if any */ public Optional> item(Object key) { final Item result; synchronized (mapping) { result = mapping.get(key); } return Optional.ofNullable(result); } /** * Returns an immutable snapshot of the current state in the desired * ordering. * * @return an immutable snapshot of the current state */ public List> items() { // Using a double-checked locking here final List> result = items; return (result != null) ? result : refresh(); } /** * Gets a value of an item. * * @param key * the key of the item * * @return the value if any */ public Optional value(Object key) { return item(key).map(Item::value); } /** * Returns an immutable snapshot of the current state in the desired * ordering. * * @return an immutable snapshot of the current state */ public List values() { List result = values; if (result != null) { return result; } synchronized (mapping) { result = Collections.unmodifiableList(refresh().stream().map(Item::value).collect(Collectors.toList())); values = result; } return result; } /** * Updates the current snapshot if missing (which indicates that the primary * data changed and the snapshot may need an update). * * @return the updated snapshot */ private List> refresh() { List> result; synchronized (mapping) { result = items; if (result == null) { result = mapping.values().stream() // @formatter:break .sorted(comparator) // Make it ordered as expected .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); items = result; } } return result; } /** * Invalidates the current snapshot, e.g., when the primary data changes. */ private void invalidate() { values = null; items = null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy