/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.appserv.management.util.jmx;

import java.io.IOException;
import java.io.Serializable;

import java.util.Set;
import java.util.List;
import java.util.Collection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.Hashtable;

import java.lang.reflect.Method;

import javax.management.*;

import com.sun.appserv.management.util.stringifier.ArrayStringifier;
import com.sun.appserv.management.util.misc.ArrayUtil;
import com.sun.appserv.management.util.misc.GSetUtil;
import com.sun.appserv.management.util.misc.MapUtil;
import com.sun.appserv.management.util.misc.TypeCast;
import com.sun.appserv.management.util.misc.RegexUtil;

import com.sun.appserv.management.util.jmx.stringifier.MBeanOperationInfoStringifier;
import com.sun.appserv.management.util.jmx.stringifier.MBeanFeatureInfoStringifierOptions;
import com.sun.appserv.management.util.jmx.stringifier.MBeanAttributeInfoStringifier;
import com.sun.appserv.management.util.jmx.stringifier.ObjectNameStringifier;
import com.sun.appserv.management.util.misc.ArrayConversion;

import com.sun.appserv.management.util.stringifier.SmartStringifier;



/**
 */
public final class JMXUtil
{
	
	public final static String	MBEAN_SERVER_DELEGATE	=
							"JMImplementation:type=MBeanServerDelegate";
	
	public final static String	MBEAN_SERVER_ID_ATTRIBUTE_NAME	=
							"MBeanServerId";
							
	/**
		The wilcard property at the end of an ObjectName which indicates
		that it's an ObjectName pattern.
	 */
	public final static String WILD_PROP		= ",*";
	
	/**
		The wilcard property at the end of an ObjectName which indicates
		that all properties should be matched.
	 */
	public final static String WILD_ALL			= "*";
	
	
		public static ObjectName
	getMBeanServerDelegateObjectName()
	{
		return( newObjectName( "JMImplementation:type=MBeanServerDelegate" ) );
	}
	
		public static void
	listenToMBeanServerDelegate(
		final MBeanServerConnection	conn,
		final NotificationListener	listener,
		final NotificationFilter	filter,
		final Object				handback)
		throws IOException, InstanceNotFoundException
	{
		conn.addNotificationListener(
			getMBeanServerDelegateObjectName(), listener, filter, handback );
	}
	
		public static String
	getMBeanServerID( final MBeanServerConnection conn )
		throws IOException,
		ReflectionException, InstanceNotFoundException, AttributeNotFoundException,
		MBeanException
	{
		return( (String)conn.getAttribute( getMBeanServerDelegateObjectName(),
					MBEAN_SERVER_ID_ATTRIBUTE_NAME ) );
	}
	
	
	/**
		Create a new ObjectName, caller is guaranteeing that the name is
		well-formed (a RuntimeException will be thrown if not). This avoids
		having to catch all sorts of JMX exceptions.
		<p>
		<b>Do not call this method if there is not 100% certainty of a well-formed name.</b>
	 */
		public static ObjectName
	newObjectName( final String name )
	{
		try
		{
			return( new ObjectName( name ) ); 
		}
		catch( Exception e )
		{
			throw new RuntimeException( e.getMessage(), e );
		}
	}
	
		public static ObjectName
	newObjectName(
		final ObjectName	objectName,
		final String 		props )
	{
		final String	domain	= objectName.getDomain();
		final String	existingProps	= objectName.getKeyPropertyListString();
		final String	allProps	= concatenateProps( existingProps, props );
		return( newObjectName( domain, allProps ) ); 
	}
	
		public static ObjectName
	newObjectName(
		final String	domain,
		final String	props )
	{
		return( newObjectName( domain + ":" + props ) );
	}
	
	/**
		Build an ObjectName pattern.
		
		@param domain	the JMX domain
		@param props	properties of the ObjectName
	 */
		public static ObjectName
	newObjectNamePattern(
		final String domain,
		final String props )
	{
		String	actualProps	= null;
		
		if ( props.endsWith( JMXUtil.WILD_PROP ) ||
			props.equals( JMXUtil.WILD_ALL ) )
		{
			actualProps	= props;
		}
		else if ( props.length() == 0 )
		{
			actualProps	= "*";
		}
		else
		{
			actualProps	= props + WILD_PROP;
		}
		
		return( newObjectName( domain + ":" + actualProps ) );
	}
	
	
	/**
		Build an ObjectName pattern.
		
		@param domain	the JMX domain
		@param props	properties of the ObjectName
	 */
		public static ObjectName
	newObjectNamePattern(
		final String domain,
		final Map<String,String>	props )
	{
		final String	propsString	= mapToProps( props );
		
		return( JMXUtil.newObjectNamePattern( domain, propsString ) );
	}
	
		public static String
	mapToProps( final Map<String,String>	propsMap )
	{
		return( MapUtil.toString( propsMap, "," ) );
	}
	
		public static ObjectName
	removeProperty(
		final ObjectName	objectName,
		final String		key )
	{
		ObjectName	nameWithoutKey	= objectName;
		
		if ( objectName.getKeyProperty( key ) != null )
		{
			final String    domain	= objectName.getDomain();
			final Hashtable<String,String> props =
			    TypeCast.asHashtable( objectName.getKeyPropertyList() );
			
			props.remove( key );
			
			if ( objectName.isPropertyPattern() )
			{
				final String	propsString	= mapToProps( props );
			
				nameWithoutKey	= newObjectNamePattern( domain,
						nameWithoutKey.getKeyPropertyListString() );
			}
			else
			{
				try
				{
				    nameWithoutKey	= new ObjectName( domain, props );
				}
				catch( Exception e )
				{
				    throw new RuntimeException( e );
				}
			}
		}
		
		return( nameWithoutKey );
	}
	
	
		private 
	JMXUtil()
	{
		// disallow;
	}
	
	public static final String	GET	= "get";
	public static final String	SET	= "set";
	public static final String	IS	= "is";
	
