/*
 * 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.
 */
 
/*
 * $Header: /cvs/glassfish/admin-cli/cli-api/src/java/com/sun/enterprise/jmx/kstat/kstatMgr.java,v 1.4 2007/05/05 05:25:02 tcfujii Exp $
 * $Revision: 1.4 $
 * $Date: 2007/05/05 05:25:02 $
 */
package com.sun.enterprise.jmx.kstat;

import javax.management.*;

import java.lang.Runtime;
import java.lang.Process;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.InputStreamReader;

import com.sun.cli.util.LineReaderImpl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ListIterator;
import java.util.Iterator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import com.sun.cli.util.stringifier.ArrayStringifier;

final class kstatModule
{
	final String	mName;
	final HashMap	mStats;
	
		public
	kstatModule( String name )
	{
		mName	= name;
		mStats	= new HashMap();
	}
	
		public void
	add( kstat stat )
	{
		mStats.put( stat.getName(), stat );
	}
	
		public kstat
	getkstat( String name )
	{
		return( (kstat)mStats.get( name ) );
	}
	
		public String
	getName()
	{
		return( mName );
	}
	
		public Set
	getNames()
	{
		return( mStats.keySet() );
	}
}


class kstatCache implements kstatRepository
{
	long	mRefreshMillis;
	long	mLastRefreshMillis;
	
	final HashMap	mkstats;
	final HashMap	mModules;
	
		private static void
	dm( Object o )
	{
		System.out.println( o.toString() );
	}

		public
	kstatCache()
	{
		mRefreshMillis		= 30 * 1000;
		mLastRefreshMillis	= 0;
		mkstats				= new HashMap();
		mModules			= new HashMap();
	}
	
		public void
	clear()
	{
		mkstats.clear();
		mModules.clear();
	}
	
		private kstat
	parsekstatAttr( final String line )
	{
		// parse first line for module name and instance number
		String []	tok	= line.split( ":" );
		
		// kstat -p spits out malformed lines occassionally, so check for the correct
		// number of tokens
		kstat	stat	= null;
		if ( tok.length == 4 )
		{
			//dm( ArrayStringifier.stringify( tok, "," ) );
			final String	moduleName		= tok[ 0 ];
			final int		instanceNumber	= Integer.parseInt( tok[ 1 ] );
			final String	name			= tok[ 2 ];
			final String	attrAll			= tok[ 3 ];
			
			tok	= attrAll.split( "[ \t]+" );
			
			if ( tok.length == 2 )
			{
				final String	attrName	= tok[ 0 ].trim();
				final String	attrValue	= tok[ 1 ].trim();
				
				final kstat.kstatAttribute attr	= new kstat.kstatAttribute( attrName, attrValue );
				
				final String	scopedName	=
									kstat.getScopedName( moduleName, instanceNumber, name );
				stat	= (kstat)mkstats.get( scopedName );
				if ( stat == null )
				{
					stat	= new kstat( moduleName, instanceNumber, name );
				}
				stat.addAttribute( attr );
			}
		}
		
		return( stat );
	}
	
		private void
	add( final kstat	stat )
	{
		mkstats.put( stat.getScopedName( ), stat );
		
		final String	moduleName	= stat.getModuleName();
		
		kstatModule	module	= (kstatModule)mModules.get( moduleName );
		if ( module == null )
		{
			module	= new kstatModule( moduleName );
			mModules.put( moduleName, module );
		}
		
		module.add( stat );
	}

		private void
	processResults( String [] lines )
		throws java.io.IOException
	{
		final ListIterator	iter	= Arrays.asList( lines ).listIterator( );
		
		while( iter.hasNext() )
		{
			final String	line	= ((String)iter.next()).trim();
			
			final kstat	stat = parsekstatAttr( line );
			if ( stat != null )
			{
				add( stat );
			}
		}
	}

