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

com.sun.javafx.css.FixedCapacitySet Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.css;

import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;

/**
 * Provides set implementations which have a fixed capacity. These are highly optimized
 * and only suitable for very specific use cases where the maximum size is known in advance
 * and when items are expected to never be removed from the sets. Fixed capacity sets will
 * throw {@link IllegalStateException} if adding an element would exceed the maximum capacity.
 *
 * 

These sets do not allow {@code null} elements, and unless otherwise specified, passing * {@code null} for any argument will result in a {@link NullPointerException}. * *

Specifically, these sets are optimized for holding sets of style class names, which * have the following characteristics: * *

    *
  • Never {@code null}
  • *
  • Sets in almost all cases contain just 1 or 2 elements, only rarely containing 3 or more
  • *
  • Sets are often compared using containsAll; to avoid creating an iterator when the input is not * a {@code FixedSizeSet}, the inverse function {@link #isSuperSetOf(Collection)} is provided
  • *
* * The provided implementations are optimized for memory use, fast containsAll and * fast iteration. Generally, these sets will use half the memory of an equivalent * {@code HashSet} (and comparable to the immutable sets provided by {@code Set.of()}) * as they do not require a wrapper to hold each element. * *

These sets can only be appended, reject {@code null}s, have a fixed maximum size (which will throw * an exception if exceeded), and can be frozen (made read-only without using a wrapper). * *

The fall back set implementation for large sets uses open addressing. It is * only lightly optimized as the expectation is that it will see little to no use. * It is still preferred over {@code HashSet} due to its low memory foot print, and * faster iteration. * * @param the element type */ public sealed abstract class FixedCapacitySet extends AbstractSet { private static final FixedCapacitySet EMPTY; static { EMPTY = new Single<>(); EMPTY.freeze(); } @SuppressWarnings("unchecked") private static FixedCapacitySet empty() { return (FixedCapacitySet) EMPTY; } /** * Creates a new {@link FixedCapacitySet} with the given maximum capacity. * If the capacity is exceeded, fixed capacity sets do not grow, but instead * throw an {@link IllegalStateException}. * * @param the element type * @param maximumCapacity the maximum possible number of elements the set can hold, cannot be negative * @return a new empty set, never {@code null} */ public static FixedCapacitySet of(int maximumCapacity) { return maximumCapacity == 0 ? empty() : maximumCapacity == 1 ? new Single<>() : maximumCapacity == 2 ? new Duo<>() : maximumCapacity < 10 ? new Hashless<>(maximumCapacity) // will reject negative values : new OpenAddressed<>(maximumCapacity); } private boolean frozen; /** * Checks if the given collection contains all elements * of this collection. This is the same as {@link #containsAll(Collection)} * with the source and target reversed, ie. {@code "a.containsAll(b)"} is equivalent * to {@code "b.isSuperSetOf(a)"}. * *

If the given collection is small, or has good {@link #contains(Object)} * performance, using this inverse function avoids creating an {@link Iterator} * for this collection. * * @param c a collection to check, cannot be {@code null} * @return {@code true} if the given collection contains all elements of this * collection, otherwise {@code false} */ public abstract boolean isSuperSetOf(Collection c); /** * Freezes this collection, turning it permanently read-only. After freezing, * any method that would modify the collection will instead throw * {@link UnsupportedOperationException}. * *

This can be used to avoid wrapping the collection with an unmodifiable * collection or making a read-only copy. */ public final void freeze() { this.frozen = true; } /** * Checks if the set is allowed to be mutated, and throws an * {@link UnsupportedOperationException} otherwise. */ protected final void ensureNotFrozen() { if (frozen) { throw new UnsupportedOperationException(); } } /** * A set that can hold 0 or 1 elements. * * @param the element type */ private static final class Single extends FixedCapacitySet { private T element; @Override public int size() { return element == null ? 0 : 1; } @Override public Iterator iterator() { return new Iterator<>() { private boolean hasNext = element != null; @Override public boolean hasNext() { return hasNext; } @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); } hasNext = false; return element; } }; } @Override public boolean isSuperSetOf(Collection c) { return element == null || c.contains(element); } @Override public boolean contains(Object o) { return element != null && element.equals(o); } @Override public boolean add(T e) { ensureNotFrozen(); if (contains(Objects.requireNonNull(e, "e"))) { return false; } if (element != null) { throw new IllegalStateException("set is full"); } element = e; return true; } @Override public int hashCode() { return element == null ? 0 : element.hashCode(); } } /** * A set that can hold 0, 1 or 2 elements. * * @param the element type */ private static final class Duo extends FixedCapacitySet { private T element1; private T element2; private int size; @Override public int size() { return size; } @Override public Iterator iterator() { return new Iterator<>() { private int index; @Override public boolean hasNext() { return index < size; } @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); } return index++ == 0 ? element1 : element2; } }; } @Override public boolean isSuperSetOf(Collection c) { return element1 == null || (c.contains(element1) && (element2 == null || c.contains(element2))); } @Override public boolean contains(Object o) { return (element1 != null && element1.equals(o)) || (element2 != null && element2.equals(o)); } @Override public boolean add(T e) { ensureNotFrozen(); if (contains(Objects.requireNonNull(e, "e"))) { return false; } if (size == 2) { throw new IllegalStateException("set is full"); } if (size == 0) { element1 = e; } else { element2 = e; } size++; return true; } @Override public int hashCode() { return element1 == null ? 0 : element1.hashCode() + (element2 == null ? 0 : element2.hashCode()); } } /** * A set which can hold a fixed maximum number of elements. This implementation * does not use hashing, but does eliminate duplicates (as per the set contract). * Performance is better than sets which use hashing when the number of elements * is small enough (cut off point is somewhere around 10 elements, but it will * depend on the cost of the hash function). * * @param the element type */ private static final class Hashless extends FixedCapacitySet { private final T[] elements; private int size; @SuppressWarnings("unchecked") private Hashless(int capacity) { this.elements = (T[]) new Object[capacity]; } @Override public int size() { return size; } @Override public Iterator iterator() { return new Iterator<>() { private int index; @Override public boolean hasNext() { return index < size; } @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); } return elements[index++]; } }; } @Override public boolean contains(Object o) { for (int i = 0; i < size; i++) { if (elements[i].equals(o)) { return true; } } return false; } @Override public boolean isSuperSetOf(Collection c) { for (int i = 0; i < size; i++) { if (!c.contains(elements[i])) { return false; } } return true; } @Override public boolean add(T e) { ensureNotFrozen(); if (contains(Objects.requireNonNull(e, "e"))) { return false; // already present, set unchanged } if (size == elements.length) { throw new IllegalStateException("set is full"); } elements[size++] = e; return true; // not present, set changed } @Override public int hashCode() { int h = 0; for (int i = 0; i < size; i++) { h += elements[i].hashCode(); } return h; } } /** * A set which can hold a fixed maximum number of elements. This implementation * uses open addressing to handle hash collisions using linear probing. It has a * memory footprint which is much smaller than an equivalent {@code HashSet} but * has worse worst case performance (for modification and contains) when there * are many collisions. Iteration speed will be similar to other array based * collections (which is to say, faster than {@code HashSet}). * * @param the element type */ private static final class OpenAddressed extends FixedCapacitySet { private final T[] elements; private final int requestedCapacity; private final int mask; private int size; @SuppressWarnings("unchecked") private OpenAddressed(int capacity) { this.requestedCapacity = capacity; int shift = Integer.SIZE - Integer.numberOfLeadingZeros(capacity * 2 - capacity / 2); /* * The shift calculated ensures the elements array's size will be a power * of 2, and ensures that the load factor of this hash map will be roughly * between 0.3 and 0.7; high load factors are detrimental to performance, * while low load factors will consume more memory than necessary. */ this.elements = (T[]) new Object[1 << shift]; this.mask = (1 << shift) - 1; /* * Note: the size of the elements array MUST always be greater than the * requested capacity as the contains check relies on there always being * at least one empty bucket. */ assert elements.length > requestedCapacity : "must have more buckets than capacity"; } @Override public int size() { return size; } @Override public Iterator iterator() { return new Iterator<>() { private int index = findFilledBucket(0); private int findFilledBucket(int start) { for (int i = start; i < elements.length; i++) { if (elements[i] != null) { return i; } } return -1; } @Override public boolean hasNext() { return index >= 0; } @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); } T element = elements[index]; index = findFilledBucket(index + 1); return element; } }; } @Override public boolean contains(Object o) { int bucket = determineBucketIndex(o); /* * Note: because the open addressed set always has spare capacity * this loop will always exit because an unused bucket is encountered * at some point. */ while (elements[bucket] != null) { if (elements[bucket].equals(o)) { return true; } bucket++; // linear probing for simplicity if (bucket >= elements.length) { bucket = 0; } } return false; // empty bucket encountered, not contained } @Override public boolean isSuperSetOf(Collection c) { for (int i = 0; i < elements.length; i++) { T element = elements[i]; if (element != null && !c.contains(element)) { return false; } } return true; } @Override public boolean add(T e) { ensureNotFrozen(); int bucket = determineBucketIndex(e); // implicit null check here while (elements[bucket] != null) { if (elements[bucket].equals(e)) { return false; // already present, set unchanged } bucket++; // linear probing for simplicity if (bucket >= elements.length) { bucket = 0; // there is no risk of this becoming an infinite loop as there is always spare capacity } } if (size == requestedCapacity) { // this check is "late" so that adding the same element to an already "full" set will correctly return "false" throw new IllegalStateException("set is full"); } elements[bucket] = e; size++; return true; // not present, set changed } @Override public int hashCode() { int h = 0; for (int i = 0; i < elements.length; i++) { T element = elements[i]; h += element == null ? 0 : element.hashCode(); } return h; } private int determineBucketIndex(Object o) { int h = o.hashCode(); return (h ^ (h >>> 16)) & mask; // inspired by HashMap } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy