static void loadDefaultsFromProperties( List<Class<?>> lafClasses, List<FlatDefaultsAddon> addons,
		Consumer<Properties> intellijThemesHook, Properties additionalDefaults, boolean dark, UIDefaults defaults )
	{
		try {
			// temporary cache system colors while loading defaults,
			// which avoids that system color getter is invoked multiple times
			systemColorCache = (FlatLaf.getSystemColorGetter() != null) ? new HashMap<>() : null;

			// all properties files will be loaded into this map
			Properties properties = newUIProperties( dark );

			// load core properties files
			for( Class<?> lafClass : lafClasses ) {
				String propertiesName = '/' + lafClass.getName().replace( '.', '/' ) + ".properties";
				try( InputStream in = lafClass.getResourceAsStream( propertiesName ) ) {
					if( in != null )
						properties.load( in );
				}
			}

			// load properties from addons
			for( FlatDefaultsAddon addon : addons ) {
				for( Class<?> lafClass : lafClasses ) {
					try( InputStream in = addon.getDefaults( lafClass ) ) {
						if( in != null )
							properties.load( in );
					}
				}
			}

			// collect addon class loaders
			List<ClassLoader> addonClassLoaders = new ArrayList<>();
			for( FlatDefaultsAddon addon : addons ) {
				ClassLoader addonClassLoader = addon.getClass().getClassLoader();
				if( !addonClassLoaders.contains( addonClassLoader ) )
					addonClassLoaders.add( addonClassLoader );
			}

			// apply IntelliJ themes properties
			if( intellijThemesHook != null )
				intellijThemesHook.accept( properties );

			// load custom properties files (usually provided by applications)
			List<Object> customDefaultsSources = FlatLaf.getCustomDefaultsSources();
			int size = (customDefaultsSources != null) ? customDefaultsSources.size() : 0;
			for( int i = 0; i < size; i++ ) {
				Object source = customDefaultsSources.get( i );
				if( source instanceof String && i + 1 < size ) {
					// load from package in classloader
					String packageName = (String) source;
					ClassLoader classLoader = (ClassLoader) customDefaultsSources.get( ++i );

					// use class loader also for instantiating classes specified in values
					if( classLoader != null && !addonClassLoaders.contains( classLoader ) )
						addonClassLoaders.add( classLoader );

					packageName = packageName.replace( '.', '/' );
					if( classLoader == null )
						classLoader = FlatLaf.class.getClassLoader();

					for( Class<?> lafClass : lafClasses ) {
						String propertiesName = packageName + '/' + simpleClassName( lafClass ) + ".properties";
						try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) {
							if( in != null )
								properties.load( in );
						}
					}
				} else if( source instanceof URL ) {
					// load from package URL
					URL packageUrl = (URL) source;
					for( Class<?> lafClass : lafClasses ) {
						URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" );

						try( InputStream in = propertiesUrl.openStream() ) {
							properties.load( in );
						} catch( FileNotFoundException ex ) {
							// ignore
						}
					}
				} else if( source instanceof File ) {
					// load from folder
					File folder = (File) source;
					for( Class<?> lafClass : lafClasses ) {
						File propertiesFile = new File( folder, simpleClassName( lafClass ) + ".properties" );
						if( !propertiesFile.isFile() )
							continue;

						try( InputStream in = new FileInputStream( propertiesFile ) ) {
							properties.load( in );
						}
					}
				}
			}

			// add additional defaults
			if( additionalDefaults != null )
				properties.putAll( additionalDefaults );

			// get (and remove) wildcard replacements, which override all other defaults that end with same suffix
			HashMap<String, String> wildcards = new HashMap<>();
			Iterator<Entry<Object, Object>> it = properties.entrySet().iterator();
			while( it.hasNext() ) {
				Entry<Object, Object> e = it.next();
				String key = (String) e.getKey();
				if( key.startsWith( WILDCARD_PREFIX ) ) {
					wildcards.put( key.substring( WILDCARD_PREFIX.length() ), (String) e.getValue() );
					it.remove();
				}
			}

			// override UI defaults with wildcard replacements
			for( Object key : defaults.keySet() ) {
				int dot;
				if( !(key instanceof String) ||
					properties.containsKey( key ) ||
					(dot = ((String)key).lastIndexOf( '.' )) < 0 )
				  continue;

				String wildcardKey = ((String)key).substring( dot + 1 );
				String wildcardValue = wildcards.get( wildcardKey );
				if( wildcardValue != null )
					properties.put( key, wildcardValue );
			}

			Function<String, String> propertiesGetter = key -> {
				return properties.getProperty( key );
			};
			Function<String, String> resolver = value -> {
				return resolveValue( value, propertiesGetter );
			};

			// parse and add properties to UI defaults
			Map<String, String> variables = new HashMap<>( 50 );
			for( Map.Entry<Object, Object> e : properties.entrySet() ) {
				String key = (String) e.getKey();
				if( key.startsWith( VARIABLE_PREFIX ) ) {
					variables.put( key, (String) e.getValue() );
					continue;
				}

				String value = (String) e.getValue();
				try {
					value = resolveValue( value, propertiesGetter );
					defaults.put( key, parseValue( key, value, null, null, resolver, addonClassLoaders ) );
				} catch( RuntimeException ex ) {
					logParseError( key, value, ex, true );
				}
			}

			// remember variables in defaults to allow using them in styles
			defaults.put( KEY_VARIABLES, variables );

			// remember properties (for testing)
			if( FlatSystemProperties.getBoolean( KEY_PROPERTIES, false ) ) {
				Properties properties2 = new Properties();
				properties2.putAll( properties );
				for( Map.Entry<String, String> e : wildcards.entrySet() )
					properties2.put( WILDCARD_PREFIX + e.getKey(), e.getValue() );
				defaults.put( KEY_PROPERTIES, properties2 );
			}

			// clear/disable system color cache
			systemColorCache = null;
		} catch( IOException ex ) {
			LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load properties files.", ex );
		}
	}
Load UI defaults from properties files associated to Flat LaF classes and add to UI defaults.

Each class that extend the LaF class may have its own .properties file
in the same package as the class. Properties from superclasses are loaded
first to give subclasses a chance to override defaults.
E.g. if running FlatDarkLaf, then the FlatLaf.properties is loaded first
and FlatDarkLaf.properties loaded second.

@author Karl Tauber
flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java