/*******************************************************************************
* Copyright 2014 Analog Devices, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
package com.analog.lyric.options;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.jcip.annotations.Immutable;
import com.analog.lyric.collect.BitSetUtil;
import com.analog.lyric.collect.ReleasableIterator;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* An immutable map of option keys declared in a single class.
* <p>
* This provides a way to look up related option keys by their identifier name. However,
* when working with keys that can declared in multiple classes, you will typically want to make use
* of an {@link OptionRegistry} for looking up options by name.
* <p>
* This object maps simple identifier strings to {@link IOptionKey} instances declared in the
* {@link #declaringClass()} associated with this object. This will include mappings for
* every publicly accessible static final fields of {@link IOptionKey} that refer to
* a "canonical instance" of the key (an instance for which {@link OptionKey#getCanonicalInstance} returns
* the instance itself).
* <p>
* Instances are obtained using {@link #declaredInClass(Class)}.
* @since 0.07
* @author Christopher Barber
*/
@Immutable
public final class OptionKeys extends AbstractMap<String, IOptionKey<?>>
{
/*------------
* Constants
*/
private static final int publicStaticFinal = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL;
/*--------------
* Static state
*/
/**
* Used to cache value with declaring class.
*/
private static ClassValue<OptionKeys> optionKeysClassValue = new DeclaredOptionKeys();
/*-------------
* Local state
*/
private final Class<?> _declaringClass;
/**
* The keys sorted in order of their {@link IOptionKey#name} attribute.
*/
private final List<IOptionKey<?>> _keys;
private final Map<String,IOptionKey<?>> _nameToKey;
private final int _hashCode;
/*--------------
* Construction
*/
OptionKeys(Class<?> declaringClass)
{
_declaringClass = declaringClass;
_nameToKey = new HashMap<String,IOptionKey<?>>();
final List<IOptionKey<?>> keys = new ArrayList<IOptionKey<?>>();
try
{
for (Field field : declaringClass.getDeclaredFields())
{
if (IOptionKey.class.isAssignableFrom(field.getType()))
{
final int modifiers = field.getModifiers();
if (BitSetUtil.isMaskSet(modifiers, publicStaticFinal))
{
// public static final
IOptionKey<?> option = (IOptionKey<?>)field.get(declaringClass);
if (option == OptionKey.getCanonicalInstance(option))
{
String name = field.getName();
_nameToKey.put(name, option);
if (name == option.name())
{
// If names match, then this is the canonical instance of the name/key mapping.
keys.add(option);
}
}
}
}
}
}
catch (IllegalAccessException ex)
{
// This shouldn't happen for public fields, but turn into RuntimeException if it does
throw new RuntimeException(ex);
}
Collections.sort(keys, IOptionKey.CompareByName.INSTANCE);
_keys = Collections.unmodifiableList(keys);
_hashCode = _nameToKey.hashCode();
}
/*----------------
* Static methods
*/
/**
* Option keys declared publicly in given class.
* <p>
* This method will lazily compute the list using reflection the first time
* it is invoked for a given class and will subsequently cache and return the
* same object.
* <p>
* @param declaringClass
* @since 0.07
*/
public static OptionKeys declaredInClass(Class<?> declaringClass)
{
return optionKeysClassValue.get(declaringClass);
}
/**
* Iterates over {@link OptionKeys} for classes in hierarchy.
* <p>
* Returns an iterator that produces the {@link OptionKeys} for the classes starting with the
* {@code declaringClass} and on through the chain of superclasses
* upto but not including {@link OptionKeyDeclarer}.
* <p>
* @param declaringClass a subclass of {@link OptionKeyDeclarer}.
* @since 0.07
*/
public static ReleasableIterator<OptionKeys> declaredInHierarchy(Class<? extends OptionKeyDeclarer> declaringClass)
{
return HierarchicalOptionKeyIterator.create(declaringClass);
}
/*----------------
* Object methods
*/
@Override
public int hashCode()
{
// The object is immutable, so there is no reason to compute the hash code every time.
return _hashCode;
}
/*--------------
* Map methods
*/
@Override
public OptionKeys clone()
{
// There is only one instance per declaring class so this is just the same object.
return this;
}
@NonNullByDefault(false)
@Override
public boolean containsKey(Object name)
{
return _nameToKey.containsKey(name);
}
@NonNullByDefault(false)
@Override
public boolean containsValue(Object value)
{
return value instanceof IOptionKey<?> && get(((IOptionKey<?>)value).name()) == value;
}
@Override
public Set<Map.Entry<String, IOptionKey<?>>> entrySet()
{
return Collections.unmodifiableMap(_nameToKey).entrySet();
}
@Override
public Set<String> keySet()
{
return Collections.unmodifiableSet(_nameToKey.keySet());
}
/**
* The number of name to {@link IOptionKey} mappings in this object.
* <p>
* Note that if there can be more than one name mapped to the same key (although only
* one will be the canonical one), so this number can be higher than {@link #uniqueSize()}.
*/
@Override
public int size()
{
return _nameToKey.size();
}
/**
* Returns an immutable list of unique option keys in the map.
* <p>
* The list is backed by an array, so random access is fast, and
* keys are ordered by their {@linkplain IOptionKey#name name}.
*/
@Override
public List<IOptionKey<?>> values()
{
return _keys;
}
/*--------------------
* OptionKeys methods
*/
/**
* The class containing the declarations of these option keys.
*
* @since 0.07
*/
public Class<?> declaringClass()
{
return _declaringClass;
}
/**
* @param name specifies the value of the option key's {@linkplain IOptionKey#name() name} attribute.
* @return option key with given name or else null.
* @since 0.07
*/
public @Nullable IOptionKey<?> get(String name)
{
return _nameToKey.get(name);
}
/**
* The number of unique keys in this object.
* <p>
* This is the same as the size of the {@link #values()} list and is the same
* as the number of mappings for which the name matches the key's {@linkplain IOptionKey#name name}.
* @since 0.07
*/
public int uniqueSize()
{
return _keys.size();
}
/*------------------------
* Private implementation
*/
private static class DeclaredOptionKeys extends ClassValue<OptionKeys>
{
@NonNullByDefault(false)
@Override
protected OptionKeys computeValue(Class<?> declaringClass)
{
return new OptionKeys(declaringClass);
}
}
}