001package react4j; 002 003import java.util.HashMap; 004import java.util.Map; 005import java.util.Objects; 006import javax.annotation.Nonnull; 007import javax.annotation.Nullable; 008import static org.realityforge.braincheck.Guards.*; 009 010/** 011 * A global registry containing the react contexts that have been created. 012 * A react context is expected to be registered with a java type and an optional qualifier. 013 * This allows multiple contexts of the same type to be present in the global registry. 014 */ 015public final class Contexts 016{ 017 /** 018 * The default qualifier if not otherwise specified. 019 */ 020 @Nonnull 021 private static final String DEFAULT_QUALIFIER = ""; 022 /** 023 * The map containing all the contexts. See the class javadocs for further details. 024 */ 025 @Nonnull 026 private static final Map<Class<?>, Map<String, Context<?>>> c_contexts = new HashMap<>(); 027 /** 028 * Context provider. 029 */ 030 @Nonnull 031 private static ContextProvider c_contextProvider = new DefaultContextProvider(); 032 033 private Contexts() 034 { 035 } 036 037 /** 038 * Register the context with the specified type, an empty "" qualifier and a null default value. 039 * A context with matching type and qualifier parameters must not have been already registered. 040 * 041 * @param type the type of the context value. 042 * @param <T> the type of the context value. 043 * @see #register(Class, String, Object) 044 */ 045 public static <T> void register( @Nonnull final Class<T> type ) 046 { 047 register( type, DEFAULT_QUALIFIER, null ); 048 } 049 050 /** 051 * Register the context with the specified type, an empty "" qualifier and a default value. 052 * A context with matching type and qualifier parameters must not have been already registered. 053 * 054 * @param type the type of the context value. 055 * @param defaultValue the default value to return if no provider has been specified higher in the tree. 056 * @param <T> the type of the context value. 057 * @see #register(Class, String, Object) 058 */ 059 public static <T> void register( @Nonnull final Class<T> type, @Nullable final T defaultValue ) 060 { 061 register( type, DEFAULT_QUALIFIER, defaultValue ); 062 } 063 064 /** 065 * Register the context with the specified type, qualifier and a null default value. 066 * A context with matching type and qualifier parameters must not have been already registered. 067 * 068 * @param type the type of the context value. 069 * @param qualifier the qualifier to distinguish multiple instances of the the same type. 070 * @param <T> the type of the context value. 071 * @see #register(Class, String, Object) 072 */ 073 public static <T> void register( @Nonnull final Class<T> type, @Nonnull final String qualifier ) 074 { 075 register( type, qualifier, null ); 076 } 077 078 /** 079 * Register the context with the specified type, qualifier and a default value. 080 * A context with matching type and qualifier parameters must not have been already registered. 081 * 082 * @param type the type of the context value. 083 * @param qualifier the qualifier to distinguish multiple instances of the the same type. 084 * @param defaultValue the default value to return if no provider has been specified higher in the tree. 085 * @param <T> the type of the context value. 086 */ 087 public static <T> void register( @Nonnull final Class<T> type, 088 @Nonnull final String qualifier, 089 @Nullable final T defaultValue ) 090 { 091 if ( ReactConfig.shouldCheckInvariants() ) 092 { 093 apiInvariant( () -> !type.isPrimitive(), 094 () -> "Attempting to register primitive type " + type + 095 " in React context. Use the boxed type instead" ); 096 } 097 final Map<String, Context<?>> map = c_contexts.computeIfAbsent( type, t -> new HashMap<>() ); 098 if ( ReactConfig.shouldCheckInvariants() ) 099 { 100 apiInvariant( () -> !map.containsKey( qualifier ), 101 () -> "Attempting to register React context with type " + type + 102 " and qualifier '" + qualifier + "' but a context already exists with that type and name" ); 103 } 104 assert !map.containsKey( qualifier ); 105 map.put( qualifier, c_contextProvider.createContext( defaultValue ) ); 106 } 107 108 /** 109 * Return the context with the specified type and an empty "" qualifier. 110 * A context with matching type and qualifier parameters must have already been registered. 111 * 112 * @param type the type of the context value. 113 * @param <T> the type of the context value. 114 * @return the context. 115 * @see #get(Class, String) 116 */ 117 public static <T> Context<T> get( @Nonnull final Class<T> type ) 118 { 119 return get( type, DEFAULT_QUALIFIER ); 120 } 121 122 /** 123 * Return the context with the specified type and qualifier. 124 * A context with matching type and qualifier parameters must have already been registered. 125 * 126 * @param type the type of the context value. 127 * @param qualifier the qualifier to distinguish multiple instances of the the same type. 128 * @param <T> the type of the context value. 129 * @return the context. 130 * @see #get(Class) 131 */ 132 @SuppressWarnings( "unchecked" ) 133 public static <T> Context<T> get( @Nonnull final Class<T> type, @Nonnull final String qualifier ) 134 { 135 if ( ReactConfig.shouldCheckInvariants() ) 136 { 137 apiInvariant( () -> !type.isPrimitive(), 138 () -> "Attempting to access primitive type " + type + 139 " from the React context. Access using the boxed type instead" ); 140 } 141 final Map<String, Context<?>> map = c_contexts.get( type ); 142 if ( ReactConfig.shouldCheckInvariants() ) 143 { 144 apiInvariant( () -> null != map && map.containsKey( qualifier ), 145 () -> "Attempting to retrieve React context with type " + type + 146 " and qualifier '" + qualifier + "' but no such context exists with that type and name" ); 147 } 148 assert null != map; 149 assert map.containsKey( qualifier ); 150 return (Context<T>) map.get( qualifier ); 151 } 152 153 static void setContextProvider( @Nonnull final ContextProvider contextProvider ) 154 { 155 c_contextProvider = Objects.requireNonNull( contextProvider ); 156 } 157 158 static void clear() 159 { 160 c_contexts.clear(); 161 } 162 163 /** 164 * Interface used to provide context. This can be switched out as part of testing. 165 */ 166 interface ContextProvider 167 { 168 <T> Context<T> createContext( @Nullable T defaultValue ); 169 } 170 171 static class DefaultContextProvider 172 implements ContextProvider 173 { 174 @Override 175 public <T> Context<T> createContext( @Nullable final T defaultValue ) 176 { 177 return React.createContext( defaultValue ); 178 } 179 } 180}