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}