		public static String
	makeProp( String name, String value )
	{
		return( name + "=" + value );
	}
	
		public static String
	concatenateProps( String props1, String props2 )
	{
		String	result	= null;
		
		if ( props1.length() == 0 )
		{
			result	= props2;
		}
		else if ( props2.length() == 0 )
		{
			result	= props1;
		}
		else
		{
			result	= props1 + "," + props2;
		}
		
		return( result );
	}
	
		public static String
	concatenateProps( String props1, String props2, String props3 )
	{
		return( concatenateProps( concatenateProps( props1, props2), props3) );
	}
	
	
	
	/**
		Convert a Set of ObjectName into an array
		
		@param objectNameSet	a Set of ObjectName
		@return an ObjectName[]
	 */
		public static ObjectName[]
	objectNameSetToArray( final Set<ObjectName> objectNameSet )
	{
		final ObjectName[]	objectNames	= new ObjectName[ objectNameSet.size() ];
		objectNameSet.toArray( objectNames );
		
		return( objectNames );
	}
	
	
	/**
		@param key	the property name, within the ObjectName
		@param objectNames	
		@return values from each ObjectName
	 */
		public static String[]
	getKeyProperty( String key, ObjectName[] objectNames )
	{
		final String[]	values	= new String[ objectNames.length ];
		
		for( int i = 0; i < objectNames.length; ++i )
		{
			values[ i ]	= objectNames[ i ].getKeyProperty( key );
		}
		
		return( values );
	}
	
	/**
		@param objectName
		@param key	
		@return an ObjectName property with the specified key
	 */
		public static String
	getProp(
		final ObjectName	objectName,
		final String		key )
	{
		final String	value	= objectName.getKeyProperty( key );
		if ( value == null )
		{
			return( null );
		}
		
		return( makeProp( key, value ) );
	}
	
		public static String
	getProps(
		final ObjectName	objectName,
		final Set<String>   propKeys )
	{
		return( getProps( objectName, propKeys, false ) );
	}

		public static String
	getProps(
		final ObjectName	objectName,
		final Set<String>	propKeys,
		final boolean		ignoreMissing )
	{
		String	props	= "";
		
		final Iterator	iter	= propKeys.iterator();
		while( iter.hasNext() )
		{
			final String	key	= (String)iter.next();
			
			final String	pair	= getProp( objectName, key );
			if ( pair != null )
			{
				props	= concatenateProps( props, pair );
			}
			else if ( ! ignoreMissing )
			{
				throw new IllegalArgumentException(
					"key not found: " + key + " in " + objectName );
			}
		}
		return( props );
	}
	
	/**
		@param key	the property name, within the ObjectName
		@param objectNameSet
		@return values from each ObjectName
	 */
		public static String[]
	getKeyProperty( String key, Set<ObjectName> objectNameSet )
	{
		final ObjectName[]	objectNames	=
			JMXUtil.objectNameSetToArray( objectNameSet );
		
		return( getKeyProperty( key, objectNames ) );
	}
	

	/**
		@param key	the property name, within the ObjectName
		@param objectNameSet
		@return values from each ObjectName
	 */
		public static Set<String>
	getKeyPropertySet( String key, Set<ObjectName> objectNameSet )
	{
		final ObjectName[]	objectNames	=
			JMXUtil.objectNameSetToArray( objectNameSet );
		
		final String[]	values	= getKeyProperty( key, objectNames );
		
		return( ArrayConversion.arrayToSet( values ) );
	}
	
	/**
		Find the first key that is present in the ObjectName
		
		@param candidateKeys
		@param objectName
		@return first key present in the ObjectName
	 */
		public static String
	findKey(
		final Set<String>   candidateKeys,
		final ObjectName	objectName )
	{
		final Iterator	iter	= candidateKeys.iterator();
		
		String	match	= null;
		
		while ( iter.hasNext() )
		{
			final String	key	= (String)iter.next();
			
			if ( objectName.getKeyProperty( key ) != null )
			{
				match	= key;
				break;
			}
		}
		
		return( match );
	}
	
	
	/**
		Find all ObjectName(s) that contains the associated key and value
		
		@param objectNames
		@param propertyKey
		@param propertyValue
		@return Set of all ObjectName that match
	 */
		public static Set<ObjectName>
	findByProperty(
		final Set<ObjectName>   objectNames,
		final String	propertyKey,
		final String	propertyValue )
	{
		final Set<ObjectName>	result	= new HashSet<ObjectName>();
		
		final Iterator	iter	= objectNames.iterator();
		while ( iter.hasNext() )
		{
			final ObjectName	objectName	= (ObjectName)iter.next();
			
			final String	value	= objectName.getKeyProperty( propertyKey );
			if ( propertyValue.equals( value ) )
			{
				result.add( objectName );
			}
		}
		
		return( result );
	}
	
	/**
		Change or add a key property in an ObjectName.
	 */
		public static ObjectName
	setKeyProperty( final ObjectName objectName, final String key, final String value )
	{
		final String	domain	= objectName.getDomain();
		final Hashtable<String,String> props	= TypeCast.asHashtable( objectName.getKeyPropertyList() );
		
		props.put( key, value );
		
		ObjectName	newObjectName	= null;
		try
		{
			newObjectName	= new ObjectName( domain, props );
		}
		catch( MalformedObjectNameException e )
		{
			throw new RuntimeException( e );
		}
		
		return( newObjectName  );
	}
	
		private static String
	toString( Object o )
	{
		return( SmartStringifier.toString( o ) );
	}
	
		public static void
	unregisterAll( final MBeanServerConnection conn, final Set<ObjectName> allNames)
		throws IOException, MalformedObjectNameException, MBeanRegistrationException
	{
		for( final ObjectName name : allNames )
		{
			try
			{
				conn.unregisterMBean( name );
			}
			catch( Exception e )
			{
				// OK, gone, it objects, etc
			}
		}
	}
	
