blob: 367dfb873a1e37908772f9497387d4eaf73ede2b [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2006, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ecommons.preferences.core;
import static org.eclipse.statet.jcommons.lang.NullDefaultLocation.ARRAY_CONTENTS;
import static org.eclipse.statet.jcommons.lang.NullDefaultLocation.FIELD;
import static org.eclipse.statet.jcommons.lang.NullDefaultLocation.PARAMETER;
import static org.eclipse.statet.jcommons.lang.NullDefaultLocation.RETURN_TYPE;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.RuleBasedCollator;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
/**
* Representing a single typed preference.
* <p>
* This package should help to manage the new preference system
* with scopes and nodes introduced with Eclipse 3.1.
*
* @param <T> the type, which this preference can store
* (normally, thats the same as the type property, but not have to be)
*/
@NonNullByDefault ({ PARAMETER, RETURN_TYPE, FIELD, ARRAY_CONTENTS })
public abstract class Preference<T> {
private static final Collator DEFAULT_COLLATOR= Collator.getInstance(Locale.ENGLISH);
static {
((RuleBasedCollator) DEFAULT_COLLATOR).setUpperCaseFirst(true);
}
/*-- Definition --------------------------------------------------------------*/
private final String qualifier;
private final String key;
protected Preference(final String qualifier, final String key) {
this.qualifier= qualifier;
this.key= key;
}
public String getQualifier() {
return this.qualifier;
}
public String getKey() {
return this.key;
}
public abstract Class<T> getUsageType();
/**
* Converts object of type T (this Preference is designed for) in the value for the PreferenceStore
*
* @param usageValue
* @return
*/
public abstract @Nullable String usage2Store(T usageValue);
/**
* Converts the value from the PreferenceStore into a object of type T (this Preference is designed for).
*
* @param storeValue
* @return
*/
public abstract T store2Usage(@Nullable String storeValue);
@Override
public String toString() {
return this.qualifier + '/' + this.key;
}
@Override
public int hashCode() {
return this.qualifier.hashCode() + this.key.hashCode();
}
@Override
public boolean equals(final @Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj != null && getClass() == obj.getClass()) {
final Preference<?> other= (Preference<?>) obj;
return (this.qualifier.equals(other.getQualifier())
&& this.key.equals(other.getQualifier()) );
}
return false;
}
/*-- Implementation for common types -----------------------------------------*/
/**
* Default separator for list preferences
*/
public static final char LIST_SEPARATOR_CHAR= ',';
protected static final Pattern LIST_SEPARATOR_PATTERN= Pattern.compile(","); //$NON-NLS-1$
/**
* Separator for file list preferences
*/
public static final char IS2_SEPARATOR_CHAR= '\u001e';
protected static final Pattern IS2_SEPARATOR_PATTERN= Pattern.compile("\u001e"); //$NON-NLS-1$
public static final char IS1_SEPARATOR_CHAR= '\u001f';
protected static final Pattern IS1_SEPARATOR_PATTERN= Pattern.compile("\u001f"); //$NON-NLS-1$
/**
* Default implementation for preferences of type String
*/
@NonNullByDefault
public static final class StringPref extends Preference<String> {
private final String defaultValue;
public StringPref(final String qualifier, final String key, final String defaultValue) {
super(qualifier, key);
this.defaultValue= defaultValue;
}
public StringPref(final String qualifier, final String key) {
this(qualifier, key, "");
}
@Override
public Class<String> getUsageType() {
return String.class;
}
@Override
public String store2Usage(final @Nullable String storeValue) {
if (storeValue != null) {
return storeValue;
}
return this.defaultValue;
}
@Override
public String usage2Store(final String usageValue) {
return usageValue;
}
}
/**
* Default implementation for preferences of type String
*
* Usage value can be null
*/
public static final class NullableStringPref extends Preference<@Nullable String> {
public NullableStringPref(final String qualifier, final String key) {
super(qualifier, key);
}
@Override
public Class<String> getUsageType() {
return String.class;
}
@Override
public @Nullable String store2Usage(final @Nullable String storeValue) {
return storeValue;
}
@Override
public @Nullable String usage2Store(final @Nullable String usageValue) {
return usageValue;
}
}
/**
* Default implementation for preferences of type Boolean/boolean
*/
@NonNullByDefault
public static final class BooleanPref extends Preference<Boolean> {
private final Boolean defaultValue;
public BooleanPref(final String qualifier, final String key, final Boolean defaultValue) {
super(qualifier, key);
this.defaultValue= defaultValue;
}
public BooleanPref(final String qualifier, final String key) {
this(qualifier, key, Boolean.FALSE);
}
@Override
public Class<Boolean> getUsageType() {
return Boolean.class;
}
@Override
public Boolean store2Usage(final @Nullable String storeValue) {
if (storeValue != null) {
return Boolean.valueOf(storeValue);
}
return this.defaultValue;
}
@Override
public String usage2Store(final Boolean usageValue) {
return usageValue.toString();
}
}
/**
* Default implementation for preferences of type Integer/int
*/
@NonNullByDefault
public static final class IntPref extends Preference<Integer> {
private final Integer defaultValue;
public IntPref(final String qualifier, final String key, final int defaultValue) {
super(qualifier, key);
this.defaultValue= Integer.valueOf(defaultValue);
}
public IntPref(final String qualifier, final String key) {
this(qualifier, key, 0);
}
@Override
public Class<Integer> getUsageType() {
return Integer.class;
}
@Override
public Integer store2Usage(final @Nullable String storeValue) {
if (storeValue != null) {
try {
return Integer.valueOf(storeValue);
}
catch (final NumberFormatException e) {}
}
return this.defaultValue;
}
@Override
public String usage2Store(final Integer usageValue) {
return usageValue.toString();
}
}
/**
* Default implementation for preferences of type Long/long
*/
@NonNullByDefault
public static final class LongPref extends Preference<Long> {
private final Long defaultValue;
public LongPref(final String qualifier, final String key, final long defaultValue) {
super(qualifier, key);
this.defaultValue= Long.valueOf(defaultValue);
}
public LongPref(final String qualifier, final String key) {
this(qualifier, key, 0L);
}
@Override
public Class<Long> getUsageType() {
return Long.class;
}
@Override
public Long store2Usage(final @Nullable String storeValue) {
if (storeValue != null) {
try {
return Long.valueOf(storeValue);
}
catch (final NumberFormatException e) {}
}
return this.defaultValue;
}
@Override
public String usage2Store(final Long usageValue) {
return usageValue.toString();
}
}
/**
* Default implementation for preferences of type Float/float
*/
@NonNullByDefault
public static final class FloatPref extends Preference<Float> {
private final Float defaultValue;
public FloatPref(final String qualifier, final String key, final float defaultValue) {
super(qualifier, key);
this.defaultValue= Float.valueOf(defaultValue);
}
public FloatPref(final String qualifier, final String key) {
this(qualifier, key, 0f);
}
@Override
public Class<Float> getUsageType() {
return Float.class;
}
@Override
public Float store2Usage(final @Nullable String storeValue) {
if (storeValue != null) {
try {
return Float.valueOf(storeValue);
}
catch (final NumberFormatException e) {}
}
return this.defaultValue;
}
@Override
public String usage2Store(final Float usageValue) {
return usageValue.toString();
}
}
/**
* Default implementation for preferences of type Double/double
*/
@NonNullByDefault
public static final class DoublePref extends Preference<Double> {
private final double defaultValue;
public DoublePref(final String qualifier, final String key, final double defaultValue) {
super(qualifier, key);
this.defaultValue= defaultValue;
}
public DoublePref(final String qualifier, final String key) {
this(qualifier, key, 0.0);
}
@Override
public Class<Double> getUsageType() {
return Double.class;
}
@Override
public Double store2Usage(final @Nullable String storeValue) {
if (storeValue != null) {
try {
return Double.valueOf(storeValue);
}
catch (final NumberFormatException e) {}
}
return this.defaultValue;
}
@Override
public String usage2Store(final Double usageValue) {
return usageValue.toString();
}
}
/**
* Default implementation for preferences of type Enum
*/
@NonNullByDefault
public static class EnumPref<E extends Enum<E>> extends Preference<E> {
private final Class<E> enumType;
private final E defaultValue;
public EnumPref(final String qualifier, final String key, final Class<E> enumType,
final E defaultValue) {
super(qualifier, key);
this.enumType= enumType;
this.defaultValue= defaultValue;
}
@Override
public Class<E> getUsageType() {
return this.enumType;
}
@Override
public E store2Usage(final @Nullable String storeValue) {
if (storeValue != null) {
try {
return Enum.valueOf(this.enumType, storeValue);
}
catch (final IllegalArgumentException e) {}
}
return this.defaultValue;
}
@Override
public @Nullable String usage2Store(final E usageValue) {
return usageValue.name();
}
}
/**
* Default implementation for preferences of type EnumSet.
*/
@NonNullByDefault
public static class EnumSetPref<E extends Enum<E>> extends Preference<EnumSet<E>> {
private final Class<E> enumType;
public EnumSetPref(final String qualifier, final String key, final Class<E> enumType) {
super(qualifier, key);
this.enumType= enumType;
}
@Override
public Class getUsageType() {
return EnumSet.class;
}
@Override
public EnumSet<E> store2Usage(final @Nullable String storeValue) {
final EnumSet<E> set= EnumSet.noneOf(this.enumType);
if (storeValue != null && !storeValue.isEmpty()) {
final String[] values= LIST_SEPARATOR_PATTERN.split(storeValue);
for (final String name : values) {
if (name.length() > 0) {
set.add(Enum.valueOf(this.enumType, name));
}
}
}
return set;
}
@Override
public String usage2Store(final EnumSet<E> usageValue) {
if (usageValue.isEmpty()) {
return ""; //$NON-NLS-1$
}
final StringBuilder sb= new StringBuilder();
for (final E e : usageValue) {
sb.append(e.name());
sb.append(LIST_SEPARATOR_CHAR);
}
return sb.substring(0, sb.length() - 1);
}
}
/**
* Default implementation for preferences of type List&lt;Enum&gt;
*/
@NonNullByDefault
public static class EnumListPref<E extends Enum<E>> extends Preference<List<E>> {
private final Class<E> enumType;
public EnumListPref(final String qualifier, final String key, final Class<E> enumType) {
super(qualifier, key);
this.enumType= enumType;
}
@Override
public Class getUsageType() {
return List.class;
}
@Override
public List<E> store2Usage(final @Nullable String storeValue) {
if (storeValue != null && !storeValue.isEmpty()) {
final String[] values= LIST_SEPARATOR_PATTERN.split(storeValue);
final ArrayList<@NonNull E> list= new ArrayList<>(values.length);
for (final String name : values) {
if (name.length() > 0) {
try {
list.add(Enum.valueOf(this.enumType, name));
}
catch (final IllegalArgumentException e) {}
}
}
return list;
}
return ImCollections.<E>emptyList();
}
@Override
public String usage2Store(final List<E> usageValue) {
if (usageValue.isEmpty()) {
return ""; //$NON-NLS-1$
}
final StringBuilder sb= new StringBuilder();
for (final E e : usageValue) {
sb.append(e.name());
sb.append(LIST_SEPARATOR_CHAR);
}
return sb.substring(0, sb.length() - 1);
}
}
/**
* Default implementation for preferences of type String-Array
*/
public static class StringArrayPref extends Preference<@NonNull String @NonNull[]> {
private static final String[] EMPTY_ARRAY= new String[0];
private final char separator;
public StringArrayPref(final String qualifier, final String key) {
super(qualifier, key);
this.separator= LIST_SEPARATOR_CHAR;
}
public StringArrayPref(final String qualifier, final String key, final char separator) {
super(qualifier, key);
this.separator= separator;
}
@Override
public Class<String[]> getUsageType() {
return String[].class;
}
@Override
public String[] store2Usage(final @Nullable String storeValue) {
if (storeValue != null && !storeValue.isEmpty()) {
switch (this.separator) {
case LIST_SEPARATOR_CHAR:
return LIST_SEPARATOR_PATTERN.split(storeValue);
case IS2_SEPARATOR_CHAR:
return IS2_SEPARATOR_PATTERN.split(storeValue);
default:
return ((this.separator == LIST_SEPARATOR_CHAR) ?
LIST_SEPARATOR_PATTERN :
Pattern.compile("\\Q"+this.separator+"\\E")).split(storeValue);
}
}
return EMPTY_ARRAY;
}
@Override
public String usage2Store(final String[] usageValue) {
if (usageValue.length == 0) {
return ""; //$NON-NLS-1$
}
final StringBuilder sb= new StringBuilder();
for (int i= 0; i < usageValue.length; i++) {
sb.append(usageValue[i]);
sb.append(this.separator);
}
return sb.substring(0, sb.length() - 1);
}
}
/**
* Default implementation for preferences of type String-Set
*/
public static class StringSetPref extends Preference<@NonNull Set<@NonNull String>> {
public StringSetPref(final String qualifier, final String key) {
super(qualifier, key);
}
@Override
public Class<Set<String>> getUsageType() {
final Object o= Set.class;
return (Class<Set<String>>) o;
}
@Override
public Set<@NonNull String> store2Usage(final @Nullable String storeValue) {
if (storeValue != null && !storeValue.isEmpty()) {
final String[] strings= LIST_SEPARATOR_PATTERN.split(storeValue);
return (strings.length <= 16) ?
ImCollections.newSet(strings) :
new HashSet<>(ImCollections.newList(strings));
}
return ImCollections.emptySet();
}
@Override
public String usage2Store(final Set<@NonNull String> usageValue) {
if (usageValue.isEmpty()) {
return ""; //$NON-NLS-1$
}
final StringBuilder sb= new StringBuilder();
final String[] array= usageValue.toArray(new String[usageValue.size()]);
Arrays.sort(array, DEFAULT_COLLATOR);
// { // Debug
// System.out.print(getKey());
// System.out.print("= \"");
// int first= 0;
// for (int i= 0; i < array.length; i++) {
// int thisFirst= Character.toUpperCase(array[i].charAt(0));
// if (thisFirst != first) {
// first= thisFirst;
// System.out.print("\"\n// \"");
// }
// System.out.print(array[i]);
// System.out.print(',');
// }
// System.out.println("\"");
// }
for (int i= 0; i < array.length; i++) {
sb.append(array[i]);
sb.append(',');
}
return sb.substring(0, sb.length() - 1);
}
}
}