All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.javafx.application.preferences.PlatformPreferences Maven / Gradle / Ivy

There is a newer version: 24-ea+19
Show newest version
/*
 * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.application.preferences;

import com.sun.javafx.binding.MapExpressionHelper;
import javafx.application.ColorScheme;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.collections.MapChangeListener;
import javafx.scene.paint.Color;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Contains the implementation of a read-only map of platform preferences.
 * 

* When the operating system signals that a preference has changed, the mappings are updated * by calling the {@link #update(Map)} method. */ public class PlatformPreferences extends AbstractMap implements Platform.Preferences { /** * Contains mappings from platform-specific keys to their types. This information is * used to catch misuse of typed getters even if the preferences map doesn't contain * the preference mapping at runtime. */ private final Map> platformKeys; /** * Contains mappings from platform-specific keys to well-known keys, which are used * in the implementation of the property-based API in {@link PreferenceProperties}. */ private final Map platformKeyMappings; /** * Contains the current set of effective preferences, i.e. the set of preferences that * we know to be the current state of the world, and are exposed to users of this map. */ private final Map effectivePreferences = new HashMap<>(); private final Map unmodifiableEffectivePreferences = Collections.unmodifiableMap(effectivePreferences); /** Contains the implementation of the property-based API. */ private final PreferenceProperties properties = new PreferenceProperties(this); private final List invalidationListeners = new CopyOnWriteArrayList<>(); private final List> mapChangeListeners = new CopyOnWriteArrayList<>(); /** * Initializes a new {@code PlatformPreferences} instance with the given platform-specific keys and key mappings. * * @param platformKeys the platform-specific keys and the types of their values * @param platformKeyMappings the platform-specific key mappings * @throws NullPointerException if {@code platformKeys} or {@code platformKeyMappings} is {@code null} or * contains {@code null} keys or values */ public PlatformPreferences(Map> platformKeys, Map platformKeyMappings) { this.platformKeys = Map.copyOf(platformKeys); this.platformKeyMappings = Map.copyOf(platformKeyMappings); } @Override public Set> entrySet() { return unmodifiableEffectivePreferences.entrySet(); } @Override public Object remove(Object key) { throw new UnsupportedOperationException(); } @Override public void addListener(InvalidationListener listener) { invalidationListeners.add(listener); } @Override public void removeListener(InvalidationListener listener) { invalidationListeners.remove(listener); } @Override public void addListener(MapChangeListener listener) { mapChangeListeners.add(listener); } @Override public void removeListener(MapChangeListener listener) { mapChangeListeners.remove(listener); } @Override public Optional getValue(String key, Class type) { Objects.requireNonNull(key, "key cannot be null"); Objects.requireNonNull(type, "type cannot be null"); Class platformType = platformKeys.get(key); Object value = effectivePreferences.get(key); if (platformType == null) { // Well-behaved toolkits shouldn't report values for keys that are not listed in the // platform key-type map. However, if they do, we need to respect the invariant that // Map.getValue(key, type) should only return an empty value if Map.get(key) would // return null. In all other cases we need to return the value if the cast succeeds. if (value != null) { if (type.isInstance(value)) { @SuppressWarnings("unchecked") T v = (T)value; return Optional.of(v); } throw new IllegalArgumentException( "Incompatible types: requested = " + type.getName() + ", actual = " + value.getClass().getName()); } return Optional.empty(); } // Check whether the declared platform type is convertible to the requested type. // This check validates that a casting conversion exists at all, even if we don't have a // value that we would need in order to use Class.isInstance to check if the conversion // succeeds at runtime. if (!isConvertible(platformType, type)) { throw new IllegalArgumentException( "Incompatible types: requested = " + type.getName() + ", actual = " + platformType.getName()); } if (value == null) { return Optional.empty(); } // The runtime type of the value might be a subtype of the platform type, which necessitates // checking whether the actual type is convertible to the requested type. if (!type.isInstance(value)) { throw new IllegalArgumentException( "Incompatible types: requested = " + type.getName() + ", actual = " + value.getClass().getName()); } @SuppressWarnings("unchecked") T v = (T)value; return Optional.of(v); } @Override public Optional getInteger(String key) { return getValue(key, Integer.class); } @Override public Optional getDouble(String key) { return getValue(key, Double.class); } @Override public Optional getBoolean(String key) { return getValue(key, Boolean.class); } @Override public Optional getString(String key) { return getValue(key, String.class); } @Override public Optional getColor(String key) { return getValue(key, Color.class); } @Override public ReadOnlyObjectProperty colorSchemeProperty() { return properties.colorSchemeProperty(); } @Override public ColorScheme getColorScheme() { return properties.getColorScheme(); } @Override public ReadOnlyObjectProperty backgroundColorProperty() { return properties.backgroundColorProperty(); } @Override public Color getBackgroundColor() { return properties.getBackgroundColor(); } @Override public ReadOnlyObjectProperty foregroundColorProperty() { return properties.foregroundColorProperty(); } @Override public Color getForegroundColor() { return properties.getForegroundColor(); } @Override public ReadOnlyObjectProperty accentColorProperty() { return properties.accentColorProperty(); } @Override public Color getAccentColor() { return properties.getAccentColor(); } /** * Updates this map of preferences with a new set of platform preferences. * The specified preferences may include all available preferences, or only the changed preferences. * The absence of a mapping in the specified preferences does not indicate that it should be removed; * instead, a key must be explicitly mapped to {@code null} to remove the mapping. Consequently, this * map will never contain {@code null} values. * * @param preferences the new preference mappings * @throws NullPointerException if {@code preferences} is {@code null} */ public void update(Map preferences) { Map currentPreferences = Map.copyOf(effectivePreferences); effectivePreferences.putAll(preferences); // Only fire change notifications if any preference has effectively changed. Map effectivelyChangedPreferences = ChangedValue.getEffectiveChanges(currentPreferences, effectivePreferences); // The new mappings may contain null values, which indicates that a mapping was removed. effectivePreferences.entrySet().removeIf(entry -> entry.getValue() == null); if (!effectivelyChangedPreferences.isEmpty()) { properties.update(effectivelyChangedPreferences, platformKeyMappings); fireValueChangedEvent(effectivelyChangedPreferences); } } private void fireValueChangedEvent(Map changedEntries) { invalidationListeners.forEach(listener -> listener.invalidated(this)); var change = new MapExpressionHelper.SimpleChange<>(this); for (Map.Entry entry : changedEntries.entrySet()) { Object oldValue = entry.getValue().oldValue(); Object newValue = entry.getValue().newValue(); if (oldValue == null && newValue != null) { change.setAdded(entry.getKey(), newValue); } else if (oldValue != null && newValue == null) { change.setRemoved(entry.getKey(), oldValue); } else { change.setPut(entry.getKey(), oldValue, newValue); } for (var listener : mapChangeListeners) { listener.onChanged(change); } } } /** * Determines whether a compile-time casting conversion exists from {@code source} to {@code target}. * This is an implementation of JLS 5.5.1 (Reference Type Casting). * * @param source the source type * @param target the target type * @return {@code true} if a casting conversion exists, {@code false} otherwise */ private boolean isConvertible(Class source, Class target) { if (source.isArray()) { return isArrayConvertible(source, target); } if (source.isInterface()) { return isInterfaceConvertible(source, target); } return isClassConvertible(source, target); } // Assuming S is a class type: private boolean isClassConvertible(Class source, Class target) { // If T is an interface type: // 1. If S is final, then S must implement T. // 2. If S is not final, the cast is always legal (because even if S does not // implement T, a subclass of S might). if (target.isInterface()) { return !Modifier.isFinal(source.getModifiers()) || target.isAssignableFrom(source); } // If T is an array type, then S must be the class Object. if (target.isArray()) { return source == Object.class; } // If T is a class type, then either S<:T, or T<:S. return target.isAssignableFrom(source) || source.isAssignableFrom(target); } // Assuming S is an interface type: private boolean isInterfaceConvertible(Class source, Class target) { // If T is an array type, then S must be the type Serializable or Cloneable. if (target.isArray()) { return source == Serializable.class || source == Cloneable.class; } // If T is not final, the cast is always legal (because even if S does not // implement T, a subclass of S might). if (!Modifier.isFinal(target.getModifiers())) { return true; } // If T is a class type that is final, then T must implement S. return source.isAssignableFrom(target); } // Assuming S is an array type SC[], that is, an array of components of type SC: private boolean isArrayConvertible(Class source, Class target) { // If T is an interface type, then it must be the type Serializable or Cloneable, // which are the only interfaces implemented by arrays. if (target.isInterface()) { return target == Serializable.class || target == Cloneable.class; } // If T is an array type TC[], that is, an array of components of type TC, // then one of the following must be true: // 1. TC and SC are the same primitive type // 2. TC and SC are reference types and type SC can undergo casting conversion to TC if (target.isArray()) { Class sourceComponentType = source.getComponentType(); Class targetComponentType = target.getComponentType(); if (sourceComponentType.isPrimitive() && targetComponentType.isPrimitive()) { return sourceComponentType == targetComponentType; } if (!sourceComponentType.isPrimitive() && !targetComponentType.isPrimitive()) { return isConvertible(sourceComponentType, targetComponentType); } return false; } // If T is a class type, then T must be Object because Object is the only // class type to which arrays can be assigned. return target == Object.class; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy