IconDeclaration.java

package swingtree.api;

import com.google.errorprone.annotations.Immutable;
import swingtree.UI;
import swingtree.layout.Size;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import java.util.Objects;
import java.util.Optional;

/**
 *  Primarily designed to be implemented by an {@link Enum} type
 *  that declares a set of icon paths so that the enum instances
 *  can be used to identify and load
 *  (cached) icons across your application.
 *  <p>
 *  Here an example of how to use this interface:
 *  <pre>{@code
 * public enum Icons implements IconDeclaration
 * {
 *     ADD("icons/add.png"),
 *     REMOVE("icons/remove.png"),
 *     EDIT("icons/edit.png"),
 *     SAVE("icons/save.png"),
 *     CANCEL("icons/cancel.png"),
 *     REFRESH("icons/refresh.png");
 *     // ...
 *
 *     private final String path;
 *
 *     Icons(String path) { this.path = path; }
 *
 *     {@literal @}Override public String path() {
 *         return path;
 *     }
 * }
 * }</pre>
 *
 *  You may then use the enum instances
 *  in the SwingTree API just like you would use the {@link ImageIcon} class:
 *  <pre>{@code
 *  UI.button(Icons.ADD)
 *  .onClick( it -> vm.add() )
 *  }</pre>
 *
 *  The reason why enums should be used instead of Strings is
 *  so that you have some more compile time safety in your application!
 *  When it comes to resource loading Strings are brittle because they
 *  are susceptible to typos and refactoring mistakes.
 *  <p>
 *  Instances of this class are intended to be used as part of a view model
 *  instead of using the {@link Icon} or {@link ImageIcon} classes directly.
 *  <p>
 *  The reason for this is the fact that traditional Swing icons
 *  are often heavyweight objects whose loading may or may not succeed, and so they are
 *  not suitable for direct use in a property as part of your view model.
 *  Instead, you should use this {@link IconDeclaration} interface, which is a
 *  lightweight value object that merely models the resource location of the icon
 *  even if it is not yet loaded or even does not exist at all.
 *  <p>
 *  Not only does this make your view model more robust, but it also allows you
 *  to write unit tests much more easily, because you can now create instances
 *  where the icon may not be available at all, yet you can still test the
 *  behavior of your view model.
 */
@Immutable
public interface IconDeclaration
{
    /**
     *  The most important part of the identity of an
     *  icon declaration is the path to the icon resource.
     *  This path may be relative to the classpath or may be an absolute path.
     *
     * @return The path to the icon resource, which is relative
     *         to the classpath or may be an absolute path.
     */
    String path();

    /**
     *  The preferred size of the icon, which is not necessarily the actual size
     *  of the icon that is being loaded but rather the size that the icon should
     *  be scaled to when it is being loaded.
     *
     * @return The preferred size of the icon or {@link Size#unknown()} if the size is unspecified,
     *          which means that the icon should be loaded in its original size.
     */
    default Size size() {
        return Size.unknown();
    }

    /**
     *  This method is used to find the icon resource
     *  and load it as an {@link ImageIcon} instance
     *  wrapped in an {@link Optional},
     *  or return an empty {@link Optional} if the icon resource
     *  could not be found.
     *
     * @return An {@link Optional} that contains the {@link ImageIcon}
     *         if the icon resource was found, otherwise an empty {@link Optional}.
     */
    default Optional<ImageIcon> find() {
        return UI.findIcon(this);
    }

    /**
     *  Creates and returns an updated {@link IconDeclaration} instance
     *  with a new preferred size for the icon.
     *
     * @param size The preferred size of the icon in the form of a {@link Size} instance.
     * @return A new {@link IconDeclaration} instance with the same path
     *        but with the given size.
     */
    default IconDeclaration withSize( Size size ) {
        return IconDeclaration.of(size, path());
    }

    /**
     *  Creates and returns an updated {@link IconDeclaration} instance
     *  with a new preferred width and height for the icon.
     *
     * @param width The preferred width of the icon.
     * @param height The preferred height of the icon.
     * @return A new {@link IconDeclaration} instance with the same path
     *        but with the specified width and height as preferred size.
     */
    default IconDeclaration withSize( int width, int height ) {
        return IconDeclaration.of(Size.of(width, height), path());
    }

    /**
     *  Creates and returns an updated {@link IconDeclaration} instance
     *  with a new preferred width for the icon.
     *
     * @param width The preferred width of the icon.
     * @return A new {@link IconDeclaration} instance with the same path
     *        but with the specified width as preferred width.
     */
    default IconDeclaration withWidth( int width ) {
        return IconDeclaration.of(size().withWidth(width), path());
    }

    /**
     *  Allows you to create an updated {@link IconDeclaration} instance
     *  with a new preferred height for the icon.
     *
     * @param height The preferred height of the icon.
     * @return A new {@link IconDeclaration} instance with the same path
     *        but with the specified height as preferred height.
     */
    default IconDeclaration withHeight( int height ) {
        return IconDeclaration.of(size().withHeight(height), path());
    }

    /**
     *  This method is used to find an {@link ImageIcon} of a specific type
     *  and load and return it wrapped in an {@link Optional},
     *  or return an empty {@link Optional} if the icon resource could not be found.
     *
     * @param type The type of icon to find.
     * @return An {@link Optional} that contains the {@link ImageIcon} of the given type.
     * @param <T> The type of icon to find.
     */
    default <T extends ImageIcon> Optional<T> find( Class<T> type ) {
        return UI.findIcon(this).map(type::cast);
    }

    /**
     *  A factory method for creating an {@link IconDeclaration} instance
     *  from the provided path to the icon resource.
     *
     * @param path The path to the icon resource, which may be relative
     *             to the classpath or may be an absolute path.
     * @return A new {@link IconDeclaration} instance
     *        that represents the given icon resource as a constant.
     */
    static IconDeclaration of( String path ) {
        return of(Size.unknown(), path);
    }

    /**
     *  A factory method for creating an {@link IconDeclaration} instance
     *  from the provided path to the icon resource and the preferred size.
     *
     * @param size The preferred size of the icon.
     * @param path The path to the icon resource, which may be relative
     *             to the classpath or may be an absolute path.
     * @return A new {@link IconDeclaration} instance
     *        that represents the given icon resource as a constant.
     */
    static IconDeclaration of( Size size, String path ) {
        Objects.requireNonNull(size);
        Objects.requireNonNull(path);
        return new BasicIconDeclaration(size, path);
    }
}