/*
* Copyright (c) 1996, 2011, 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 java.beans;
import com.sun.beans.TypeResolver;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map.Entry;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.SideEffectFree;
/**
* The FeatureDescriptor class is the common baseclass for PropertyDescriptor,
* EventSetDescriptor, and MethodDescriptor, etc.
* <p>
* It supports some common information that can be set and retrieved for
* any of the introspection descriptors.
* <p>
* In addition it provides an extension mechanism so that arbitrary
* attribute/value pairs can be associated with a design feature.
*/
public class FeatureDescriptor {
private static final String TRANSIENT = "transient";
private @Nullable Reference<? extends Class<?>> classRef;
/**
* Constructs a <code>FeatureDescriptor</code>.
*/
public FeatureDescriptor() {
}
/**
* Gets the programmatic name of this feature.
*
* @return The programmatic name of the property/method/event
*/
public String getName() {
return name;
}
/**
* Sets the programmatic name of this feature.
*
* @param name The programmatic name of the property/method/event
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets the localized display name of this feature.
*
* @return The localized display name for the property/method/event.
* This defaults to the same as its programmatic name from getName.
*/
public String getDisplayName() {
if (displayName == null) {
return getName();
}
return displayName;
}
/**
* Sets the localized display name of this feature.
*
* @param displayName The localized display name for the
* property/method/event.
*/
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
/**
* The "expert" flag is used to distinguish between those features that are
* intended for expert users from those that are intended for normal users.
*
* @return True if this feature is intended for use by experts only.
*/
public boolean isExpert() {
return expert;
}
/**
* The "expert" flag is used to distinguish between features that are
* intended for expert users from those that are intended for normal users.
*
* @param expert True if this feature is intended for use by experts only.
*/
public void setExpert(boolean expert) {
this.expert = expert;
}
/**
* The "hidden" flag is used to identify features that are intended only
* for tool use, and which should not be exposed to humans.
*
* @return True if this feature should be hidden from human users.
*/
public boolean isHidden() {
return hidden;
}
/**
* The "hidden" flag is used to identify features that are intended only
* for tool use, and which should not be exposed to humans.
*
* @param hidden True if this feature should be hidden from human users.
*/
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
/**
* The "preferred" flag is used to identify features that are particularly
* important for presenting to humans.
*
* @return True if this feature should be preferentially shown to human users.
*/
public boolean isPreferred() {
return preferred;
}
/**
* The "preferred" flag is used to identify features that are particularly
* important for presenting to humans.
*
* @param preferred True if this feature should be preferentially shown
* to human users.
*/
public void setPreferred(boolean preferred) {
this.preferred = preferred;
}
/**
* Gets the short description of this feature.
*
* @return A localized short description associated with this
* property/method/event. This defaults to be the display name.
*/
public String getShortDescription() {
if (shortDescription == null) {
return getDisplayName();
}
return shortDescription;
}
/**
* You can associate a short descriptive string with a feature. Normally
* these descriptive strings should be less than about 40 characters.
* @param text A (localized) short description to be associated with
* this property/method/event.
*/
public void setShortDescription(String text) {
shortDescription = text;
}
/**
* Associate a named attribute with this feature.
*
* @param attributeName The locale-independent name of the attribute
* @param value The value.
*/
public void setValue(String attributeName, Object value) {
getTable().put(attributeName, value);
}
/**
* Retrieve a named attribute with this feature.
*
* @param attributeName The locale-independent name of the attribute
* @return The value of the attribute. May be null if
* the attribute is unknown.
*/
public @Nullable Object getValue(String attributeName) {
return (this.table != null)
? this.table.get(attributeName)
: null;
}
/**
* Gets an enumeration of the locale-independent names of this
* feature.
*
* @return An enumeration of the locale-independent names of any
* attributes that have been registered with setValue.
*/
public Enumeration<String> attributeNames() {
return getTable().keys();
}
/**
* Package-private constructor,
* Merge information from two FeatureDescriptors.
* The merged hidden and expert flags are formed by or-ing the values.
* In the event of other conflicts, the second argument (y) is
* given priority over the first argument (x).
*
* @param x The first (lower priority) MethodDescriptor
* @param y The second (higher priority) MethodDescriptor
*/
FeatureDescriptor(FeatureDescriptor x, FeatureDescriptor y) {
expert = x.expert | y.expert;
hidden = x.hidden | y.hidden;
preferred = x.preferred | y.preferred;
name = y.name;
shortDescription = x.shortDescription;
if (y.shortDescription != null) {
shortDescription = y.shortDescription;
}
displayName = x.displayName;
if (y.displayName != null) {
displayName = y.displayName;
}
classRef = x.classRef;
if (y.classRef != null) {
classRef = y.classRef;
}
addTable(x.table);
addTable(y.table);
}
/*
* Package-private dup constructor
* This must isolate the new object from any changes to the old object.
*/
FeatureDescriptor(FeatureDescriptor old) {
expert = old.expert;
hidden = old.hidden;
preferred = old.preferred;
name = old.name;
shortDescription = old.shortDescription;
displayName = old.displayName;
classRef = old.classRef;
addTable(old.table);
}
/**
* Copies all values from the specified attribute table.
* If some attribute is exist its value should be overridden.
*
* @param table the attribute table with new values
*/
private void addTable(Hashtable<String, Object> table) {
if ((table != null) && !table.isEmpty()) {
getTable().putAll(table);
}
}
/**
* Returns the initialized attribute table.
*
* @return the initialized attribute table
*/
@EnsuresNonNull("this.table")
private Hashtable<String, Object> getTable() {
if (this.table == null) {
this.table = new Hashtable<>();
}
return this.table;
}
/**
* Sets the "transient" attribute according to the annotation.
* If the "transient" attribute is already set
* it should not be changed.
*
* @param annotation the annotation of the element of the feature
*/
void setTransient(@Nullable Transient annotation) {
if ((annotation != null) && (null == getValue(TRANSIENT))) {
setValue(TRANSIENT, annotation.value());
}
}
/**
* Indicates whether the feature is transient.
*
* @return {@code true} if the feature is transient,
* {@code false} otherwise
*/
boolean isTransient() {
Object value = getValue(TRANSIENT);
return (value instanceof Boolean)
? (Boolean) value
: false;
}
// Package private methods for recreating the weak/soft referent
// Wildcard commented out for Java 7 compiler; commenting not needed for Java 8.
void setClass0(Class/*<?>*/ cls) {
this.classRef = getWeakReference((Class<?>) cls);
}
@Nullable Class<?> getClass0() {
return (this.classRef != null)
? this.classRef.get()
: null;
}
/**
* Creates a new soft reference that refers to the given object.
*
* @return a new soft reference or <code>null</code> if object is <code>null</code>
*
* @see SoftReference
*/
static <T> @Nullable Reference<T> getSoftReference(@Nullable T object) {
return (object != null)
? new SoftReference<>(object)
: null;
}
/**
* Creates a new weak reference that refers to the given object.
*
* @return a new weak reference or <code>null</code> if object is <code>null</code>
*
* @see WeakReference
*/
static <T> @Nullable Reference<T> getWeakReference(@Nullable T object) {
return (object != null)
? new WeakReference<>(object)
: null;
}
/**
* Resolves the return type of the method.
*
* @param base the class that contains the method in the hierarchy
* @param method the object that represents the method
* @return a class identifying the return type of the method
*
* @see Method#getGenericReturnType
* @see Method#getReturnType
*/
static Class<?> getReturnType(@Nullable Class<?> base, Method method) {
if (base == null) {
base = method.getDeclaringClass();
}
return TypeResolver.erase(TypeResolver.resolveInClass(base, method.getGenericReturnType()));
}
/**
* Resolves the parameter types of the method.
*
* @param base the class that contains the method in the hierarchy
* @param method the object that represents the method
* @return an array of classes identifying the parameter types of the method
*
* @see Method#getGenericParameterTypes
* @see Method#getParameterTypes
*/
static Class<?>[] getParameterTypes(@Nullable Class<?> base, Method method) {
if (base == null) {
base = method.getDeclaringClass();
}
return TypeResolver.erase(TypeResolver.resolveInClass(base, method.getGenericParameterTypes()));
}
private boolean expert;
private boolean hidden;
private boolean preferred;
private @Nullable String shortDescription;
private String name;
private @Nullable String displayName;
private @Nullable Hashtable<String, Object> table;
/**
* Returns a string representation of the object.
*
* @return a string representation of the object
*
* @since 1.7
*/
public String toString() {
StringBuilder sb = new StringBuilder(getClass().getName());
sb.append("[name=").append(this.name);
appendTo(sb, "displayName", this.displayName);
appendTo(sb, "shortDescription", this.shortDescription);
appendTo(sb, "preferred", this.preferred);
appendTo(sb, "hidden", this.hidden);
appendTo(sb, "expert", this.expert);
if ((this.table != null) && !this.table.isEmpty()) {
sb.append("; values={");
for (Entry<String, Object> entry : this.table.entrySet()) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("; ");
}
sb.setLength(sb.length() - 2);
sb.append("}");
}
appendTo(sb);
return sb.append("]").toString();
}
void appendTo(StringBuilder sb) {
}
static void appendTo(StringBuilder sb, String name, @Nullable Reference<?> reference) {
if (reference != null) {
appendTo(sb, name, reference.get());
}
}
static void appendTo(StringBuilder sb, String name, @Nullable Object value) {
if (value != null) {
sb.append("; ").append(name).append("=").append(value);
}
}
static void appendTo(StringBuilder sb, String name, boolean value) {
if (value) {
sb.append("; ").append(name);
}
}
}