/*
 
* 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;

/**
 
* 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 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 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
     
*/

    
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(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

    
void setClass0(Class<?> cls) {
        
this.classRef = getWeakReference(cls);
    
}

    
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> Reference<T> getSoftReference(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> Reference<T> getWeakReference(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(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(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 String shortDescription;
    
private String name;
    
private String displayName;
    
private 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, Reference<?> reference) {
        
if (reference != null) {
            
appendTo(sb, name, reference.get());
        
}
    
}

    
static void appendTo(StringBuilder sb, String name, 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);
        
}
    
}
}