		public static void
	unregisterAll( final MBeanServerConnection conn )
		throws IOException, MalformedObjectNameException, MBeanRegistrationException
	{
		unregisterAll( conn, queryNames( conn, new ObjectName( "*:*" ), null ) );
	}
	
		public static String[]
	getAllAttributeNames(
		final MBeanServerConnection	conn,
		final ObjectName			objectName )
		throws IOException,
			ReflectionException, IntrospectionException, InstanceNotFoundException
	{
		return( getAttributeNames( getAttributeInfos( conn, objectName ) ) );
	}
	
		public static MBeanAttributeInfo[]
	filterAttributeInfos(
		final MBeanAttributeInfo[]	infos,
		final AttributeFilter		filter )
	{
		final ArrayList<MBeanAttributeInfo>	matches	= new ArrayList<MBeanAttributeInfo>();
		for( int i = 0; i < infos.length; ++i )
		{
			if ( filter.filterAttribute( infos[ i ] ) )
			{
				matches.add( infos[ i ] );
			}
		}
		
		final MBeanAttributeInfo[]	results	= new MBeanAttributeInfo[ matches.size() ];
		matches.toArray( results );
		
		return( results );
	}
	
	/**
		Get a String[] of Attribute names.
		
		@param infos	array of infos
	 */
		public static String []
	getAttributeNames( final MBeanAttributeInfo[]	infos  )
	{
		final String[]	names	= new String[ infos.length ];
		
		for( int i = 0; i < infos.length; ++i )
		{
			names[ i ]	= infos[ i ].getName();
		}
		
		return( names );
	}
	
	/**
		@param infos	array of infos
		@param attrName
	 */
		public static MBeanAttributeInfo
	getMBeanAttributeInfo(
		final MBeanAttributeInfo[]	infos,
		final String				attrName )
	{
		MBeanAttributeInfo	info	= null;
		
		for( int i = 0; i < infos.length; ++i )
		{
			if ( infos[ i ].getName().equals( attrName ) )
			{
				info	= infos[ i ];
				break;
			}
		}
		
		return( info );
	}
	
	/**
		@param mbeanInfo
		@param attrName
	 */
		public static MBeanAttributeInfo
	getMBeanAttributeInfo(
		final MBeanInfo	mbeanInfo,
		final String	attrName )
	{
		return( getMBeanAttributeInfo( mbeanInfo.getAttributes(), attrName ) );
	}
	
	
	/**
		@param conn
		@param objectName
	 */
		public static MBeanAttributeInfo []
	getAttributeInfos( 
		final MBeanServerConnection	conn,
		final ObjectName			objectName )
		throws IOException,
			ReflectionException, IntrospectionException, InstanceNotFoundException
	{
		final MBeanAttributeInfo []	infos	= conn.getMBeanInfo( objectName ).getAttributes();
		
		return( infos );
	}
	
	
	/**
		Convert an AttributeList to a Map where the keys are the Attribute names,
		and the values are Attribute.
		
		@param attrs	the AttributeList
	 */
		public static Map<String,Attribute>
	attributeListToAttributeMap( final AttributeList attrs )
	{
		final HashMap<String,Attribute>	map	= new HashMap<String,Attribute>();
		
		for( int i = 0; i < attrs.size(); ++i )
		{
			final Attribute	attr	= (Attribute)attrs.get( i );
			
			map.put( attr.getName(), attr );
		}
		
		return( map );
	}
	
	/**
		Convert an AttributeList to a Map where the keys are the Attribute names,
		and the values are the Attribute values.
		
		@param attrs	the AttributeList
	 */
		public static Map<String,Object>
	attributeListToValueMap( final AttributeList attrs )
	{
		final Map<String,Object>	map	= new HashMap<String,Object>();
		
		for( int i = 0; i < attrs.size(); ++i )
		{
			final Attribute	attr	= (Attribute)attrs.get( i );
			
		    final Object value   = attr.getValue();
		    
			map.put( attr.getName(), value );
		}
		
		return( map );
	}
	
	/**
		Convert an AttributeList to a Map where the keys are the Attribute names,
		and the values are the Attribute values.
		
		@param attrs	the AttributeList
	 */
		public static Map<String,String>
	attributeListToStringMap( final AttributeList attrs )
	{
		final Map<String,String>	map	= new HashMap<String,String>();
		
		for( int i = 0; i < attrs.size(); ++i )
		{
			final Attribute	attr	= (Attribute)attrs.get( i );
			
			final Object value  = attr.getValue();
			final String s  = (String)(value == null ? value : "" + value);
			map.put( attr.getName(), s );
		}
		
		return( map );
	}
	
	
		
	/**
		Convert an MBeanAttributeInfo[] to a Map where the keys are the Attribute names,
		and the values are MBeanAttributeInfo.
		
		@param attrInfos	the AttributeList
	 */
		public static Map<String,MBeanAttributeInfo>
	attributeInfosToMap( final MBeanAttributeInfo[] attrInfos )
	{
		final Map<String,MBeanAttributeInfo>	map	= new HashMap<String,MBeanAttributeInfo>();
		
		for( int i = 0; i < attrInfos.length; ++i )
		{
			final MBeanAttributeInfo	attrInfo	= attrInfos[ i ];
			
			map.put( attrInfo.getName(), attrInfo );
		}
		
		return( map );
	}
	
	
		public static MBeanInfo
	removeAttributes(
		final MBeanInfo origInfo,
		final String[]	attributeNames )
	{
		MBeanInfo	result	= origInfo;
		
		if ( attributeNames.length != 0 )
		{
			final Map<String,MBeanAttributeInfo>	infos	=
			    JMXUtil.attributeInfosToMap( origInfo.getAttributes() );
			
			for( int i = 0; i < attributeNames.length; ++i )
			{
				infos.remove( attributeNames[ i ] );
			}
			
			final MBeanAttributeInfo[]	newInfos = new MBeanAttributeInfo[ infos.keySet().size() ];
			infos.values().toArray( newInfos );
			
			result	= new MBeanInfo(
					origInfo.getClassName(),
					origInfo.getDescription(),
					newInfos,
					origInfo.getConstructors(),
					origInfo.getOperations(),
					origInfo.getNotifications() );
		}
	
		return( result );
	}
	