		private Process
	invoke_kstat( String args )
		throws java.io.IOException
	{
		final String	execString	= "kstat -p " + ((args == null) ? "" : args);
		
		dm( "invoking kstat as: " + execString );
		// invoke kstat with -p option, which is machine parseable output
		return( Runtime.getRuntime().exec( execString ) );
	}
	

	
		private String []
	readResults( final InputStream resultsStream )
		throws java.io.IOException
	{
		final InputStreamReader reader	= new InputStreamReader( resultsStream );
		
		final StringBuffer	sbuf	= new StringBuffer();
		int	count;
		final char []	cbuf	= new char [ 256 * 1024 ];
		while ( (count = reader.read( cbuf, 0, cbuf.length)) >= 0 )
		{
			sbuf.append( cbuf, 0, count );
		}
		
		return( sbuf.toString().split( "\n" ) );
	}
	
		public void
	refresh( String scopedName )
		throws java.io.IOException
	{
		if ( scopedName == null || scopedName.equals( "" )  )
		{
			clear();
		}

		final Process	proc	= invoke_kstat( scopedName );
		
		final InputStream	resultsStream	= proc.getInputStream();
		
		final String []	outputLines	= readResults( resultsStream );
		
		processResults( outputLines );
		
		try
		{
			proc.waitFor();
		}
		catch( InterruptedException e )
		{
			System.err.println( "Interrupted: " + e.toString() );
		}
		
		mLastRefreshMillis	= System.currentTimeMillis();
	}
	
		public void
	refresh()
		throws java.io.IOException
	{
		refresh( "" );
	}
	
		void
	maybeRefresh()
	{
		if ( ( System.currentTimeMillis() - mLastRefreshMillis ) > mRefreshMillis )
		{
			try
			{
				refresh();
			}
			catch( java.io.IOException e )
			{
				System.err.println("couldn't refresh kstat" );
			}
		}
	}

		public void
	setRefreshMillis( long	millis)
	{
		mRefreshMillis	= millis;
		maybeRefresh();
	}
	
		public Set
	getModuleNames()
	{
		maybeRefresh();
		
		return( mModules.keySet() );
	}
	
		public Set
	getNamesInModule( String moduleName )
	{
		maybeRefresh();
			
		Set	names	= null;
		
		final kstatModule	module	= (kstatModule)mModules.get( moduleName );
		
		if ( module != null )
		{
			names	= module.getNames();
		}
		return( names );
	}

		public kstat
	getkstat( String moduleName, String name )
	{
		maybeRefresh();
		
		final kstatModule	module	= (kstatModule)mModules.get( moduleName );
		kstat	stat	= null;
		if ( module != null )
		{
			stat	= module.getkstat( name );
		}
		
		return( stat  );
	}
	
		public String
	query_kstatAttribute( String module, int instance, String name, String attributeName )
	{
		maybeRefresh();
		return( null );
	}
};


/*
	Solaris specific MBean to support kstat
 */
public final class kstatMgr implements kstatMgrMBean, MBeanRegistration
{
	MBeanServer			mServer;
	ObjectName			mMyName;
	final kstatCache	mCache;
	
	public final static String	KSTAT_DOMAIN	= "kstat";
	
		private static void
	dm( Object o )
	{
		System.out.println( o.toString() );
	}
	
		public
	kstatMgr()
	{
		mCache		= new kstatCache();
		mServer		= null;
		mMyName		= null;
		// we get this in preRegister()
	}
	
	
		public ObjectName
	preRegister(MBeanServer server, ObjectName name)
	{
		mServer		= server;
		mMyName		= name;
		
		return( mMyName );
	}
	
		public void
	postRegister( Boolean registrationDone )
	{
	}
	
		public void
	preDeregister()
	{
		// nothing to do
	}
		public void
	postDeregister(  )
	{
		// nothing to do
	}
	
	
	private static final String	TYPE_PROPERTY		= "type=kstat";
	private static final String	NAME_PROPERTY= "name";
	private static final String	MODULE_PROPERTY		= "kstat-module";
	private static final String	INSTANCE_PROPERTY	= "kstat-instance";
	private static final String	KSTAT_NAME_PROPERTY		= "kstat-name";
	private static final String	CLASS_PROPERTY		= "kstat-class";
	private static final char	PROPERTY_DELIM		= ',';
	private static final char	PROPERTY_VALUE_DELIM		= '=';
	
	
		private String
	mapkstatName( final String name)
	{
		String	result	= name;
		
		if ( name.indexOf( ',' ) >= 0 )
		{
			final char []	chars	= name.toCharArray();
			
			for( int i = 0; i < chars.length; ++i )
			{
				if ( chars[ i ] == ',' )
				{
					chars[ i ]	= '.';
				}
			}
			
			result	= new String( chars );
			
		}
		return( result );
	}

