blob: 78c28720d2f3e618045f1e7c7089b55f75929562 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/*
2015-01-15, rgf: take into account that a context thread class loader may be null (not set)
2007-09-21: Rony G. Flatscher, new class loading sequence:
- Thread's context class loader
- BSFManager's defining class loader
2007-01-29: Rony G. Flatscher: added BSF_Log[Factory] to allow BSF to run without org.apache.commons.logging present
*/
package org.apache.bsf.util.event.generator;
import org.apache.bsf.BSF_Log;
import org.apache.bsf.BSF_LogFactory;
import org.apache.bsf.BSFManager;
import java.io.FileOutputStream;
import java.io.IOException;
/** EventAdapterGenerator
*
* Generate an "Event Adapter" dynamically during program execution
*
**/
public class EventAdapterGenerator
{
public static AdapterClassLoader ldr = new AdapterClassLoader();
static Class EVENTLISTENER = null;
static String CLASSPACKAGE = "org/apache/bsf/util/event/adapters/";
static String WRITEDIRECTORY = null;
// starting 8 bytes of all Java Class files
static byte CLASSHEADER[];
// constant pool items found in all event adapters
static short BASECPCOUNT; // number of cp items + 1 ( cp item # 0 reserved for JVM )
static byte BASECP[]; //
// some bytes in the middle of the class file (see below)
static byte FIXEDCLASSBYTES[];
// the initialization method, noargs constructor
static byte INITMETHOD[];
private static BSF_Log logger=null;
/* The static initializer */
static
{
// logger = LogFactory.getLog((org.apache.bsf.util.event.generator.EventAdapterGenerator.class).getName());
logger = BSF_LogFactory.getLog((org.apache.bsf.util.event.generator.EventAdapterGenerator.class).getName());
String USERCLASSPACKAGE = System.getProperty("DynamicEventClassPackage",
"");
if (!USERCLASSPACKAGE.equals(""))
{
CLASSPACKAGE = USERCLASSPACKAGE;
}
if(CLASSPACKAGE.length() > 0 )
{
CLASSPACKAGE = CLASSPACKAGE.replace('\\','/');
if(!CLASSPACKAGE.endsWith("/"))
{ CLASSPACKAGE = CLASSPACKAGE+"/"; }
}
WRITEDIRECTORY = System.getProperty("DynamicEventClassWriteDirectory",CLASSPACKAGE);
if(WRITEDIRECTORY.length() > 0 )
{
WRITEDIRECTORY = WRITEDIRECTORY.replace('\\','/');
if(!WRITEDIRECTORY.endsWith("/"))
{ WRITEDIRECTORY = WRITEDIRECTORY+"/"; }
}
try
// { EVENTLISTENER = Class.forName("java.util.EventListener"); }
{
// EVENTLISTENER = Thread.currentThread().getContextClassLoader().loadClass ("java.util.EventListener"); // rgf, 2006-01-05
// rgf, 20070917: first try context class loader, then BSFManager's defining class loader
EVENTLISTENER=null;
ClassLoader tccl=Thread.currentThread().getContextClassLoader();
if (tccl!=null)
{
try {
EVENTLISTENER = tccl.loadClass ("java.util.EventListener");
}
catch(ClassNotFoundException ex01)
{}
}
if (EVENTLISTENER==null) // did not work, try to load it via the definedClassLoader
{
EVENTLISTENER = BSFManager.getDefinedClassLoader().loadClass ("java.util.EventListener");
}
}
catch(ClassNotFoundException ex)
{
System.err.println(ex.getMessage());
ex.printStackTrace();
}
// start of the Java Class File
CLASSHEADER = ByteUtility.addBytes(CLASSHEADER,(byte)0xCA); // magic
CLASSHEADER = ByteUtility.addBytes(CLASSHEADER,(byte)0xFE); // magic
CLASSHEADER = ByteUtility.addBytes(CLASSHEADER,(byte)0xBA); // magic
CLASSHEADER = ByteUtility.addBytes(CLASSHEADER,(byte)0xBE); // magic
CLASSHEADER = ByteUtility.addBytes(CLASSHEADER,(short)3); // minor version
CLASSHEADER = ByteUtility.addBytes(CLASSHEADER,(short)45); // major version
// Start the constant pool for base items in all event adapter classes
BASECPCOUNT = 17; // number of cp items + 1 ( cp item # 0 reserved for JVM )
// cp item 01
BASECP = Bytecode.addUtf8(BASECP,"()V");
// cp item 02
BASECP = Bytecode.addUtf8(BASECP,"<init>");
// cp item 03
BASECP = Bytecode.addUtf8(BASECP,"Code");
// cp item 04
BASECP = Bytecode.addUtf8(BASECP,"eventProcessor");
// cp item 05
BASECP = Bytecode.addUtf8(BASECP,"java/lang/Object");
// cp item 06
BASECP = Bytecode.addUtf8(BASECP,"org/apache/bsf/util/event/EventAdapterImpl");
// cp item 07
BASECP = Bytecode.addUtf8(BASECP,"org/apache/bsf/util/event/EventProcessor");
// cp item 08
BASECP = Bytecode.addUtf8(BASECP,"(Ljava/lang/String;[Ljava/lang/Object;)V");
// cp item 09
BASECP = Bytecode.addUtf8(BASECP,"Lorg/apache/bsf/util/event/EventProcessor;");
// cp item 10
BASECP = Bytecode.addClass(BASECP,(short)5); // Class "java/lang/Object"
// cp item 11
BASECP = Bytecode.addClass(BASECP,(short)6); // Class "org/apache/bsf/util/event/EventAdapterImpl"
// cp item 12
BASECP = Bytecode.addClass(BASECP,(short)7); // Class "org/apache/bsf/util/event/EventProcessor"
// cp item 13
BASECP = Bytecode.addNameAndType(BASECP,(short)2,(short)1); // "<init>" "()V"
// cp item 14
BASECP = Bytecode.addNameAndType(BASECP,(short)4,(short)9); // "eventProcessor" "Lorg/apache/bsf/util/event/EventProcessor;"
// cp item 15
BASECP = Bytecode.addFieldRef(BASECP,(short)11,(short)14);
// cp item 16
BASECP = Bytecode.addMethodRef(BASECP,(short)11,(short)13);
// fixed bytes in middle of class file
FIXEDCLASSBYTES = ByteUtility.addBytes(FIXEDCLASSBYTES,(short)0x21); // access_flags (fixed)
FIXEDCLASSBYTES = ByteUtility.addBytes(FIXEDCLASSBYTES,(short)20); // this_class (fixed)
FIXEDCLASSBYTES = ByteUtility.addBytes(FIXEDCLASSBYTES,(short)11); // super_class (fixed)
FIXEDCLASSBYTES = ByteUtility.addBytes(FIXEDCLASSBYTES,(short)1); // interface_count (fixed)
FIXEDCLASSBYTES = ByteUtility.addBytes(FIXEDCLASSBYTES,(short)19); // interfaces (fixed)
FIXEDCLASSBYTES = ByteUtility.addBytes(FIXEDCLASSBYTES,(short)0); // field_count (fixed)
// initialization method, constructor
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(short)1); // access_flags
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(short)2); // name_index "<init>"
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(short)1); // descriptor_index "()V"
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(short)1); // attribute_count
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(short)3); // attribute_name_index "Code"
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(long)17); // attribute_length
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(short)1); // max_stack
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(short)1); // max_locals
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(long)5); // code_length
//code
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(byte)0x2A); // aload_0
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(byte)0xB7); // invokespecial
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(short)16); // method_ref index
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(byte)0xB1); // return
// exception table
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(short)0); // exception_table_length
INITMETHOD = ByteUtility.addBytes(INITMETHOD,(short)0); // attributes_count
}
/* methods that take an EventListener Class Type to create an EventAdapterClass */
public static Class makeEventAdapterClass(Class listenerType,boolean writeClassFile)
{
logger.debug("EventAdapterGenerator");
if( EVENTLISTENER.isAssignableFrom(listenerType) )
{
boolean exceptionable = false;
boolean nonExceptionable = false;
byte constantPool[] = null;
short cpBaseIndex;
short cpCount = 0;
short cpExceptionBaseIndex;
short exceptionableCount;
short nonExceptionableCount;
/* Derive Names */
String listenerTypeName = listenerType.getName();
logger.debug("ListenerTypeName: "+listenerTypeName);
String adapterClassName =
CLASSPACKAGE+
(listenerTypeName.endsWith("Listener")
? listenerTypeName.substring(0, listenerTypeName.length() - 8)
: listenerTypeName).replace('.', '_') +
"Adapter";
String finalAdapterClassName = adapterClassName;
Class cached = null;
int suffixIndex = 0;
do
{
if (null != (cached = ldr.getLoadedClass(finalAdapterClassName)))
{
logger.debug("cached: "+cached);
try
{
if (!listenerType.isAssignableFrom(cached))
finalAdapterClassName = adapterClassName + "_" + suffixIndex++;
else
return cached;
}
catch(VerifyError ex)
{
System.err.println(ex.getMessage());
ex.printStackTrace();
return cached;
}
}
}
while (cached != null);
String eventListenerName = listenerTypeName.replace('.', '/');
/* method stuff */
java.lang.reflect.Method lms[] = listenerType.getMethods();
/* ****************************************************************************************** */
// Listener interface
// Class name
cpCount += 4;
// cp item 17
constantPool = Bytecode.addUtf8(constantPool,eventListenerName);
// cp item 18
constantPool = Bytecode.addUtf8(constantPool,finalAdapterClassName);
// cp item 19
constantPool = Bytecode.addClass(constantPool,(short)17);
// cp item 20
constantPool = Bytecode.addClass(constantPool,(short)18);
// do we have nonExceptionalble event, exceptionable or both
for (int i = 0 ; i < lms.length ; ++i)
{
Class exceptionTypes[] = lms[i].getExceptionTypes();
if( 0 < exceptionTypes.length)
{ exceptionable = true; }
else
{ nonExceptionable = true; }
}/* End for*/
/* ****************************************************************************************** */
// optional inclusion of nonexceptional events affects exceptional events indices
nonExceptionableCount = 0;
if(nonExceptionable)
{
nonExceptionableCount = 3;
cpCount += nonExceptionableCount;
// cp item 21
constantPool = Bytecode.addUtf8(constantPool,"processEvent");
// cp item 22
constantPool = Bytecode.addNameAndType(constantPool,(short)21,(short)8);
// cp item 23
constantPool = Bytecode.addInterfaceMethodRef(constantPool,(short)12,(short)22);
}
/* ****************************************************************************************** */
// optional inclusion of exceptional events affects CP Items which follow for specific methods
exceptionableCount = 0;
if(exceptionable)
{
int classIndex = BASECPCOUNT + cpCount + 1;
int nameIndex = BASECPCOUNT + cpCount + 0;
int natIndex = BASECPCOUNT + cpCount + 3;
exceptionableCount = 5;
cpCount += exceptionableCount;
// cp item 24 or 21
constantPool = Bytecode.addUtf8(constantPool,"processExceptionableEvent");
// cp item 25 or 22
constantPool = Bytecode.addUtf8(constantPool,"java/lang/Exception");
// cp item 26 or 23
constantPool = Bytecode.addClass(constantPool,(short)classIndex);
// cp item 27 or 24
constantPool = Bytecode.addNameAndType(constantPool,(short)nameIndex,(short)8);
// cp item 28 or 25
constantPool = Bytecode.addInterfaceMethodRef(constantPool,(short)12,(short)natIndex);
}
// base index for method cp references
cpBaseIndex = (short)(BASECPCOUNT + cpCount);
logger.debug("cpBaseIndex: " + cpBaseIndex);
for (int i = 0 ; i < lms.length ; ++i)
{
String eventMethodName = lms[i].getName();
String eventName = lms[i].getParameterTypes()[0].getName().replace('.','/');
cpCount += 3;
// cp items for event methods
constantPool = Bytecode.addUtf8(constantPool,eventMethodName);
constantPool = Bytecode.addUtf8(constantPool,("(L" + eventName + ";)V"));
constantPool = Bytecode.addString(constantPool,(short)(BASECPCOUNT+cpCount-3));
}/* End for*/
boolean propertyChangeFlag[] = new boolean[lms.length];
int cpIndexPCE = 0;
for (int i = 0 ; i < lms.length ; ++i)
{
String eventName = lms[i].getParameterTypes()[0].getName().replace('.','/');
// cp items for PropertyChangeEvent special handling
if(eventName.equalsIgnoreCase("java/beans/PropertyChangeEvent"))
{
propertyChangeFlag[i] = true;
if( 0 == cpIndexPCE )
{
constantPool = Bytecode.addUtf8(constantPool,eventName);
constantPool = Bytecode.addUtf8(constantPool,"getPropertyName");
constantPool = Bytecode.addUtf8(constantPool,"()Ljava/lang/String;");
constantPool = Bytecode.addClass(constantPool,(short)(BASECPCOUNT + cpCount));
constantPool = Bytecode.addNameAndType(constantPool,
(short)(BASECPCOUNT + cpCount + 1),
(short)(BASECPCOUNT + cpCount + 2));
constantPool = Bytecode.addMethodRef(constantPool,
(short)(BASECPCOUNT + cpCount + 3),
(short)(BASECPCOUNT + cpCount + 4));
cpCount += 6;
cpIndexPCE = BASECPCOUNT + cpCount - 1;
}
}
else
{ propertyChangeFlag[i] = false; }
}/* End for*/
cpExceptionBaseIndex = (short)(BASECPCOUNT + cpCount);
logger.debug("cpExceptionBaseIndex: " + cpExceptionBaseIndex);
int excpIndex[][] = new int[lms.length][];
for (int i = 0 ; i < lms.length ; ++i)
{
Class exceptionTypes[] = lms[i].getExceptionTypes();
excpIndex[i] = new int[exceptionTypes.length];
for ( int j = 0 ; j < exceptionTypes.length ; j++)
{
constantPool = Bytecode.addUtf8(constantPool,exceptionTypes[j].getName().replace('.', '/'));
constantPool = Bytecode.addClass(constantPool,(short)(BASECPCOUNT+cpCount));
excpIndex[i][j] = BASECPCOUNT + cpCount + 1;
cpCount += 2;
}
}/* End for*/
/* end constant pool */
/* ************************************************************************************************ */
// put the Class byte array together
/* start */
byte newClass[] = CLASSHEADER; // magic, version (fixed)
short count = (short)(BASECPCOUNT + cpCount);
newClass = ByteUtility.addBytes(newClass,count); // constant_pool_count (variable)
newClass = ByteUtility.addBytes(newClass,BASECP); // constant_pool (fixed)
newClass = ByteUtility.addBytes(newClass,constantPool); // constant_pool (variable)
newClass = ByteUtility.addBytes(newClass,FIXEDCLASSBYTES); // see FIXEDCLASSBYTES (fixed)
newClass = ByteUtility.addBytes(newClass,(short)(lms.length+1)); // method_count (variable)
newClass = ByteUtility.addBytes(newClass,INITMETHOD); // constructor <init> (fixed)
// methods
/* ****************************************************************************************** */
/* loop over listener methods from listenerType */
for (int i = 0 ; i < lms.length ; ++i)
{
newClass = ByteUtility.addBytes(newClass,(short)1); // access_flags (fixed)
newClass = ByteUtility.addBytes(newClass,(short)(cpBaseIndex+3*i+0)); // name_index (variable)
newClass = ByteUtility.addBytes(newClass,(short)(cpBaseIndex+3*i+1)); // descriptor_index (variable)
newClass = ByteUtility.addBytes(newClass,(short)1); // attribute_count (fixed)
newClass = ByteUtility.addBytes(newClass,(short)3); // attribute_name_index code(fixed)
// Code Attribute Length
int length = 32;
if( 0 < excpIndex[i].length )
{ length += 5 + 8 * ( 1 + excpIndex[i].length ); }
if(propertyChangeFlag[i])
{ length += 2; }
newClass = ByteUtility.addBytes(newClass,(long)length); // attribute_length (variable)
// start code attribute
newClass = ByteUtility.addBytes(newClass,(short)6); // max_stack (fixed)
newClass = ByteUtility.addBytes(newClass,(short)3); // max_locals (fixed)
// Code Length
length = 20;
if(exceptionable && 0 < excpIndex[i].length)
{ length += 5; }
if(propertyChangeFlag[i])
{ length += 2; }
newClass = ByteUtility.addBytes(newClass,(long)length); // code_length (variable)
// start code
newClass = ByteUtility.addBytes(newClass,(byte)0x2A); // aload_0 (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0xB4); // getfield (fixed)
newClass = ByteUtility.addBytes(newClass,(short)15); // index (fixed)
if(propertyChangeFlag[i])
{ // the propertyName is passed as the first parameter
newClass = ByteUtility.addBytes(newClass,(byte)0x2B); // aload_1 (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0xB6); // invokevirtual (fixed)
newClass = ByteUtility.addBytes(newClass,(short)cpIndexPCE); // methodref (variable)
}
else
{ // the eventMethodName is passed as the first parameter
// Target for method invocation.
newClass = ByteUtility.addBytes(newClass,(byte)0x12); // ldc (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)(cpBaseIndex+3*i+2)); // index (byte) (variable)
}
newClass = ByteUtility.addBytes(newClass,(byte)0x04); // iconst_1 (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0xBD); // anewarray (fixed)
newClass = ByteUtility.addBytes(newClass,(short)10); // Class java/lang/Object (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0x59); // dup (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0x03); // iconst_0 (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0x2B); // aload_1 (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0x53); // aastore (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0xB9); // invokeinterface (fixed)
// index to processEvent or processExceptionableEvent method
length = 23; // actually an index into cp
if(exceptionable && nonExceptionable)
{ // interface method index
if( 0 < lms[i].getExceptionTypes().length )
{ length += 5; }
}
else if(exceptionable)
{ length += 2; }
newClass = ByteUtility.addBytes(newClass,(short)length); // index (process??????...) (variable)
newClass = ByteUtility.addBytes(newClass,(byte)0x03); // iconst_0 (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0x00); // noop (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0xB1); // return (fixed)
if(exceptionable && 0 < excpIndex[i].length)
{ // exception code
newClass = ByteUtility.addBytes(newClass,(byte)0x4D); // astore_2 (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0x2C); // aload_2 (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0xBF); // athrow (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0x57); // pop (fixed)
newClass = ByteUtility.addBytes(newClass,(byte)0xB1); // return (fixed)
// end code
// exception table
length = excpIndex[i].length;
newClass = ByteUtility.addBytes(newClass,(short)(1+length)); // exception_table_length (variable)
for( int j = 0 ; j < length ; j++ )
{ // catch exception types and rethrow
newClass = ByteUtility.addBytes(newClass,(short)0); // start_pc (fixed)
if(propertyChangeFlag[i])
{
newClass = ByteUtility.addBytes(newClass,(short)21); // end_pc (fixed)
newClass = ByteUtility.addBytes(newClass,(short)22); // handler_pc (fixed)
}
else
{
newClass = ByteUtility.addBytes(newClass,(short)19); // end_pc (fixed)
newClass = ByteUtility.addBytes(newClass,(short)20); // handler_pc (fixed)
}
newClass = ByteUtility.addBytes(newClass,(short)excpIndex[i][j]); // catch_type (variable)
}
// catch "exception" and trap it
newClass = ByteUtility.addBytes(newClass,(short)0); // start_pc (fixed)
if(propertyChangeFlag[i])
{
newClass = ByteUtility.addBytes(newClass,(short)21); // end_pc (fixed)
newClass = ByteUtility.addBytes(newClass,(short)25); // handler_pc (fixed)
}
else
{
newClass = ByteUtility.addBytes(newClass,(short)19); // end_pc (fixed)
newClass = ByteUtility.addBytes(newClass,(short)23); // handler_pc (fixed)
}
if(nonExceptionable)
{ newClass = ByteUtility.addBytes(newClass,(short)26); } // catch_type (fixed)
else // or
{ newClass = ByteUtility.addBytes(newClass,(short)23); } // catch_type (fixed)
}
else
{ newClass = ByteUtility.addBytes(newClass,(short)0); } // exception_table_length (fixed)
// attributes on the code attribute (none)
newClass = ByteUtility.addBytes(newClass,(short)0); // attribute_count (fixed)
// end code attribute
}/* End for*/
// Class Attributes (none for this)
newClass = ByteUtility.addBytes(newClass,(short)0); // attribute_count (fixed)
/* done */
logger.debug("adapterName: " + finalAdapterClassName);
logger.debug("cpCount: " + count + " = " + BASECPCOUNT + " + " + cpCount);
logger.debug("methodCount: " + (lms.length+1));
// output to disk class file
/* ****************************************************************************************** */
// now create the class and load it
// return the Class.
if (writeClassFile)
{
try
{
// removed "WRITEDIRECTORY+", as this path is already part of 'finalAdapterClassName'
FileOutputStream fos = new FileOutputStream(finalAdapterClassName+".class");
fos.write(newClass);
fos.close();
}
catch(IOException ex)
{
System.err.println(ex.getMessage());
ex.printStackTrace();
}
try
{
Class ret = ldr.loadClass(finalAdapterClassName);
logger.debug("EventAdapterGenerator: " +
ret.getName() +
" dynamically generated");
return ret;
}
catch (ClassNotFoundException ex)
{
System.err.println(ex.getMessage());
ex.printStackTrace();
}
}
try
{
Class ret = ldr.defineClass(finalAdapterClassName,newClass);
logger.debug("EventAdapterGenerator: " +
ret.getName() +
" dynamically generated");
return ret;
}
catch(Throwable ex) // rgf, 2012-01-15
{
System.err.println(ex.getMessage());
ex.printStackTrace();
}
}
return null;
}
}