	/**
		Find a feature by name (attribute name, operation name, etc) and return
		all matches.  The feature is matched by calling MBeanFeatureInfo.getName().
		
		@param infos	infos
		@param name	name
		@return Set of the matching items
	 */
		public static Set<MBeanFeatureInfo>
	findInfoByName(
		final MBeanFeatureInfo[]	infos,
		final String				name )
	{
		final Set<MBeanFeatureInfo>	s	= new HashSet<MBeanFeatureInfo>();
		
		for( int i = 0; i < infos.length; ++i )
		{
			final MBeanFeatureInfo	info	= infos[ i ];
			
			if ( info.getName().equals( name ) )
			{
				s.add( info );
			}
		}
		
		return( s );
	}
	
	
	
	/**
		Convert an Map to an Attribute list where the keys are the Attribute names,
		and the values are objects.
		
		@param m
	 */
		public static AttributeList
	mapToAttributeList( final Map<String,Object> m )
	{
		final AttributeList	attrList	= new AttributeList();
		
		for( final String key : m.keySet() )
		{
			final Object	value	= m.get( key );
			
			final Attribute	attr	= new Attribute( key, value );
			
			attrList.add( attr);
		}
		
		return( attrList );
	}
	
	
	/**
		Convert ObjectName into a Set of String.  The resulting
		strings are more readable than just a simple toString() on the ObjectName;
		they are sorted and output in preferential order.
	 */
		public static List<String>
	objectNamesToStrings( final Collection<ObjectName> objectNames )
	{
		// sorting doesn't work on returned array, so convert to Strings first,then sort
		final List<String>	result	= new ArrayList<String>();
		
		for( final ObjectName objectName : objectNames )
		{
			result.add( ObjectNameStringifier.DEFAULT.stringify( objectName ) );
		}
		
		return( result );
	}
	
	/**
		Convert a Set of ObjectName into a Set of String
	 */
		public static String[]
	objectNamesToStrings( final ObjectName[] objectNames )
	{
		final String[]	strings	= new String[ objectNames.length ];
		
		for( int i = 0; i < strings.length; ++i )
		{
			strings[ i ]	= objectNames[ i ].toString();
		}
		
		return( strings );
	}
	
		private static boolean
	connectionIsDead( final MBeanServerConnection conn )
	{
		boolean	isDead	= false;
		
		// see if the connection is really dead by calling something innocuous
		try
		{
			conn.isRegistered( new ObjectName( MBEAN_SERVER_DELEGATE )  );
		}
		catch( MalformedObjectNameException e )
		{
			assert( false );
		}
		catch( IOException e )
		{
			isDead	= true;
		}
		
		return( isDead );
	}
	
		private static AttributeList
	getAttributesSingly(
		MBeanServerConnection	conn,
		ObjectName				objectName,
		String[]				attrNames,
		Set<String>				problemNames )
		throws InstanceNotFoundException
	{
		AttributeList	attrs	= new AttributeList();
		
		for( int i = 0; i < attrNames.length; ++i )
		{
			final String	name	= attrNames[ i ];
			
			try
			{
				final Object	value	= conn.getAttribute( objectName, name );
				
				attrs.add( new Attribute( name, value ) );
			}
			catch( Exception e )
			{
				// if the MBean disappeared while processing, just consider it gone
				// from the start, even if we got some Attributes
				if ( e instanceof InstanceNotFoundException )
				{
					throw (InstanceNotFoundException)e;
				}
				
				if ( problemNames != null )
				{
					problemNames.add( name );
				}
			}
		}
		
		return( attrs );
	}
	
	
	/**
		Get the Attributes using getAttributes() if possible, but if exceptions
		are encountered, attempt to get them one-by-one.
		
		@param conn			the conneciton
		@param objectName	name of the object to access
		@param attrNames	attribute names
		@param problemNames	optional Set to which problem names will be added.
		@return AttributeList
	 */
		public static AttributeList
	getAttributesRobust(
		MBeanServerConnection	conn,
		ObjectName				objectName,
		String[]				attrNames,
		Set<String>				problemNames )
		throws InstanceNotFoundException, IOException
	{
		AttributeList	attrs	= null;
		
		if ( problemNames != null )
		{
			problemNames.clear();
		}
		
		try
		{
			attrs	= conn.getAttributes( objectName, attrNames );
			if ( attrs == null )
			{
				attrs	= new AttributeList();
			}
		}
		catch( InstanceNotFoundException e )
		{
			// if it's not found, we can't do anything about it.
			throw e;
		}
		catch( IOException e )
		{
			if ( connectionIsDead( conn ) )
			{
				throw e;
			}
			
			// connection is still good
			
			attrs	= getAttributesSingly( conn, objectName, attrNames, problemNames );
		}
		catch( Exception e )
		{
			attrs	= getAttributesSingly( conn, objectName, attrNames, problemNames );
		}
		
		return( attrs );
	}
	
	
	/**
		Return true if the two MBeanAttributeInfo[] contain the same attributes
		WARNING: arrays will be sorted to perform the comparison if they are the same length.
	 */
		boolean
	sameAttributes( MBeanAttributeInfo[] infos1, MBeanAttributeInfo[] infos2 )
	{
		boolean	equal	= false;
		
		if( infos1.length == infos2.length )
		{
			equal	= ArrayUtil.arraysEqual( infos1, infos2 );
			if ( ! equal )
			{
				// could still be equal, just in different order
				Arrays.sort( infos1, MBeanAttributeInfoComparator.INSTANCE );
				Arrays.sort( infos2, MBeanAttributeInfoComparator.INSTANCE );
				
				equal	= true;	// reset to false upon failure
				for( int i = 0; i < infos1.length; ++i )
				{
					if ( ! infos1[ i ].equals( infos2[ i ] ) )
					{
						equal	= false;
						break;
					}
				}
			}
			else
			{
				equal	= true;
			}
		}
		return( equal );
	}
	
	
	/**
		Return true if the two MBeanAttributeInfo[] contain the same operations
		WARNING: arrays will be sorted to perform the comparison if they are the same length.
	 */
		boolean
	sameOperations( final MBeanOperationInfo[] infos1, final MBeanOperationInfo[] infos2 )
	{
		boolean	equal	= false;
		
		if ( infos1.length == infos2.length )
		{
			// if they're in identical order, this is the quickest test if they ultimately succeed
			equal	= ArrayUtil.arraysEqual( infos1, infos2 );
			if ( ! equal )
			{
				// could still be equal, just in different order
				Arrays.sort( infos1, MBeanOperationInfoComparator.INSTANCE );
				Arrays.sort( infos2, MBeanOperationInfoComparator.INSTANCE );
				
				equal	= true;	// reset to false upon failure
				for( int i = 0; i < infos1.length; ++i )
				{
					if ( ! infos1[ i ].equals( infos2[ i ] ) )
					{
						equal	= false;
						break;
					}
				}
			}
		}
		return( equal );
	}
	
