001package jmri;
002
003import java.util.*;
004import java.util.concurrent.CopyOnWriteArrayList;
005
006/**
007 * A category of something.
008 * <P>
009 * Category was created for LogixNG actions and expressions but it can be used
010 * for everything in JMRI that needs "extendable enums".
011 * <P>
012 * This class is intended to be an Enum, but implemented as an abstract class
013 * to allow adding more categories later without needing to change this class.
014 * For example, external programs using JMRI as a lib might want to add their
015 * own categories.
016 *
017 * @author Daniel Bergqvist Copyright 2025
018 */
019public abstract class Category implements Comparable<Category> {
020
021    /**
022     * Other things.
023     */
024    public static final Other OTHER = new Other();
025
026    static {
027        // It's not often any item is added to this list so we use CopyOnWriteArrayList
028        _categories = new CopyOnWriteArrayList<>();
029        registerCategory(OTHER);
030    }
031
032    /**
033     * Get all the registered Categories
034     * @return a list of categories
035     */
036    public static List<Category> values() {
037        return Collections.unmodifiableList(_categories);
038    }
039
040    /**
041     * Register a category.
042     * There must not exist any category with either the name or the description
043     * of this category. Otherwise an IllegalArgumentException will be thrown.
044     * @param category the category
045     * @return the new category
046     * @throws IllegalArgumentException if the category already is registered.
047     */
048    public static Category registerCategory(Category category)
049            throws IllegalArgumentException {
050        for (Category c : _categories) {
051            if (c == category) {
052                // Don't register category twice. This can happen during tests.
053                return c;
054            }
055            if (c.equals(category)) {
056                throw new IllegalArgumentException(String.format(
057                        "Category '%s' with description '%s' is already registered",
058                        category._name, category._description));
059            }
060        }
061        _categories.add(category);
062        return category;
063    }
064
065
066    private static final List<Category> _categories;
067
068    private final String _name;
069    private final String _description;
070    private final int _order;
071
072
073    protected Category(String name, String description, int order) {
074        _name = name;
075        _description = description;
076        _order = order;
077    }
078
079    public final String name() {
080        return _name;
081    }
082
083    @Override
084    public final String toString() {
085        return _description;
086    }
087
088    public final int order() {
089        return _order;
090    }
091
092    @Override
093    public final boolean equals(Object o) {
094        if (o instanceof Category) {
095            Category c = (Category)o;
096            // We check during initialization that two categories isn't equal.
097            return _name.equals(c._name) || _description.equals(c._description);
098        }
099        return false;
100    }
101
102    @Override
103    public final int hashCode() {
104        return _description.hashCode();
105    }
106
107    @Override
108    public final int compareTo(Category c) {
109        if (_order < c.order()) return -1;
110        if (_order > c.order()) return 1;
111        return toString().compareTo(c.toString());
112    }
113
114
115    public static final class Other extends Category {
116
117        public Other() {
118            super("OTHER", Bundle.getMessage("CategoryOther"), Integer.MAX_VALUE);
119        }
120    }
121
122}