|
Java example source code file (hprof_reference.c)
The hprof_reference.c Java example source code/* * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of Oracle nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * This source code is provided to illustrate the usage of a given feature * or technique and has been deliberately simplified. Additional steps * required for a production-quality application, such as security checks, * input validation and proper error handling, might not be present in * this sample code. */ /* Object references table (used in hprof_object.c). */ /* * This table is used by the object table to store object reference * and primitive data information obtained from iterations over the * heap (see hprof_site.c). * * Most of these table entries have no Key, but the key is used to store * the primitive array and primitive field jvalue. None of these entries * are ever looked up, there will be no hash table, use of the * LookupTable was just an easy way to handle a unbounded table of * entries. The object table (see hprof_object.c) will completely * free this reference table after each heap dump or after processing the * references and primitive data. * * The hprof format required this accumulation of all heap iteration * references and primitive data from objects in order to compose an * hprof records for it. * * This file contains detailed understandings of how an hprof CLASS * and INSTANCE dump is constructed, most of this is derived from the * original hprof code, but some has been derived by reading the HAT * code that accepts this format. * */ #include "hprof.h" /* The flavor of data being saved in the RefInfo */ enum { INFO_OBJECT_REF_DATA = 1, INFO_PRIM_FIELD_DATA = 2, INFO_PRIM_ARRAY_DATA = 3 }; /* Reference information, object reference or primitive data information */ typedef struct RefInfo { ObjectIndex object_index; /* If an object reference, the referree index */ jint index; /* If array or field, array or field index */ jint length; /* If array the element count, if not -1 */ RefIndex next; /* The next table element */ unsigned flavor : 8; /* INFO_*, flavor of RefInfo */ unsigned refKind : 8; /* The kind of reference */ unsigned primType : 8; /* If primitive data involved, it's type */ } RefInfo; /* Private internal functions. */ /* Get the RefInfo structure from an entry */ static RefInfo * get_info(RefIndex index) { RefInfo *info; info = (RefInfo*)table_get_info(gdata->reference_table, index); return info; } /* Get a jvalue that was stored as the key. */ static jvalue get_key_value(RefIndex index) { void *key; int len; jvalue value; static jvalue empty_value; key = NULL; table_get_key(gdata->reference_table, index, &key, &len); HPROF_ASSERT(key!=NULL); HPROF_ASSERT(len==(int)sizeof(jvalue)); if ( key != NULL ) { (void)memcpy(&value, key, (int)sizeof(jvalue)); } else { value = empty_value; } return value; } /* Get size of a primitive type */ static jint get_prim_size(jvmtiPrimitiveType primType) { jint size; switch ( primType ) { case JVMTI_PRIMITIVE_TYPE_BOOLEAN: size = (jint)sizeof(jboolean); break; case JVMTI_PRIMITIVE_TYPE_BYTE: size = (jint)sizeof(jbyte); break; case JVMTI_PRIMITIVE_TYPE_CHAR: size = (jint)sizeof(jchar); break; case JVMTI_PRIMITIVE_TYPE_SHORT: size = (jint)sizeof(jshort); break; case JVMTI_PRIMITIVE_TYPE_INT: size = (jint)sizeof(jint); break; case JVMTI_PRIMITIVE_TYPE_FLOAT: size = (jint)sizeof(jfloat); break; case JVMTI_PRIMITIVE_TYPE_LONG: size = (jint)sizeof(jlong); break; case JVMTI_PRIMITIVE_TYPE_DOUBLE: size = (jint)sizeof(jdouble); break; default: HPROF_ASSERT(0); size = 1; break; } return size; } /* Get a void* elements array that was stored as the key. */ static void * get_key_elements(RefIndex index, jvmtiPrimitiveType primType, jint *nelements, jint *nbytes) { void *key; jint byteLen; HPROF_ASSERT(nelements!=NULL); HPROF_ASSERT(nbytes!=NULL); table_get_key(gdata->reference_table, index, &key, &byteLen); HPROF_ASSERT(byteLen>=0); HPROF_ASSERT(byteLen!=0?key!=NULL:key==NULL); *nbytes = byteLen; *nelements = byteLen / get_prim_size(primType); return key; } /* Dump a RefInfo* structure */ static void dump_ref_info(RefInfo *info) { debug_message("[%d]: flavor=%d" ", refKind=%d" ", primType=%d" ", object_index=0x%x" ", length=%d" ", next=0x%x" "\n", info->index, info->flavor, info->refKind, info->primType, info->object_index, info->length, info->next); } /* Dump a RefIndex list */ static void dump_ref_list(RefIndex list) { RefInfo *info; RefIndex index; debug_message("\nFOLLOW REFERENCES RETURNED:\n"); index = list; while ( index != 0 ) { info = get_info(index); dump_ref_info(info); index = info->next; } } /* Dump information about a field and what ref data we had on it */ static void dump_field(FieldInfo *fields, jvalue *fvalues, int n_fields, jint index, jvalue value, jvmtiPrimitiveType primType) { ClassIndex cnum; StringIndex name; StringIndex sig; cnum = fields[index].cnum; name = fields[index].name_index; sig = fields[index].sig_index; debug_message("[%d] %s \"%s\" \"%s\"", index, cnum!=0?string_get(class_get_signature(cnum)):"?", name!=0?string_get(name):"?", sig!=0?string_get(sig):"?"); if ( fields[index].primType!=0 || fields[index].primType!=primType ) { debug_message(" (primType=%d(%c)", fields[index].primType, primTypeToSigChar(fields[index].primType)); if ( primType != fields[index].primType ) { debug_message(", got %d(%c)", primType, primTypeToSigChar(primType)); } debug_message(")"); } else { debug_message("(ty=OBJ)"); } if ( value.j != (jlong)0 || fvalues[index].j != (jlong)0 ) { debug_message(" val=[0x%08x,0x%08x] or [0x%08x,0x%08x]", jlong_high(value.j), jlong_low(value.j), jlong_high(fvalues[index].j), jlong_low(fvalues[index].j)); } debug_message("\n"); } /* Dump all the fields of interest */ static void dump_fields(RefIndex list, FieldInfo *fields, jvalue *fvalues, int n_fields) { int i; debug_message("\nHPROF LIST OF ALL FIELDS:\n"); for ( i = 0 ; i < n_fields ; i++ ) { if ( fields[i].name_index != 0 ) { dump_field(fields, fvalues, n_fields, i, fvalues[i], fields[i].primType); } } dump_ref_list(list); } /* Verify field data */ static void verify_field(RefIndex list, FieldInfo *fields, jvalue *fvalues, int n_fields, jint index, jvalue value, jvmtiPrimitiveType primType) { HPROF_ASSERT(fvalues != NULL); HPROF_ASSERT(n_fields > 0); HPROF_ASSERT(index < n_fields); HPROF_ASSERT(index >= 0 ); if ( primType!=fields[index].primType ) { dump_fields(list, fields, fvalues, n_fields); debug_message("\nPROBLEM WITH:\n"); dump_field(fields, fvalues, n_fields, index, value, primType); debug_message("\n"); HPROF_ERROR(JNI_FALSE, "Trouble with fields and heap data"); } if ( primType == JVMTI_PRIMITIVE_TYPE_BOOLEAN && ( value.b != 1 && value.b != 0 ) ) { dump_fields(list, fields, fvalues, n_fields); debug_message("\nPROBLEM WITH:\n"); dump_field(fields, fvalues, n_fields, index, value, primType); debug_message("\n"); HPROF_ERROR(JNI_FALSE, "Trouble with fields and heap data"); } } /* Fill in a field value, making sure the index is safe */ static void fill_in_field_value(RefIndex list, FieldInfo *fields, jvalue *fvalues, int n_fields, jint index, jvalue value, jvmtiPrimitiveType primType) { HPROF_ASSERT(fvalues != NULL); HPROF_ASSERT(n_fields > 0); HPROF_ASSERT(index < n_fields); HPROF_ASSERT(index >= 0 ); HPROF_ASSERT(fvalues[index].j==(jlong)0); verify_field(list, fields, fvalues, n_fields, index, value, primType); if (index >= 0 && index < n_fields) { fvalues[index] = value; } } /* Walk all references for an ObjectIndex and construct the hprof CLASS dump. */ static void dump_class_and_supers(JNIEnv *env, ObjectIndex object_index, RefIndex list) { SiteIndex site_index; SerialNumber trace_serial_num; RefIndex index; ClassIndex super_cnum; ObjectIndex super_index; LoaderIndex loader_index; ObjectIndex signers_index; ObjectIndex domain_index; FieldInfo *fields; jvalue *fvalues; jint n_fields; jboolean skip_fields; jint n_fields_set; jlong size; ClassIndex cnum; char *sig; ObjectKind kind; TraceIndex trace_index; Stack *cpool_values; ConstantPoolValue *cpool; jint cpool_count; HPROF_ASSERT(object_index!=0); kind = object_get_kind(object_index); if ( kind != OBJECT_CLASS ) { return; } site_index = object_get_site(object_index); HPROF_ASSERT(site_index!=0); cnum = site_get_class_index(site_index); HPROF_ASSERT(cnum!=0); if ( class_get_status(cnum) & CLASS_DUMPED ) { return; } class_add_status(cnum, CLASS_DUMPED); size = (jlong)object_get_size(object_index); super_index = 0; super_cnum = class_get_super(cnum); if ( super_cnum != 0 ) { super_index = class_get_object_index(super_cnum); if ( super_index != 0 ) { dump_class_and_supers(env, super_index, object_get_references(super_index)); } } trace_index = site_get_trace_index(site_index); HPROF_ASSERT(trace_index!=0); trace_serial_num = trace_get_serial_number(trace_index); sig = string_get(class_get_signature(cnum)); loader_index = class_get_loader(cnum); signers_index = 0; domain_index = 0; /* Get field information */ n_fields = 0; skip_fields = JNI_FALSE; n_fields_set = 0; fields = NULL; fvalues = NULL; if ( class_get_all_fields(env, cnum, &n_fields, &fields) == 1 ) { /* Problems getting all the fields, can't trust field index values */ skip_fields = JNI_TRUE; /* Class with no references at all? (ok to be unprepared if list==0?) */ if ( list != 0 ) { /* It is assumed that the reason why we didn't get the fields * was because the class is not prepared. */ if ( gdata->debugflags & DEBUGFLAG_UNPREPARED_CLASSES ) { dump_ref_list(list); debug_message("Unprepared class with references: %s\n", sig); } HPROF_ERROR(JNI_FALSE, "Trouble with unprepared classes"); } /* Why would an unprepared class contain references? */ } if ( n_fields > 0 ) { fvalues = (jvalue*)HPROF_MALLOC(n_fields*(int)sizeof(jvalue)); (void)memset(fvalues, 0, n_fields*(int)sizeof(jvalue)); } /* We use a Stack just because it will automatically expand as needed */ cpool_values = stack_init(16, 16, sizeof(ConstantPoolValue)); cpool = NULL; cpool_count = 0; index = list; while ( index != 0 ) { RefInfo *info; jvalue ovalue; static jvalue empty_value; info = get_info(index); switch ( info->flavor ) { case INFO_OBJECT_REF_DATA: switch ( info->refKind ) { case JVMTI_HEAP_REFERENCE_FIELD: case JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT: /* Should never be seen on a class dump */ HPROF_ASSERT(0); break; case JVMTI_HEAP_REFERENCE_STATIC_FIELD: if ( skip_fields == JNI_TRUE ) { break; } ovalue = empty_value; ovalue.i = info->object_index; fill_in_field_value(list, fields, fvalues, n_fields, info->index, ovalue, 0); n_fields_set++; HPROF_ASSERT(n_fields_set <= n_fields); break; case JVMTI_HEAP_REFERENCE_CONSTANT_POOL: { ConstantPoolValue cpv; ObjectIndex cp_object_index; SiteIndex cp_site_index; ClassIndex cp_cnum; cp_object_index = info->object_index; HPROF_ASSERT(cp_object_index!=0); cp_site_index = object_get_site(cp_object_index); HPROF_ASSERT(cp_site_index!=0); cp_cnum = site_get_class_index(cp_site_index); cpv.constant_pool_index = info->index; cpv.sig_index = class_get_signature(cp_cnum); cpv.value.i = cp_object_index; stack_push(cpool_values, (void*)&cpv); cpool_count++; break; } case JVMTI_HEAP_REFERENCE_SIGNERS: signers_index = info->object_index; break; case JVMTI_HEAP_REFERENCE_PROTECTION_DOMAIN: domain_index = info->object_index; break; case JVMTI_HEAP_REFERENCE_CLASS_LOADER: case JVMTI_HEAP_REFERENCE_INTERFACE: default: /* Ignore, not needed */ break; } break; case INFO_PRIM_FIELD_DATA: if ( skip_fields == JNI_TRUE ) { break; } HPROF_ASSERT(info->primType!=0); HPROF_ASSERT(info->length==-1); HPROF_ASSERT(info->refKind==JVMTI_HEAP_REFERENCE_STATIC_FIELD); ovalue = get_key_value(index); fill_in_field_value(list, fields, fvalues, n_fields, info->index, ovalue, info->primType); n_fields_set++; HPROF_ASSERT(n_fields_set <= n_fields); break; case INFO_PRIM_ARRAY_DATA: default: /* Should never see these */ HPROF_ASSERT(0); break; } index = info->next; } /* Get constant pool data if we have any */ HPROF_ASSERT(cpool_count==stack_depth(cpool_values)); if ( cpool_count > 0 ) { cpool = (ConstantPoolValue*)stack_element(cpool_values, 0); } io_heap_class_dump(cnum, sig, object_index, trace_serial_num, super_index, loader_object_index(env, loader_index), signers_index, domain_index, (jint)size, cpool_count, cpool, n_fields, fields, fvalues); stack_term(cpool_values); if ( fvalues != NULL ) { HPROF_FREE(fvalues); } } /* Walk all references for an ObjectIndex and construct the hprof INST dump. */ static void dump_instance(JNIEnv *env, ObjectIndex object_index, RefIndex list) { jvmtiPrimitiveType primType; SiteIndex site_index; SerialNumber trace_serial_num; RefIndex index; ObjectIndex class_index; jlong size; ClassIndex cnum; char *sig; void *elements; jint num_elements; jint num_bytes; ObjectIndex *values; FieldInfo *fields; jvalue *fvalues; jint n_fields; jboolean skip_fields; jint n_fields_set; ObjectKind kind; TraceIndex trace_index; jboolean is_array; jboolean is_prim_array; HPROF_ASSERT(object_index!=0); kind = object_get_kind(object_index); if ( kind == OBJECT_CLASS ) { return; } site_index = object_get_site(object_index); HPROF_ASSERT(site_index!=0); cnum = site_get_class_index(site_index); HPROF_ASSERT(cnum!=0); size = (jlong)object_get_size(object_index); trace_index = site_get_trace_index(site_index); HPROF_ASSERT(trace_index!=0); trace_serial_num = trace_get_serial_number(trace_index); sig = string_get(class_get_signature(cnum)); class_index = class_get_object_index(cnum); values = NULL; elements = NULL; num_elements = 0; num_bytes = 0; n_fields = 0; skip_fields = JNI_FALSE; n_fields_set = 0; fields = NULL; fvalues = NULL; index = list; is_array = JNI_FALSE; is_prim_array = JNI_FALSE; if ( sig[0] != JVM_SIGNATURE_ARRAY ) { if ( class_get_all_fields(env, cnum, &n_fields, &fields) == 1 ) { /* Trouble getting all the fields, can't trust field index values */ skip_fields = JNI_TRUE; /* It is assumed that the reason why we didn't get the fields * was because the class is not prepared. */ if ( gdata->debugflags & DEBUGFLAG_UNPREPARED_CLASSES ) { if ( list != 0 ) { dump_ref_list(list); debug_message("Instance of unprepared class with refs: %s\n", sig); } else { debug_message("Instance of unprepared class without refs: %s\n", sig); } HPROF_ERROR(JNI_FALSE, "Big Trouble with unprepared class instances"); } } if ( n_fields > 0 ) { fvalues = (jvalue*)HPROF_MALLOC(n_fields*(int)sizeof(jvalue)); (void)memset(fvalues, 0, n_fields*(int)sizeof(jvalue)); } } else { is_array = JNI_TRUE; if ( sig[0] != 0 && sigToPrimSize(sig+1) != 0 ) { is_prim_array = JNI_TRUE; } } while ( index != 0 ) { RefInfo *info; jvalue ovalue; static jvalue empty_value; info = get_info(index); /* Process reference objects, many not used right now. */ switch ( info->flavor ) { case INFO_OBJECT_REF_DATA: switch ( info->refKind ) { case JVMTI_HEAP_REFERENCE_SIGNERS: case JVMTI_HEAP_REFERENCE_PROTECTION_DOMAIN: case JVMTI_HEAP_REFERENCE_CLASS_LOADER: case JVMTI_HEAP_REFERENCE_INTERFACE: case JVMTI_HEAP_REFERENCE_STATIC_FIELD: case JVMTI_HEAP_REFERENCE_CONSTANT_POOL: /* Should never be seen on an instance dump */ HPROF_ASSERT(0); break; case JVMTI_HEAP_REFERENCE_FIELD: if ( skip_fields == JNI_TRUE ) { break; } HPROF_ASSERT(is_array!=JNI_TRUE); ovalue = empty_value; ovalue.i = info->object_index; fill_in_field_value(list, fields, fvalues, n_fields, info->index, ovalue, 0); n_fields_set++; HPROF_ASSERT(n_fields_set <= n_fields); break; case JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT: /* We get each object element one at a time. */ HPROF_ASSERT(is_array==JNI_TRUE); HPROF_ASSERT(is_prim_array!=JNI_TRUE); if ( num_elements <= info->index ) { int nbytes; if ( values == NULL ) { num_elements = info->index + 1; nbytes = num_elements*(int)sizeof(ObjectIndex); values = (ObjectIndex*)HPROF_MALLOC(nbytes); (void)memset(values, 0, nbytes); } else { void *new_values; int new_size; int obytes; obytes = num_elements*(int)sizeof(ObjectIndex); new_size = info->index + 1; nbytes = new_size*(int)sizeof(ObjectIndex); new_values = (void*)HPROF_MALLOC(nbytes); (void)memcpy(new_values, values, obytes); (void)memset(((char*)new_values)+obytes, 0, nbytes-obytes); HPROF_FREE(values); num_elements = new_size; values = new_values; } } HPROF_ASSERT(values[info->index]==0); values[info->index] = info->object_index; break; default: /* Ignore, not needed */ break; } break; case INFO_PRIM_FIELD_DATA: if ( skip_fields == JNI_TRUE ) { break; } HPROF_ASSERT(info->primType!=0); HPROF_ASSERT(info->length==-1); HPROF_ASSERT(info->refKind==JVMTI_HEAP_REFERENCE_FIELD); HPROF_ASSERT(is_array!=JNI_TRUE); ovalue = get_key_value(index); fill_in_field_value(list, fields, fvalues, n_fields, info->index, ovalue, info->primType); n_fields_set++; HPROF_ASSERT(n_fields_set <= n_fields); break; case INFO_PRIM_ARRAY_DATA: /* Should only be one, and it's handled below */ HPROF_ASSERT(info->refKind==0); /* We assert that nothing else was saved with this array */ HPROF_ASSERT(index==list&&info->next==0); HPROF_ASSERT(is_array==JNI_TRUE); HPROF_ASSERT(is_prim_array==JNI_TRUE); primType = info->primType; elements = get_key_elements(index, primType, &num_elements, &num_bytes); HPROF_ASSERT(info->length==num_elements); size = num_bytes; break; default: HPROF_ASSERT(0); break; } index = info->next; } if ( is_array == JNI_TRUE ) { if ( is_prim_array == JNI_TRUE ) { HPROF_ASSERT(values==NULL); io_heap_prim_array(object_index, trace_serial_num, (jint)size, num_elements, sig, elements); } else { HPROF_ASSERT(elements==NULL); io_heap_object_array(object_index, trace_serial_num, (jint)size, num_elements, sig, values, class_index); } } else { io_heap_instance_dump(cnum, object_index, trace_serial_num, class_index, (jint)size, sig, fields, fvalues, n_fields); } if ( values != NULL ) { HPROF_FREE(values); } if ( fvalues != NULL ) { HPROF_FREE(fvalues); } if ( elements != NULL ) { /* Do NOT free elements, it's a key in the table, leave it be */ } } /* External interfaces. */ void reference_init(void) { HPROF_ASSERT(gdata->reference_table==NULL); gdata->reference_table = table_initialize("Ref", 2048, 4096, 0, (int)sizeof(RefInfo)); } /* Save away a reference to an object */ RefIndex reference_obj(RefIndex next, jvmtiHeapReferenceKind refKind, ObjectIndex object_index, jint index, jint length) { static RefInfo empty_info; RefIndex entry; RefInfo info; info = empty_info; info.flavor = INFO_OBJECT_REF_DATA; info.refKind = refKind; info.object_index = object_index; info.index = index; info.length = length; info.next = next; entry = table_create_entry(gdata->reference_table, NULL, 0, (void*)&info); return entry; } /* Save away some primitive field data */ RefIndex reference_prim_field(RefIndex next, jvmtiHeapReferenceKind refKind, jvmtiPrimitiveType primType, jvalue field_value, jint field_index) { static RefInfo empty_info; RefIndex entry; RefInfo info; HPROF_ASSERT(primType==JVMTI_PRIMITIVE_TYPE_BOOLEAN?(field_value.b==1||field_value.b==0):1); info = empty_info; info.flavor = INFO_PRIM_FIELD_DATA; info.refKind = refKind; info.primType = primType; info.index = field_index; info.length = -1; info.next = next; entry = table_create_entry(gdata->reference_table, (void*)&field_value, (int)sizeof(jvalue), (void*)&info); return entry; } /* Save away some primitive array data */ RefIndex reference_prim_array(RefIndex next, jvmtiPrimitiveType primType, const void *elements, jint elementCount) { static RefInfo empty_info; RefIndex entry; RefInfo info; HPROF_ASSERT(next == 0); HPROF_ASSERT(elementCount >= 0); HPROF_ASSERT(elements != NULL); info = empty_info; info.flavor = INFO_PRIM_ARRAY_DATA; info.refKind = 0; info.primType = primType; info.index = 0; info.length = elementCount; info.next = next; entry = table_create_entry(gdata->reference_table, (void*)elements, elementCount * get_prim_size(primType), (void*)&info); return entry; } void reference_cleanup(void) { if ( gdata->reference_table == NULL ) { return; } table_cleanup(gdata->reference_table, NULL, NULL); gdata->reference_table = NULL; } void reference_dump_instance(JNIEnv *env, ObjectIndex object_index, RefIndex list) { dump_instance(env, object_index, list); } void reference_dump_class(JNIEnv *env, ObjectIndex object_index, RefIndex list) { dump_class_and_supers(env, object_index, list); } Other Java examples (source code examples)Here is a short list of links related to this Java hprof_reference.c source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.