	/**
		Return true if the MBeanInfos have the same interface (for Attributes and
		operations).  MBeanInfo.equals() is not sufficient as it will fail if the
		infos are in different order, but are actually the same.
	 */
		boolean
	sameInterface( MBeanInfo info1, MBeanInfo info2 )
	{
		return( sameAttributes( info1.getAttributes(), info2.getAttributes() ) &&
			sameOperations( info1.getOperations(), info2.getOperations() ) );
	}
	
		public static boolean
	isIs( final Method method )
	{
		return( method.getName().startsWith( IS ) && method.getParameterTypes().length == 0 );
	}
	
	/**
		Return true if the method is of the form isXyz() or getXyz()
		(no parameters)
	 */
		public static boolean
	isGetter( Method method )
	{
		return( method.getName().startsWith( GET ) && method.getParameterTypes().length == 0 );
	}
	
	
		public static boolean
	isGetter( final MBeanOperationInfo info )
	{
		return ( info.getName().startsWith( GET ) &&
				info.getSignature().length == 0 &&
				! info.getReturnType().equals( "void" ) );
	}


		public static MBeanOperationInfo[]
	findOperations(
		final MBeanOperationInfo[]	operations,
		final String				operationName)
	{
		final Set<MBeanOperationInfo>	items	= new HashSet<MBeanOperationInfo>();
		for( int i = 0; i < operations.length; ++i )
		{
			if ( operations[ i ].getName().equals( operationName ) )
			{
				items.add( operations[ i ] );
			}
		}
		
		final MBeanOperationInfo[]	itemsArray	= new MBeanOperationInfo[ items.size() ];
		items.toArray( itemsArray );
		return itemsArray;
	}
	
		public static MBeanOperationInfo
	findOperation(
		final MBeanOperationInfo[]	operations,
		final String				operationName,
		final String[]				types )
	{
		MBeanOperationInfo	result	= null;
		
		for( int i = 0; i < operations.length; ++i )
		{
			final MBeanOperationInfo	info	= operations[ i ];
			
			if ( info.getName().equals( operationName ) )
			{
				final MBeanParameterInfo[]	sig	= info.getSignature();
				
				if ( sig.length == types.length )
				{
					result	= info;	// assume match...
					for( int j = 0; j < sig.length; ++j )
					{
						if ( ! types[ j ].equals( sig[ j ].getType() ) )
						{	
							result	= null;	// no match
							break;
						}
					}
				}
			}
		}
		
		return( result );
	}

	
	/**
		Return true if the method is of the form isXyz() or getXyz()
		(no parameters)
	 */
		public static boolean
	isIsOrGetter( Method method )
	{
		return( isGetter( method ) || isIs( method ) );
	}
	
		public static String
	getAttributeName( final Method method )
	{
		final String	methodName	= method.getName();
		String			attrName	= null;
		
		int prefixLength	= 0;
		
		if ( methodName.startsWith( GET ) || methodName.startsWith( SET ) )
		{
			prefixLength	= 3;
		}
		else
		{
			prefixLength	= 2;
		}
		
		return( methodName.substring( prefixLength, methodName.length() ) );
	}
	
	
		public static boolean
	isSetter( Method method )
	{
		return( method.getName().startsWith( SET ) &&
			method.getParameterTypes().length == 1 &&
			method.getParameterTypes()[ 0 ] != Attribute.class &&
			method.getReturnType().getName().equals( "void" ) );
	}
	
		public static boolean
	isGetAttribute( Method m )
	{
		return( m.getName().equals( "getAttribute" ) &&
			m.getParameterTypes().length == 1 && m.getParameterTypes()[ 0 ] == String.class );
			
	}
	
		public static boolean
	isGetAttributes( Method m )
	{
		return( m.getName().equals( "getAttributes" ) &&
			m.getParameterTypes().length == 1 && m.getParameterTypes()[ 0 ] == String[].class );
			
	}
	
		public static boolean
	isSetAttribute( Method m )
	{
		return( m.getName().equals( "setAttribute" ) &&
			m.getParameterTypes().length == 1 && m.getParameterTypes()[ 0 ] == Attribute.class );
			
	}
	
		public static boolean
	isSetAttributes( Method m )
	{
		return( m.getName().equals( "setAttributes" ) &&
			m.getParameterTypes().length == 1 && m.getParameterTypes()[ 0 ] == AttributeList.class );
			
	}
	
	
		public static ArrayList<MBeanAttributeInfo>
	generateAttributeInfos(
		final Collection<Method> methodSet,
		final boolean	read,
		final boolean	write)
	{
		final ArrayList<MBeanAttributeInfo>	infos	= new ArrayList<MBeanAttributeInfo>();
		
		assert( methodSet != null );
	
		for( final Method m : methodSet )
		{
			final String	methodName	= m.getName();
			
			assert( read || ( write && methodName.startsWith( SET )) );
			final MBeanAttributeInfo	info = new MBeanAttributeInfo(
				getAttributeName( m ),
				m.getReturnType().getName(),
				methodName,
				read,
				write,
				methodName.startsWith( "is" )
			);
			
			infos.add( info );
		}
		
		return( infos );
	}
	
		public static MBeanAttributeInfo[]
	generateMBeanAttributeInfos(
		final Collection<Method> getterSetters,
		final Collection<Method> getters,
		final Collection<Method> setters  )
	{
		final ArrayList<MBeanAttributeInfo>	attrsList	= new ArrayList<MBeanAttributeInfo>();
		
		attrsList.addAll( generateAttributeInfos( getterSetters, true, true ) );
		attrsList.addAll( generateAttributeInfos( getters, true, false ) );
		attrsList.addAll( generateAttributeInfos( setters, false, true ) );
		
		final MBeanAttributeInfo[]	attrs	= new MBeanAttributeInfo[ attrsList.size() ];
		attrsList.toArray( attrs );
		
		return( attrs );
	}
	
	
	    public static String[]
    getSignature( final MBeanParameterInfo[]infos )
    {
        final String[]  sig = new String[ infos.length ];
        
        int i = 0;
        for( final MBeanParameterInfo info : infos )
        {
            sig[ i ]    = info.getType();
            ++i;
        }
        return sig;
    }
    
		public static MBeanParameterInfo[]
	generateSignature( final Class[] sig )
	{
		final MBeanParameterInfo[]	infos	= new MBeanParameterInfo[ sig.length ];
		
		for( int i = 0; i < sig.length; ++i )
		{
			final Class	paramClass	= sig[ i ];
			
			final String	name		= "p" + i;
			final String	type		= paramClass.getName();
			final String	description	= paramClass.getName();
			
			final MBeanParameterInfo	info	=
				new MBeanParameterInfo( name, type, description );
			infos[ i ]	= info;
		}
		
		return( infos );
	}

		public static MBeanOperationInfo[]
	generateMBeanOperationInfos(
		final Collection<Method> 		methodSet )
	{
		final MBeanOperationInfo[]	infos	= new MBeanOperationInfo[ methodSet.size() ];
		
		final Iterator	iter	= methodSet.iterator();
	
		int	i = 0;
		while ( iter.hasNext() )
		{
			final Method	m	= (Method)iter.next();
			final String	methodName	= m.getName();
			
			final MBeanOperationInfo	info = new MBeanOperationInfo(
				methodName,
				methodName,
				generateSignature( m.getParameterTypes() ),
				m.getReturnType().getName(),
				MBeanOperationInfo.UNKNOWN
			);
			
			infos[ i ]	= info;
			++i;
			
		}
		
		return( infos );
	}
	

		public static MBeanInfo
	interfaceToMBeanInfo( final Class theInterface )
	{
		final Method[]	methods	= theInterface.getMethods();
		
		final Map<String,Method>	getters			= new HashMap<String,Method>();
		final Map<String,Method>	setters			= new HashMap<String,Method>();
		final Map<String,Method>	getterSetters	= new HashMap<String,Method>();
		final Set<Method>	operations		= new HashSet<Method>();
		
		for( int i = 0; i < methods.length; ++i )
		{
			final Method	method	= methods[ i ];
			
			final String	methodName	= method.getName();
			
			String	attrName	= null;
			if ( isIsOrGetter( method ) )
			{
				attrName	= getAttributeName( method );
				getters.put( attrName, method );
			}
			else if ( isSetter( method ) )
			{
				attrName	= getAttributeName( method );
				setters.put( attrName, method );
			}
			else
			{
				operations.add( method );
			}
			
			if ( (attrName != null) &&
					getters.containsKey( attrName ) &&
					setters.containsKey( attrName ) )
			{
				final Method	getter	= (Method)getters.get( attrName );
					
				final Class	getterType	= getter.getReturnType();
				final Class	setterType	= ((Method)setters.get( attrName )).getParameterTypes()[ 0 ];
				
				if ( getterType == setterType )
				{
					getters.remove( attrName );
					setters.remove( attrName );
					getterSetters.put( attrName, getter );
				}
				else
				{
					throw new IllegalArgumentException( "Attribute " + attrName + 
						"has type " + getterType.getName() + " as getter but type " +
						setterType.getName() + " as setter" );
				}
			}
		}
		
		/*
		java.util.Iterator	iter	= null;
		trace( "-------------------- getterSetters -------------------" );
		iter	= getterSetters.values().iterator();
		while ( iter.hasNext() )
		{
			trace( ((Method)iter.next()).getName() + ", " );
		}
		trace( "-------------------- getters -------------------" );
		iter	= getters.values().iterator();
		while ( iter.hasNext() )
		{
			trace( ((Method)iter.next()).getName() + ", " );
		}
		trace( "-------------------- setters -------------------" );
		iter	= setters.values().iterator();
		while ( iter.hasNext() )
		{
			trace( ((Method)iter.next()).getName() + ", " );
		}
		*/
		
		final MBeanAttributeInfo[]	attrInfos	=
			generateMBeanAttributeInfos( getterSetters.values(),
				getters.values(), setters.values() );
			
		final MBeanOperationInfo[]	operationInfos	=
			generateMBeanOperationInfos( operations );
		
		final MBeanConstructorInfo[]	constructorInfos	= null;
		final MBeanNotificationInfo[]	notificationInfos	= null;
		
		final MBeanInfo	mbeanInfo	= new MBeanInfo(
				theInterface.getName(),
				theInterface.getName(),
				attrInfos,
				constructorInfos,
				operationInfos,
				notificationInfos );
		
		return( mbeanInfo );
	}
	
