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

com.alee.utils.swing.WeakComponentData Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.utils.swing;

import com.alee.api.annotations.NotNull;
import com.alee.api.annotations.Nullable;
import com.alee.api.jdk.BiConsumer;
import com.alee.api.jdk.BiFunction;
import com.alee.api.jdk.BiPredicate;
import com.alee.api.jdk.Function;
import com.alee.utils.collection.ImmutableSet;
import com.alee.utils.collection.WeakHashSet;

import javax.swing.*;
import java.util.Set;

/**
 * This is a special wrapper for any {@link JComponent}-related data that have to be stored using weak references. The only hard reference
 * to that data will be stored within {@link JComponent} itself, so the data will only get removed along with the {@link JComponent} itself
 * or if removal calls are made explicitly.
 *
 * This class also keeps a {@link Set} of weak references to all components that have data of this kind stored within them. It can be used
 * to iterate through all of those components whenever it is needed.
 *
 * Note that stored data cannot be {@code null} at any point as {@code null} is considered to be no data case.
 *
 * @param  {@link JComponent} type
 * @param  data type
 * @author Mikle Garin
 */
public class WeakComponentData
{
    /**
     * Key used to place data within {@link JComponent} using {@link JComponent#putClientProperty(Object, Object)} method.
     * Make sure that every manager uses its own unique keys for registering different data to avoid unwanted overwrites.
     */
    @NotNull
    protected final String key;

    /**
     * {@link Set} of {@link JComponent}s that have data of this kind stored within them.
     * {@link WeakHashSet} implementation is used to avoid hard component references that could cause memory leaks.
     */
    @NotNull
    protected final Set components;

    /**
     * Constructs new {@link WeakComponentData}.
     *
     * @param key             key used to place data within {@link JComponent}
     * @param initialCapacity initial capacity for the {@link Set} of {@link JComponent}s
     */
    public WeakComponentData ( @NotNull final String key, final int initialCapacity )
    {
        this.key = key;
        this.components = new WeakHashSet ( initialCapacity );
    }

    /**
     * Returns {@link Set} of {@link JComponent}s that have data of this kind stored within them.
     *
     * @return {@link Set} of {@link JComponent}s that have data of this kind stored within them
     */
    @NotNull
    public synchronized Set components ()
    {
        return new ImmutableSet ( components );
    }

    /**
     * Returns size of the {@link Set} of {@link JComponent}s that have data of this kind stored within them.
     *
     * @return size of the {@link Set} of {@link JComponent}s that have data of this kind stored within them
     */
    public synchronized int size ()
    {
        return components.size ();
    }

    /**
     * Returns whether or not specified {@link JComponent} has data of this kind stored within.
     *
     * @param component {@link JComponent} to check data in
     * @return {@code true} if specified {@link JComponent} has data of this kind stored within, {@code false} otherwise
     */
    public synchronized boolean contains ( @NotNull final C component )
    {
        return components.contains ( component );
    }

    /**
     * Returns data stored in the {@link JComponent}.
     * Could return {@code null} if no data can be found.
     *
     * @param component {@link JComponent} to retrieve data from
     * @return data stored in the {@link JComponent}
     */
    @Nullable
    public synchronized D get ( @NotNull final C component )
    {
        return ( D ) component.getClientProperty ( key );
    }

    /**
     * Returns data stored in the {@link JComponent}.
     * If no data can be found it will be created using mapping {@link Function}, stored within {@link JComponent} and returned as result.
     *
     * @param component   {@link JComponent} to retrieve data from
     * @param defaultData {@link Function} that creates default data in case it is missing
     * @return data stored in the {@link JComponent}
     */
    @NotNull
    public synchronized D get ( @NotNull final C component, @NotNull final Function defaultData )
    {
        // Trying to retrieve existing data
        D data = get ( component );
        if ( data == null )
        {
            // Requesting new data
            data = defaultData.apply ( component );
            set ( component, data );
        }
        return data;
    }

    /**
     * Modifies data stored in the {@link JComponent} using specified {@link BiFunction} and returns it.
     * If no data can be found it will be created using mapping {@link Function}, stored within {@link JComponent} and returned as result.
     *
     * @param component    {@link JComponent} to retrieve and modify data from
     * @param modifiedData {@link BiFunction} that modifies data in case it exists
     * @param defaultData  {@link Function} that creates default data in case it is missing
     * @return modified data from the {@link JComponent}
     */
    @NotNull
    public synchronized D modify ( @NotNull final C component, @NotNull final BiFunction modifiedData,
                                   @NotNull final Function defaultData )
    {
        // Trying to retrieve existing data
        D data = get ( component );
        if ( data == null )
        {
            // Requesting new data
            data = defaultData.apply ( component );
            set ( component, data );
        }
        else
        {
            // Modifying existing data
            data = modifiedData.apply ( component, data );
            set ( component, data );
        }
        return data;
    }