		private String
	createObjectNameString( String domain, final kstat stat )
	{
		// kstat names can contain the "," character, which we can't use in an ObjectName
		final String kstatName	= mapkstatName( stat.getName() );
		
		final String nameProperty	= stat.getModuleName() + "." + kstatName;
		
		final String name	= domain + ":" + 
			TYPE_PROPERTY + PROPERTY_DELIM +
			NAME_PROPERTY + PROPERTY_VALUE_DELIM + nameProperty + PROPERTY_DELIM +
			KSTAT_NAME_PROPERTY + PROPERTY_VALUE_DELIM + kstatName + PROPERTY_DELIM +
			MODULE_PROPERTY + PROPERTY_VALUE_DELIM + stat.getModuleName();
			
			/* + PROPERTY_DELIM + 
			INSTANCE_PROPERTY + PROPERTY_VALUE_DELIM + stat.getInstanceNumber() + PROPERTY_DELIM +
			KSTAT_NAME_PROPERTY + PROPERTY_VALUE_DELIM + kstatName + PROPERTY_DELIM +
			CLASS_PROPERTY + PROPERTY_VALUE_DELIM + stat.getkstatClass(); */
		
		return( name );
	}
	
		private void
	addMBeanFor_kstat( final kstat stat )
	{
		String	objectNameString	= createObjectNameString( KSTAT_DOMAIN, stat );
		
		try
		{
			final kstatMBean	mb	= new kstatMBean( this, stat );
			
			final ObjectName	objectName	= new ObjectName( objectNameString );
		
			unregisterMBean( objectName );
			mServer.registerMBean( mb, objectName );
		}
		catch( Exception e )
		{
			System.err.println( "Can't add object named: " + objectNameString );
			
			System.err.println( e.getMessage() );
		}
	}

		private void
	addMBeansForModule( final String moduleName )
	{
		final Set	names	= mCache.getNamesInModule( moduleName );
		final Iterator	iter	= names.iterator();
		
		while ( iter.hasNext() )
		{
			addMBeanFor_kstat( mCache.getkstat( moduleName, (String)iter.next() ) );
		}
	}
	
		private void
	addMBeansForModules( final Set moduleNames )
		throws Exception
	{
		final Iterator	iter	= moduleNames.iterator();
		
		while ( iter.hasNext() )
		{
			addMBeansForModule( (String)iter.next() );
		}
	}
	
		private void
	unregisterMBean( ObjectName name )
	{
		try
		{
			mServer.unregisterMBean( name );
		}
		catch( InstanceNotFoundException e )
		{
			// that's fine, we wanted it unregistered anyway
		}
		catch( MBeanRegistrationException e )
		{
			// that's fine, we wanted it unregistered anyway
		}
	}
	
		private void
	unregisterAll( Iterator objectNames )
		throws MBeanRegistrationException
	{
		while ( objectNames.hasNext() )
		{
			unregisterMBean( (ObjectName)objectNames.next() );
		}
	}
	
//---------------------------------------------------

		public synchronized void
	initkstats()
		throws Exception
	{
		refresh();
	}
	
		public synchronized void
	clearkstats()
	{
		mCache.clear();
		
		try
		{
			final ObjectName	pattern	= new ObjectName( KSTAT_DOMAIN + ":type=kstat,*" );
			final Set allkstatMBeans	= mServer.queryNames( pattern, null );
			
			unregisterAll( allkstatMBeans.iterator() );
		}
		catch( MalformedObjectNameException e )
		{
			// a bug
			assert( false );
		}
		catch( MBeanRegistrationException e )
		{
			// a bug
			assert( false );
		}
	}
	
	
		public void
	refresh( String scopedName )
		throws Exception
	{
		mCache.refresh( scopedName );
		
		final Set moduleNames	= mCache.getModuleNames();
		addMBeansForModules( moduleNames );
	}
	
		public void
	refresh(  )
		throws Exception
	{
		clearkstats();
		
		refresh( null );
	}
};