	/**
		Merge two MBeanAttributeInfo[].  info1 overrides any duplication in info2.
		
		@param infos1
		@param infos2
	 */
		public static MBeanAttributeInfo[]
	mergeMBeanAttributeInfos(
		final MBeanAttributeInfo[]	infos1,
		final MBeanAttributeInfo[]	infos2 )
	{
	    // first make a Set of all names in infos1
	    final Set<String>   names   = new HashSet<String>();
	    for( final MBeanAttributeInfo info : infos1 )
	    {
	        names.add( info.getName() );
	    }

		final Set<MBeanAttributeInfo>	merged	= GSetUtil.newSet( infos1 );
		
		for( final MBeanAttributeInfo info2 : infos2 )
		{
			final String	info2Name	= info2.getName();
			
			if ( ! names.contains( info2Name ) )
			{
				merged.add( info2 );
			}
		}

		final MBeanAttributeInfo[]	infosArray	=
			new MBeanAttributeInfo[ merged.size() ];
		merged.toArray( infosArray );

		return( infosArray );
	}
	
	/**
		Merge two MBeanNotificationInfo[].
		
		@param infos1
		@param infos2
	 */
		public static MBeanNotificationInfo[]
	mergeMBeanNotificationInfos(
		final MBeanNotificationInfo[] infos1,
		final MBeanNotificationInfo[] infos2 )
	{
	    if ( infos1 == null )
	    {
	        return infos2;
	    }
	    else if ( infos2 == null )
	    {
	        return( infos1 );
	    }

	    final Set<MBeanNotificationInfo>    all = GSetUtil.newSet( infos1 );
	    all.addAll( GSetUtil.newSet( infos2 ) );
		
		final MBeanNotificationInfo[]   merged  = new MBeanNotificationInfo[ all.size() ];
		return all.toArray( merged );
	}
	
	/**
		Add MBeanNotificationInfo into the MBeanInfo.
		
		@param origInfo
		@param notifs
	 */
	    public static MBeanInfo
	addNotificationInfos(
	    final MBeanInfo origInfo,
	    final MBeanNotificationInfo[]    notifs )
	{
	    MBeanInfo   result  = origInfo;
	    
	    if ( notifs != null && notifs.length != 0 )
	    {
    	    result  = new MBeanInfo(
    	        origInfo.getClassName(),
    	        origInfo.getDescription(),
    	        origInfo.getAttributes(),
    	        origInfo.getConstructors(),
    	        origInfo.getOperations(),
    	        mergeMBeanNotificationInfos( origInfo.getNotifications(), notifs )
    	        );
	    }
	    return result;
	}

	
	/**
		Merge two MBeanOperationInfo[].
		
		@param infos1
		@param infos2
	 */
		public static MBeanOperationInfo[]
	mergeMBeanOperationInfos(
		final MBeanOperationInfo[] infos1,
		final MBeanOperationInfo[] infos2 )
	{
	    if ( infos1 == null )
	    {
	        return infos2;
	    }
	    else if ( infos2 == null )
	    {
	        return( infos1 );
	    }
	    
	    final Set<MBeanOperationInfo>    all = GSetUtil.newSet( infos1 );
	    all.addAll( GSetUtil.newSet( infos2 ) );
		
		final MBeanOperationInfo[]   merged  = new MBeanOperationInfo[ all.size() ];
		return all.toArray( merged );
	}
	
	/**
		Merge two MBeanOperationInfo[].
		
		@param infos1
		@param infos2
	 */
		public static MBeanConstructorInfo[]
	mergeMBeanConstructorInfos(
		final MBeanConstructorInfo[] infos1,
		final MBeanConstructorInfo[] infos2 )
	{
	    if ( infos1 == null )
	    {
	        return infos2;
	    }
	    else if ( infos2 == null )
	    {
	        return( infos1 );
	    }
	    
	    final Set<MBeanConstructorInfo>    all = GSetUtil.newSet( infos1 );
	    all.addAll( GSetUtil.newSet( infos2 ) );
		
		final MBeanConstructorInfo[]   merged  = new MBeanConstructorInfo[ all.size() ];
		return all.toArray( merged );
	}
	
	
	/**
		Merge two MBeanInfo.  'info1' takes priority in conflicts, name, etc.
		
		@param info1
		@param info2
	 */
	    public static MBeanInfo
	mergeMBeanInfos(
	    final MBeanInfo info1,
	    final MBeanInfo info2 )
	{
	    if ( info1 == null )
	    {
	        return info2;
	    }
	    else if ( info2 == null )
	    {
	        return( info1 );
	    }
	    
	    return( new MBeanInfo(
	        info1.getClassName(),
	        info1.getDescription(),
	        mergeMBeanAttributeInfos( info1.getAttributes(), info2.getAttributes() ),
	        mergeMBeanConstructorInfos( info1.getConstructors(), info2.getConstructors() ),
	        mergeMBeanOperationInfos( info1.getOperations(), info2.getOperations() ),
	        mergeMBeanNotificationInfos( info1.getNotifications(), info2.getNotifications() )
	        ) );
	        
	}
	
	
	/**
		Make a new MBeanInfo from an existing one, substituting MBeanAttributeInfo[]
		
		@param origMBeanInfo
		@param newAttrInfos
	 */
		public static MBeanInfo
	newMBeanInfo(
		final MBeanInfo	origMBeanInfo,
		final MBeanAttributeInfo[]	newAttrInfos )
	{
		final MBeanInfo	info	= new MBeanInfo( origMBeanInfo.getClassName(),
									origMBeanInfo.getDescription(),
									newAttrInfos,
									origMBeanInfo.getConstructors(),
									origMBeanInfo.getOperations(),
									origMBeanInfo.getNotifications() );
		return( info );
	}
	
	/**
		Make a new MBeanInfo from an existing one, substituting MBeanOperationInfo[]
		
		@param origMBeanInfo
		@param newOps
	 */
		public static MBeanInfo
	newMBeanInfo(
		final MBeanInfo	origMBeanInfo,
		final MBeanOperationInfo[]	newOps )
	{
		final MBeanInfo	info	= new MBeanInfo( origMBeanInfo.getClassName(),
									origMBeanInfo.getDescription(),
									origMBeanInfo.getAttributes(),
									origMBeanInfo.getConstructors(),
									newOps,
									origMBeanInfo.getNotifications() );
		return( info );
	}
	
	
	/**
	    Find the index within the MBeanOperationInfo[] of the specified method with the
	    specified parameter types.  If <code>parameterTypes</code> is null, then the
	    first operation whose name matches is returned.
	    
		@param info
		@param methodName
		@param parameterTypes
		@return the index of the MBeanOperationInfo, or -1 if not found
	 */
		public static int
	findMBeanOperationInfo(
		final MBeanInfo	info,
		final String    methodName,
		final String[]  parameterTypes  )
	{
	    int resultIdx = -1;
	    
	    final MBeanOperationInfo[]  ops = info.getOperations();
	    for( int i = 0; i < ops.length; ++i )
	    {
	        final MBeanOperationInfo    op  = ops[i];
	        
	        if ( op.getName().equals( methodName ) &&
	                ( parameterTypes == null || 
	                ArrayUtil.arraysEqual( parameterTypes, op.getSignature() ) )
	           )
	        {
	            resultIdx   = i;
	            break;
	        }
	    }
	
	    return resultIdx;
	}
	
	
	
		public static boolean
	domainMatches(
		final String		defaultDomain,
		final ObjectName	pattern,
		final ObjectName	candidate )
	{
		boolean	matches	= false;
		
		final String	candidateDomain	= candidate.getDomain();
		if ( pattern.isDomainPattern() )
		{
			final String	regex	=
				RegexUtil.wildcardToJavaRegex( pattern.getDomain() );
			
			matches	= Pattern.matches( regex, candidateDomain);
		}
		else
		{	
			// domain is not a pattern
			
			String	patternDomain	= pattern.getDomain();
			if ( patternDomain.length() == 0 )
			{
				patternDomain	= defaultDomain;
			}
			
			matches	= patternDomain.equals( candidateDomain );
		}
		
		//dm( "MBeanProxyMgrImpl.domainMatches: " + matches + " " + pattern + " vs " + candidate );
		
		return( matches );
	}
	
		public static boolean
	matchesPattern(
		final String		defaultDomain,
		final ObjectName	pattern,
		final ObjectName	candidate )
	{
		boolean	matches	= false;
		
		if ( domainMatches( defaultDomain, pattern, candidate ) )
		{
			final String	patternProps	= pattern.getCanonicalKeyPropertyListString();
			final String	candidateProps	= candidate.getCanonicalKeyPropertyListString();
			assert(  patternProps.indexOf( "*" ) < 0 );
			assert(  candidateProps.indexOf( "*" ) < 0 );
			
			// Since we used canonical form any match means the pattern props String
			// must be a substring of candidateProps
			if ( candidateProps.indexOf( patternProps ) >= 0 )
			{
				matches	= true;
			}
		}
		
		return( matches );
	}
	
	    public static String
	toString( final ObjectName objectName )
	{
	    return ObjectNameStringifier.DEFAULT.stringify( objectName );
	}
	
	
	    public static Notification
	cloneNotification(
	    final Notification  in,
	    final Object        source )
	{
	    Notification out = null;
	    
	    if ( in.getClass() == AttributeChangeNotification.class )
	    {
	        final AttributeChangeNotification a = (AttributeChangeNotification)in;
	        
	        out = new AttributeChangeNotification(
	            source,
	            a.getSequenceNumber(),
	            a.getTimeStamp(),
	            a.getMessage(),
	            a.getAttributeName(),
	            a.getAttributeType(),
	            a.getOldValue(),
	            a.getNewValue() );
	    }
	    else if ( in.getClass() == Notification.class )
	    {
	        out = new Notification(
	            in.getType(),
	            source,
	            in.getSequenceNumber(),
	            in.getTimeStamp(),
	            in.getMessage() );
	    }
	    else
	    {
	        throw new IllegalArgumentException( "Not supporting cloning of: " + in.getClass() );
	    }
	    
	    return out;
	}
	
	/**
	    The sole purpose of this method is to move compiler warnings here, thus
	    eliminating them from other call sites.  May be removed when JMX becomes
	    generified.
	 */
	    public static Set<ObjectName>
	queryNames(
	    final MBeanServerConnection conn,
	    final ObjectName            pattern,
	    final QueryExp              exp)
	    throws java.io.IOException
	{
	    return TypeCast.asSet( conn.queryNames( pattern, exp ) );
	}
	
	/**
	    The sole purpose of this method is to move compiler warnings here, thus
	    eliminating them from other call sites.  May be removed when JMX becomes
	    generified.
	 */
	    public static Set<ObjectName>
	queryNames(
	    final MBeanServer  server,
	    final ObjectName   pattern,
	    final QueryExp     exp)
	{
	    try
	    {
	        return queryNames( (MBeanServerConnection)server, pattern, exp );
	    }
	    catch( final IOException e )
	    {
	        // ignore, can't happen.
	    }
	    return null;
	}
	
	
	

	/**
	    Get a Map from the user data field of a Notification.
	    This variant requires Map<String,Serializable>.
	 */
		public static final <T extends Serializable> Map<String, T>
	getUserDataMapString_Serializable( final Notification	notif )
	{
	    final Object userData   = notif.getUserData();
	    if ( ! (userData instanceof Map) )
	    {
	        throw new IllegalArgumentException();
	    }
	    
	    final Map<String,T>  result    = TypeCast.asMap( (Map)userData );
	    if ( result != null )
	    {
	        // verify that it's a Map<String,Serializable>
    	    for ( final String testKey : result.keySet() )
    	    {
    	        final T testValue  = result.get( testKey );
    	        
    	        result.put( testKey, testValue );
    	    }
	    }

	    return result;
	}
}


