    /**
     * Stores data in the specified {@link JComponent}.
     * Providing {@code null} data would result in calling {@link #clear(JComponent)} method instead.
     *
     * @param component {@link JComponent} to store data in
     * @param data      data to store
     * @return old data
     */
    @Nullable
    public synchronized D set ( @NotNull final C component, @Nullable final D data )
    {
        // Saving old data
        final D oldData = get ( component );

        // Modifying data
        if ( data != null )
        {
            // Saving new data
            component.putClientProperty ( key, data );
            components.add ( component );
        }
        else
        {
            // Clearing data instead if its null
            clear ( component );
        }

        // Returning old data
        return oldData;
    }

    /**
     * Stores data in the specified {@link JComponent}.
     * Providing {@code null} data would result in calling {@link #clear(JComponent, BiConsumer)} method instead.
     *
     * @param component       {@link JComponent} to store data in
     * @param data            data to store
     * @param oldDataConsumer {@link BiConsumer} for previous data
     * @return old data
     */
    @Nullable
    public synchronized D set ( @NotNull final C component, @Nullable final D data, @NotNull final BiConsumer oldDataConsumer )
    {
        // Processing old data
        final D oldData;
        if ( contains ( component ) )
        {
            oldData = get ( component );
            oldDataConsumer.accept ( component, get ( component ) );
        }
        else
        {
            oldData = null;
        }

        // Modifying data
        if ( data != null )
        {

            // Saving new data
            set ( component, data );
        }
        else
        {
            // Clearing data instead if its null
            clear ( component, oldDataConsumer );
        }

        // Returning old data
        return oldData;
    }

    /**
     * Clears data stored in the {@link JComponent}.
     *
     * @param component {@link JComponent} to clear stored data for
     * @return old data
     */
    @Nullable
    public synchronized D clear ( @NotNull final C component )
    {
        // Saving old data
        final D oldData = get ( component );

        // Clearing data
        component.putClientProperty ( key, null );
        components.remove ( component );

        // Returning old data
        return oldData;
    }

    /**
     * Clears data stored in the {@link JComponent}.
     *
     * @param component           {@link JComponent} to clear stored data for
     * @param removedDataConsumer {@link BiConsumer} for removed data
     * @return old data
     */
    @Nullable
    public synchronized D clear ( @NotNull final C component, @NotNull final BiConsumer removedDataConsumer )
    {
        final D oldData;
        if ( contains ( component ) )
        {
            // Saving old data
            oldData = get ( component );

            // Processing data to be removed
            removedDataConsumer.accept ( component, oldData );

            // Clearing data
            clear ( component );
        }
        else
        {
            // No old data
            oldData = null;
        }
        return oldData;
    }

    /**
     * Provides each stored data piece into specified {@link BiConsumer}.
     *
     * @param consumer {@link BiConsumer} for {@link JComponent} and data
     */
    public synchronized void forEach ( @NotNull final BiConsumer consumer )
    {
        for ( final C component : components () )
        {
            // Retrieving data from the component
            final D data = get ( component );

            // Consuming data
            consumer.accept ( component, data );
        }
    }

    /**
     * Returns whether or not at least one of the stored data pieces is accepted by specified {@link BiPredicate}.
     *
     * @param predicate {@link BiPredicate} for {@link JComponent} and data
     * @return {@code true} if at least one of the stored data pieces is accepted by specified {@link BiPredicate}, {@code false} otherwise
     */
    public synchronized boolean anyMatch ( @NotNull final BiPredicate predicate )
    {
        boolean anyMatch = false;
        for ( final C component : components () )
        {
            if ( predicate.test ( component, get ( component ) ) )
            {
                anyMatch = true;
                break;
            }
        }
        return anyMatch;
    }

    /**
     * Returns whether or not all of the stored data pieces are accepted by specified {@link BiPredicate}.
     *
     * @param predicate {@link BiPredicate} for {@link JComponent} and data
     * @return {@code true} if all of the stored data pieces are accepted by specified {@link BiPredicate}, {@code false} otherwise
     */
    public synchronized boolean allMatch ( @NotNull final BiPredicate predicate )
    {
        boolean allMatch = true;
        for ( final C component : components () )
        {
            if ( !predicate.test ( component, get ( component ) ) )
            {
                allMatch = false;
                break;
            }
        }
        return allMatch;
    }

    /**
     * Returns whether or not none of the stored data pieces are accepted by specified {@link BiPredicate}.
     *
     * @param predicate {@link BiPredicate} for {@link JComponent} and data
     * @return {@code true} if none of the stored data pieces are accepted by specified {@link BiPredicate}, {@code false} otherwise
     */
    public synchronized boolean noneMatch ( @NotNull final BiPredicate predicate )
    {
        boolean noneMatch = true;
        for ( final C component : components () )
        {
            if ( predicate.test ( component, get ( component ) ) )
            {
                noneMatch = false;
                break;
            }
        }
        return noneMatch;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy