static jbyteArray getThumbnail(JNIEnv *env, jobject jobj, jstring jfilename)
	{
		LOGI("getThumbnail");

		const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);

		if (filename)
		{
			loadExifInfo(filename, FALSE);
			Section_t* ExifSection = FindSection(M_EXIF);
			if (ExifSection == NULL || ImageInfo.ThumbnailSize == 0)
			{
				LOGE("no exif section or size == 0, so no thumbnail\n");
				goto noThumbnail;
			}
			uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
			jbyteArray byteArray = (*env)->NewByteArray(env, ImageInfo.ThumbnailSize);
			if (byteArray == NULL)
			{
				LOGE("couldn't allocate thumbnail memory, so no thumbnail\n");
				goto noThumbnail;
			}
			(*env)->SetByteArrayRegion(env, byteArray, 0, ImageInfo.ThumbnailSize, thumbnailPointer);
			LOGD("thumbnail size %d\n", ImageInfo.ThumbnailSize);
			(*env)->ReleaseStringUTFChars(env, jfilename, filename);
			DiscardData();
			return byteArray;
		}
		noThumbnail: if (filename)
		{
			(*env)->ReleaseStringUTFChars(env, jfilename, filename);
		}
		DiscardData();
		return NULL;
	}
static jlongArray getThumbnailRange(JNIEnv *env, jobject jobj, jstring jfilename) {
    jlongArray resultArray = NULL;
    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
    if (filename) {
        loadExifInfo(filename, FALSE);
        Section_t* ExifSection = FindSection(M_EXIF);
        if (ExifSection == NULL || ImageInfo.ThumbnailSize == 0) {
            goto done;
        }

        jlong result[2];
        result[0] = ExifSection->Offset + ImageInfo.ThumbnailOffset + 8;
        result[1] = ImageInfo.ThumbnailSize;

        resultArray = (*env)->NewLongArray(env, 2);
        if (resultArray == NULL) {
            goto done;
        }

        (*env)->SetLongArrayRegion(env, resultArray, 0, 2, result);
    }
done:
    if (filename) {
        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
    }
    DiscardData();
    return resultArray;
}
	static jstring getAttributes(JNIEnv *env, jobject jobj, jstring jfilename)
	{
		LOGI("******************************** getAttributes");

		const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
		LOGD("filename: %s", filename);

		loadExifInfo(filename, FALSE);

		// release the string
		(*env)->ReleaseStringUTFChars(env, jfilename, filename);

		attributeCount = 0;
		int bufLen = 1000;

		char* buf = malloc(bufLen);
		if (buf == NULL)
		{
			return NULL;
		}
		*buf = 0; // start the string out at zero length

		// ShowImageInfo(TRUE);

		// parse all attributes

		// thumbnail
		bufLen = addKeyValueString(&buf, bufLen, "hasThumbnail", ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE || ImageInfo.ThumbnailSize == 0 ? "false" : "true");
		if (bufLen == 0) return NULL;

		if( ImageInfo.Make[0] )
		{
			bufLen = addKeyValueString( &buf, bufLen, "Make", ImageInfo.Make );
			if( bufLen == 0 ) return NULL;
		}

		if( ImageInfo.Model[0] )
		{
			bufLen = addKeyValueString( &buf, bufLen, "Model", ImageInfo.Model );
			if( bufLen == 0 ) return NULL;
		}

		if( ImageInfo.DateTime[0] )
		{
			bufLen = addKeyValueString( &buf, bufLen, "DateTime", ImageInfo.DateTime );
			if( bufLen == 0 ) return NULL;
		}

		if( ImageInfo.DateTimeDigitized[0] )
		{
			bufLen = addKeyValueString( &buf, bufLen, "DateTimeDigitized", ImageInfo.DateTimeDigitized );
			if( bufLen == 0 ) return NULL;
		}

		if( ImageInfo.DateTimeOriginal[0] )
		{
			bufLen = addKeyValueString( &buf, bufLen, "DateTimeOriginal", ImageInfo.DateTimeOriginal );
			if( bufLen == 0 ) return NULL;
		}

		if( ImageInfo.Copyright[0] )
		{
			bufLen = addKeyValueString( &buf, bufLen, "Copyright", ImageInfo.Copyright );
			if( bufLen == 0 ) return NULL;
		}

		if( ImageInfo.Artist[0] )
		{
			bufLen = addKeyValueString( &buf, bufLen, "Artist", ImageInfo.Artist );
			if( bufLen == 0 ) return NULL;
		}

		if( ImageInfo.Software[0] )
		{
			bufLen = addKeyValueString( &buf, bufLen, "Software", ImageInfo.Software );
			if( bufLen == 0 ) return NULL;
		}

		if( ImageInfo.ImageWidth > 0 && ImageInfo.ImageLength > 0 )
		{
			bufLen = addKeyValueInt(&buf, bufLen, "ImageWidth", ImageInfo.ImageWidth);
			if (bufLen == 0) return NULL;

			bufLen = addKeyValueInt(&buf, bufLen, "ImageLength", ImageInfo.ImageLength);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.Orientation >= 0 )
		{
			bufLen = addKeyValueInt(&buf, bufLen, "Orientation", ImageInfo.Orientation);
			if (bufLen == 0) return NULL;
		}

		if (ImageInfo.Flash >= 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "Flash", ImageInfo.Flash);
			if (bufLen == 0) return NULL;
		}

		if (ImageInfo.FocalLength)
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "FocalLength", ImageInfo.FocalLength, "%4.2f");
			if (bufLen == 0) return NULL;
		}

		if (ImageInfo.ExposureTime)
		{
			const char* format;
			if (ImageInfo.ExposureTime < 0.010)
			{
				format = "%6.4f";
			} else
			{
				format = "%5.3f";
			}

			bufLen = addKeyValueDouble(&buf, bufLen, "ExposureTime", (double)ImageInfo.ExposureTime, format);
			if (bufLen == 0) return NULL;
		}

		if (ImageInfo.FNumber > 0)
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "FNumber", (double)ImageInfo.FNumber, "%3.1f");
			if (bufLen == 0) return NULL;
		}

		if( ImageInfo.ApertureValue )
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "ApertureValue", (double)ImageInfo.ApertureValue, "%4.2f");
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.BrightnessValue )
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "BrightnessValue", (double)ImageInfo.BrightnessValue, "%4.2f");
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.MaxApertureValue)
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "MaxApertureValue", (double)ImageInfo.MaxApertureValue, "%4.2f");
			if (bufLen == 0) return NULL;
		}

		if (ImageInfo.SubjectDistance)
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "SubjectDistance", (double)ImageInfo.SubjectDistance, "%4.2f");
			if (bufLen == 0) return NULL;
		}

		if (ImageInfo.ExposureBiasValue)
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "ExposureBiasValue", (double)ImageInfo.ExposureBiasValue, "%4.2f");
			if (bufLen == 0) return NULL;
		}

		if (ImageInfo.DigitalZoomRatio > 1.0)
		{
			// Digital zoom used.  Shame on you! (LOL)
			bufLen = addKeyValueDouble(&buf, bufLen, "DigitalZoomRatio", ImageInfo.DigitalZoomRatio, "%2.3f");
			if (bufLen == 0) return NULL;
		}

		if( ImageInfo.FocalLengthIn35mmFilm )
		{
			bufLen = addKeyValueInt(&buf, bufLen, "FocalLengthIn35mmFilm", ImageInfo.FocalLengthIn35mmFilm);
			if (bufLen == 0) return NULL;
		}

		if( ImageInfo.SensingMethod > 0 )
		{
			bufLen = addKeyValueInt(&buf, bufLen, "SensingMethod", ImageInfo.SensingMethod);
			if (bufLen == 0) return NULL;
		}

		if( ImageInfo.Whitebalance >= 0 && ImageInfo.Whitebalance <= 1 )
		{
			bufLen = addKeyValueInt(&buf, bufLen, "Whitebalance", ImageInfo.Whitebalance);
			if (bufLen == 0) return NULL;
		}

		if( ImageInfo.MeteringMode > 0 && ImageInfo.MeteringMode <= 255)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "MeteringMode", ImageInfo.MeteringMode);
			if (bufLen == 0) return NULL;
		}

//		if(ImageInfo.CompressedBitsPerPixel)
//		{
//			bufLen = addKeyValueDouble(&buf, bufLen, "CompressedBitsPerPixel", (double)ImageInfo.CompressedBitsPerPixel, "%4.2f");
//			if (bufLen == 0) return NULL;
//		}

		if (ImageInfo.ExposureProgram > 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "ExposureProgram", ImageInfo.ExposureProgram);
			if (bufLen == 0) return NULL;
		}

		if (ImageInfo.ExposureMode >= 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "ExposureMode", ImageInfo.ExposureMode);
			if (bufLen == 0) return NULL;
		}

		if (ImageInfo.ISOSpeedRatings)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "ISOSpeedRatings", ImageInfo.ISOSpeedRatings);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.LightSource >= 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "LightSource", ImageInfo.LightSource);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.SubjectDistanceRange > 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "SubjectDistanceRange", ImageInfo.SubjectDistanceRange);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.XResolution)
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "XResolution", ImageInfo.XResolution, "%.2f");
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.YResolution)
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "YResolution", ImageInfo.YResolution, "%.2f");
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.ResolutionUnit)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "ResolutionUnit", ImageInfo.ResolutionUnit);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.FocalPlaneXResolution)
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "FocalPlaneXResolution", ImageInfo.FocalPlaneXResolution, "%.4f");
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.FocalPlaneYResolution)
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "FocalPlaneYResolution", ImageInfo.FocalPlaneYResolution, "%.4f");
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.FocalPlaneResolutionUnit)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "FocalPlaneResolutionUnit", ImageInfo.FocalPlaneResolutionUnit);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.PixelXDimension)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "PixelXDimension", ImageInfo.PixelXDimension);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.PixelYDimension)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "PixelYDimension", ImageInfo.PixelYDimension);
			if (bufLen == 0) return NULL;
		}

		if (ImageInfo.QualityGuess)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "QualityGuess", ImageInfo.QualityGuess);
			if (bufLen == 0) return NULL;
		}

		if( ImageInfo.SceneCaptureType >= 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "SceneCaptureType", ImageInfo.SceneCaptureType);
			if (bufLen == 0) return NULL;
		}

		if( ImageInfo.ShutterSpeedValue)
		{
			bufLen = addKeyValueDouble(&buf, bufLen, "ShutterSpeedValue", ImageInfo.ShutterSpeedValue, "%.4f");
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.ExifVersion[0])
		{
			bufLen = addKeyValueBytes(&buf, bufLen, "ExifVersion", ImageInfo.ExifVersion, 4);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.ColorSpace > 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "ColorSpace", ImageInfo.ColorSpace);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.Compression > 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "Compression", ImageInfo.Compression);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.Process)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "Process", ImageInfo.Process);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.FileDateTime > 0)
		{
			char filedatetime[20];
			FileTimeAsString( filedatetime );
			bufLen = addKeyValueString( &buf, bufLen, "FileDateTime", filedatetime );
			if( bufLen == 0 ) return NULL;
		}

		if(ImageInfo.FileSize)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "FileSize", ImageInfo.FileSize);
			if (bufLen == 0) return NULL;
		}

		if( ImageInfo.Sharpness >= 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "Sharpness", ImageInfo.Sharpness);
			if (bufLen == 0) return NULL;
		}

		if( ImageInfo.Contrast >= 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "Contrast", ImageInfo.Contrast);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.Saturation >= 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "Saturation", ImageInfo.Saturation);
			if (bufLen == 0) return NULL;
		}

		if(ImageInfo.GainControl >= 0)
		{
			bufLen = addKeyValueInt(&buf, bufLen, "GainControl", ImageInfo.GainControl);
			if (bufLen == 0) return NULL;
		}


		// GPS
		if (ImageInfo.GpsInfoPresent)
		{
			if (ImageInfo.GpsLatitudeRef[0])
			{
				bufLen = addKeyValueString(&buf, bufLen, "GpsLatitudeRef", ImageInfo.GpsLatitudeRef);
				if (bufLen == 0) return NULL;
			}

			if (ImageInfo.GpsLongitudeRef[0])
			{
				bufLen = addKeyValueString(&buf, bufLen, "GpsLongitudeRef", ImageInfo.GpsLongitudeRef);
				if (bufLen == 0) return NULL;
			}

			if (ImageInfo.GpsAltitudeRef == 0 || ImageInfo.GpsAltitudeRef == 1)
			{
				bufLen = addKeyValueInt(&buf, bufLen, "GpsAltitudeRef", ImageInfo.GpsAltitudeRef);
				if (bufLen == 0) return NULL;
			}

			if (ImageInfo.GpsLatitude[0])
			{
				bufLen = addKeyValueString(&buf, bufLen, "GpsLatitude", ImageInfo.GpsLatitude);
				if (bufLen == 0) return NULL;
			}

			if (ImageInfo.GpsLongitude[0])
			{
				bufLen = addKeyValueString(&buf, bufLen, "GpsLongitude", ImageInfo.GpsLongitude);
				if (bufLen == 0) return NULL;
			}

			if (ImageInfo.GpsAltitude[0])
			{
				bufLen = addKeyValueString(&buf, bufLen, "GpsAltitude", ImageInfo.GpsAltitude);
				if (bufLen == 0) return NULL;
			}

			if( ImageInfo.GpsSpeedRef[0])
			{
				bufLen = addKeyValueString(&buf, bufLen, "GpsSpeedRef", ImageInfo.GpsSpeedRef);
				if (bufLen == 0) return NULL;
			}

			if (ImageInfo.GpsSpeed[0])
			{
				bufLen = addKeyValueString(&buf, bufLen, "GpsSpeed", ImageInfo.GpsSpeed);
				if (bufLen == 0) return NULL;
			}
		}

		// end parsing
		LOGD("final buffer: %s", buf);

		// put the attribute count at the beginnnig of the string
		int finalBufLen = strlen(buf) + 20;
		char* finalResult = malloc(finalBufLen);
		if (finalResult == NULL)
		{
			free(buf);
			return NULL;
		}
		snprintf(finalResult, finalBufLen, "%d %s", attributeCount, buf);
		int k;
		for (k = 0; k < finalBufLen; k++)
		if (!isascii(finalResult[k]))
		finalResult[k] = '?';
		free(buf);

		LOGD("*********Returning result \"%s\"", finalResult);
		jstring result = ((*env)->NewStringUTF(env, finalResult));
		free(finalResult);
		DiscardData();
		return result;
	}
	static void saveAttributes(JNIEnv *env, jobject jobj, jstring jfilename, jstring jattributes)
	{
		LOGI("******************************** saveAttributes");

		// format of attributes string passed from java:
		// "attrCnt attr1=valueLen value1attr2=value2Len value2..."
		// example input: "4 ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"

		ExifElement_t* exifElementTable = NULL;
		const char* filename = NULL;
		uchar* thumbnailData = NULL;
		int attrCnt = 0;
		const char* attributes = (*env)->GetStringUTFChars(env, jattributes, NULL);
		if (attributes == NULL)
		{
			goto exit;
		}

		// Get the number of attributes - it's the first number in the string.
		attrCnt = atoi(attributes);
		char* attrPtr = strchr(attributes, ' ') + 1;

		LOGD("attribute count %d attrPtr %s\n", attrCnt, attrPtr);

		// Load all the hash exif elements into a more c-like structure
		exifElementTable = malloc(sizeof(ExifElement_t) * attrCnt);
		if (exifElementTable == NULL)
		{
			goto exit;
		}


		int i;
		char tag[100];
		int hasDateTimeTag = FALSE;
		int gpsTagCount = 0;
		int exifTagCount = 0;
		int tagValue;
		int tagFound;
		ExifElement_t* item;

		for (i = 0; i < attrCnt; i++)
		{
			// get an element from the attribute string and add it to the c structure
			// first, extract the attribute name
			tagFound = 0;
			char* tagEnd = strchr(attrPtr, '=');
			if (tagEnd == 0)
			{
				LOGE("saveAttributes: couldn't find end of tag");
				goto exit;
			}
			if (tagEnd - attrPtr > 99)
			{
				LOGE("saveAttributes: attribute tag way too long");
				goto exit;
			}

			memcpy(tag, attrPtr, tagEnd - attrPtr);
			tag[tagEnd - attrPtr] = 0;

			exifElementTable[i].Format = 0;
			exifElementTable[i].Tag = 0;
			exifElementTable[i].GpsTag = FALSE;

			if (IsGpsTag(tag))
			{
				tagValue = GpsTagNameToValue(tag);
				if( tagValue > -1 )
				{
					LOGV("Tag '%s' with value: X%x", tag, tagValue);
					exifElementTable[i].GpsTag = TRUE;
					exifElementTable[i].Tag = GpsTagNameToValue(tag);
					++gpsTagCount;
					tagFound = 1;
				} else {
					LOGE("(GPS) Skipping gps tag: %s = %i", tag, tagValue);
				}
			} else
			{
				tagValue = TagNameToValue(tag);
				if( tagValue > -1 )
				{
					LOGV("Tag '%s' with value: X%x", tag, tagValue);
					exifElementTable[i].GpsTag = FALSE;
					exifElementTable[i].Tag = tagValue;
					++exifTagCount;
					tagFound = 1;
				} else {
					LOGE("(EXIF) Skipping tag %s = %i", tag, tagValue);
				}
			}

			LOGV("tagFound: %i", tagFound);

			attrPtr = tagEnd + 1;
			// next get the length of the attribute value
			int valueLen = atoi(attrPtr);

			if (IsDateTimeTag(exifElementTable[i].Tag))
			{
				hasDateTimeTag = TRUE;
			}


			attrPtr = strchr(attrPtr, ' ') + 1;
			if (attrPtr == 0)
			{
				LOGE("saveAttributes: couldn't find end of value len");
				goto exit;
			}


			exifElementTable[i].Value = malloc(valueLen + 1);
			if (exifElementTable[i].Value == NULL)
			{
				goto exit;
			}

			memcpy(exifElementTable[i].Value, attrPtr, valueLen);
			exifElementTable[i].Value[valueLen] = 0;
			exifElementTable[i].DataLength = valueLen;

			attrPtr += valueLen;

			LOGD("tag %s id %d value %s data length=%d isGps=%d", tag, exifElementTable[i].Tag,
					exifElementTable[i].Value, exifElementTable[i].DataLength, exifElementTable[i].GpsTag);
		}

		LOGD("Total tags: %i - %i ( total was: %i )", exifTagCount, gpsTagCount, attrCnt);

		filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
		LOGD("Call loadAttributes() with filename is %s. Loading exif info\n", filename);
		loadExifInfo(filename, TRUE);

		// DEBUG ONLY ----------
		// ShowTags = TRUE;
		// ShowImageInfo(TRUE);
		// LOGD("create exif 2");
		// ---------------------

		// If the jpg file has a thumbnail, preserve it.
		int thumbnailLength = ImageInfo.ThumbnailSize;
		if (ImageInfo.ThumbnailOffset)
		{
			Section_t* ExifSection = FindSection(M_EXIF);
			if (ExifSection)
			{
				uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
				thumbnailData = (uchar*)malloc(ImageInfo.ThumbnailSize);
				// if the malloc fails, we just won't copy the thumbnail
				if (thumbnailData)
				{
					memcpy(thumbnailData, thumbnailPointer, thumbnailLength);
				}
			}
		}

		create_EXIF_Elements(exifElementTable, exifTagCount, gpsTagCount, attrCnt, hasDateTimeTag);

		if (thumbnailData)
		{
			copyThumbnailData(thumbnailData, thumbnailLength);
		}

		exit:
		LOGE("cleaning up now in saveAttributes");
		// try to clean up resources
		if (attributes)
		{
			(*env)->ReleaseStringUTFChars(env, jattributes, attributes);
		}
		if (filename)
		{
			(*env)->ReleaseStringUTFChars(env, jfilename, filename);
		}
		if (exifElementTable)
		{
			// free the table
			for (i = 0; i < attrCnt; i++)
			{
				free(exifElementTable[i].Value);
			}
			free(exifElementTable);
		}
		if (thumbnailData)
		{
			free(thumbnailData);
		}

		LOGD("returning from saveAttributes");
	}
static jstring getAttributes(JNIEnv *env, jobject jobj, jstring jfilename)
{
#ifdef SUPERDEBUG
    LOGE("******************************** getAttributes\n");
#endif
    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
    loadExifInfo(filename, FALSE);
#ifdef SUPERDEBUG
    ShowImageInfo(TRUE);
#endif
    (*env)->ReleaseStringUTFChars(env, jfilename, filename);

    attributeCount = 0;
#ifdef REALLOCTEST
    int bufLen = 5;
#else
    int bufLen = 1000;
#endif
    char* buf = malloc(bufLen);
    if (buf == NULL) {
        return NULL;
    }
    *buf = 0;   // start the string out at zero length

    // save a fake "hasThumbnail" tag to pass to the java ExifInterface
    bufLen = addKeyValueString(&buf, bufLen, "hasThumbnail",
        ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE || ImageInfo.ThumbnailSize == 0 ?
            "false" : "true");
    if (bufLen == 0) return NULL;

    if (ImageInfo.CameraMake[0]) {
        bufLen = addKeyValueString(&buf, bufLen, "Make", ImageInfo.CameraMake);
        if (bufLen == 0) return NULL;
    }
    if (ImageInfo.CameraModel[0]) {
        bufLen = addKeyValueString(&buf, bufLen, "Model", ImageInfo.CameraModel);
        if (bufLen == 0) return NULL;
    }
    if (ImageInfo.DateTime[0]) {
        bufLen = addKeyValueString(&buf, bufLen, "DateTime", ImageInfo.DateTime);
        if (bufLen == 0) return NULL;
    }
    bufLen = addKeyValueInt(&buf, bufLen, "ImageWidth", ImageInfo.Width);
    if (bufLen == 0) return NULL;

    bufLen = addKeyValueInt(&buf, bufLen, "ImageLength", ImageInfo.Height);
    if (bufLen == 0) return NULL;

    bufLen = addKeyValueInt(&buf, bufLen, "Orientation", ImageInfo.Orientation);
    if (bufLen == 0) return NULL;

    bufLen = addKeyValueInt(&buf, bufLen, "Flash", ImageInfo.FlashUsed);
    if (bufLen == 0) return NULL;

    if (ImageInfo.FocalLength.num != 0 && ImageInfo.FocalLength.denom != 0) {
        bufLen = addKeyValueRational(&buf, bufLen, "FocalLength", ImageInfo.FocalLength);
        if (bufLen == 0) return NULL;
    }

    if (ImageInfo.DigitalZoomRatio > 1.0){
        // Digital zoom used.  Shame on you!
        bufLen = addKeyValueDouble(&buf, bufLen, "DigitalZoomRatio", ImageInfo.DigitalZoomRatio, "%1.3f");
        if (bufLen == 0) return NULL;
    }

    if (ImageInfo.ExposureTime){
        const char* format;
        if (ImageInfo.ExposureTime < 0.010){
            format = "%6.4f";
        } else {
            format = "%5.3f";
        }

        bufLen = addKeyValueDouble(&buf, bufLen, "ExposureTime", (double)ImageInfo.ExposureTime, format);
        if (bufLen == 0) return NULL;
    }

    if (ImageInfo.ApertureFNumber){
        bufLen = addKeyValueDouble(&buf, bufLen, "FNumber", (double)ImageInfo.ApertureFNumber, "%3.1f");
        if (bufLen == 0) return NULL;
    }

    if (ImageInfo.Distance){
        bufLen = addKeyValueDouble(&buf, bufLen, "SubjectDistance", (double)ImageInfo.Distance, "%4.2f");
        if (bufLen == 0) return NULL;
    }

    if (ImageInfo.ISOequivalent){
        bufLen = addKeyValueInt(&buf, bufLen, "ISOSpeedRatings", ImageInfo.ISOequivalent);
        if (bufLen == 0) return NULL;
    }

    if (ImageInfo.ExposureBias){
        // If exposure bias was specified, but set to zero, presumably its no bias at all,
        // so only show it if its nonzero.
        bufLen = addKeyValueDouble(&buf, bufLen, "ExposureBiasValue", (double)ImageInfo.ExposureBias, "%4.2f");
        if (bufLen == 0) return NULL;
    }

    bufLen = addKeyValueInt(&buf, bufLen, "WhiteBalance", ImageInfo.Whitebalance);
    if (bufLen == 0) return NULL;

    bufLen = addKeyValueInt(&buf, bufLen, "LightSource", ImageInfo.LightSource);
    if (bufLen == 0) return NULL;


    if (ImageInfo.MeteringMode) {
        bufLen = addKeyValueInt(&buf, bufLen, "MeteringMode", ImageInfo.MeteringMode);
        if (bufLen == 0) return NULL;
    }

    if (ImageInfo.ExposureProgram) {
        bufLen = addKeyValueInt(&buf, bufLen, "ExposureProgram", ImageInfo.ExposureProgram);
        if (bufLen == 0) return NULL;
    }

    if (ImageInfo.ExposureMode) {
        bufLen = addKeyValueInt(&buf, bufLen, "ExposureMode", ImageInfo.ExposureMode);
        if (bufLen == 0) return NULL;
    }

    if (ImageInfo.GpsInfoPresent) {
        if (ImageInfo.GpsLatRaw[0]) {
            bufLen = addKeyValueString(&buf, bufLen, "GPSLatitude", ImageInfo.GpsLatRaw);
            if (bufLen == 0) return NULL;
        }
        if (ImageInfo.GpsLatRef[0]) {
            bufLen = addKeyValueString(&buf, bufLen, "GPSLatitudeRef", ImageInfo.GpsLatRef);
            if (bufLen == 0) return NULL;
        }
        if (ImageInfo.GpsLongRaw[0]) {
            bufLen = addKeyValueString(&buf, bufLen, "GPSLongitude", ImageInfo.GpsLongRaw);
            if (bufLen == 0) return NULL;
        }
        if (ImageInfo.GpsLongRef[0]) {
            bufLen = addKeyValueString(&buf, bufLen, "GPSLongitudeRef", ImageInfo.GpsLongRef);
            if (bufLen == 0) return NULL;
        }
        if (ImageInfo.GpsAlt[0]) {
            bufLen = addKeyValueRational(&buf, bufLen, "GPSAltitude", ImageInfo.GpsAltRaw);
            bufLen = addKeyValueInt(&buf, bufLen, "GPSAltitudeRef", ImageInfo.GpsAltRef);
            if (bufLen == 0) return NULL;
        }
        if (ImageInfo.GpsDateStamp[0]) {
            bufLen = addKeyValueString(&buf, bufLen, "GPSDateStamp", ImageInfo.GpsDateStamp);
            if (bufLen == 0) return NULL;
        }
        if (ImageInfo.GpsTimeStamp[0]) {
            bufLen = addKeyValueString(&buf, bufLen, "GPSTimeStamp", ImageInfo.GpsTimeStamp);
            if (bufLen == 0) return NULL;
        }
        if (ImageInfo.GpsProcessingMethod[0]) {
            bufLen = addKeyValueString(&buf, bufLen, "GPSProcessingMethod", ImageInfo.GpsProcessingMethod);
            if (bufLen == 0) return NULL;
        }
    }

    if (ImageInfo.Comments[0]) {
        bufLen = addKeyValueString(&buf, bufLen, "UserComment", ImageInfo.Comments);
        if (bufLen == 0) return NULL;
    }

    // put the attribute count at the beginnnig of the string
    int finalBufLen = strlen(buf) + 20;
    char* finalResult = malloc(finalBufLen);
    if (finalResult == NULL) {
        free(buf);
        return NULL;
    }
    snprintf(finalResult, finalBufLen, "%d %s", attributeCount, buf);
    int k;
    for (k = 0; k < finalBufLen; k++)
        if (finalResult[k] > 127)
            finalResult[k] = '?';
    free(buf);

#ifdef SUPERDEBUG
    LOGE("*********Returning result \"%s\"", finalResult);
#endif
    jstring result = ((*env)->NewStringUTF(env, finalResult));
    free(finalResult);
    DiscardData();
    return result;
}
static void saveAttributes(JNIEnv *env, jobject jobj, jstring jfilename, jstring jattributes)
{
#ifdef SUPERDEBUG
    LOGE("******************************** saveAttributes\n");
#endif
    // format of attributes string passed from java:
    // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
    // example input: "4 ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
    ExifElement_t* exifElementTable = NULL;
    const char* filename = NULL;
    uchar* thumbnailData = NULL;
    int attrCnt = 0;
    const char* attributes = (*env)->GetStringUTFChars(env, jattributes, NULL);
    if (attributes == NULL) {
        goto exit;
    }
#ifdef SUPERDEBUG
    LOGE("attributes %s\n", attributes);
#endif

    // Get the number of attributes - it's the first number in the string.
    attrCnt = atoi(attributes);
    char* attrPtr = strchr(attributes, ' ') + 1;
#ifdef SUPERDEBUG
    LOGE("attribute count %d attrPtr %s\n", attrCnt, attrPtr);
#endif

    // Load all the hash exif elements into a more c-like structure
    exifElementTable = malloc(sizeof(ExifElement_t) * attrCnt);
    if (exifElementTable == NULL) {
        goto exit;
    }
#ifdef OUTOFMEMORYTEST1
    goto exit;
#endif

    int i;
    char tag[100];
    int gpsTagCount = 0;
    int exifTagCount = 0;

    for (i = 0; i < attrCnt; i++) {
        // get an element from the attribute string and add it to the c structure
        // first, extract the attribute name
        char* tagEnd = strchr(attrPtr, '=');
        if (tagEnd == 0) {
#ifdef SUPERDEBUG
            LOGE("saveAttributes: couldn't find end of tag");
#endif
            goto exit;
        }
        if (tagEnd - attrPtr > 99) {
#ifdef SUPERDEBUG
            LOGE("saveAttributes: attribute tag way too long");
#endif
            goto exit;
        }
        memcpy(tag, attrPtr, tagEnd - attrPtr);
        tag[tagEnd - attrPtr] = 0;

        if (IsGpsTag(tag)) {
            exifElementTable[i].GpsTag = TRUE;
            exifElementTable[i].Tag = GpsTagNameToValue(tag);
            ++gpsTagCount;
        } else {
            exifElementTable[i].GpsTag = FALSE;
            exifElementTable[i].Tag = TagNameToValue(tag);
            ++exifTagCount;
        }
        attrPtr = tagEnd + 1;

        // next get the length of the attribute value
        int valueLen = atoi(attrPtr);
        attrPtr = strchr(attrPtr, ' ') + 1;
        if (attrPtr == 0) {
#ifdef SUPERDEBUG
            LOGE("saveAttributes: couldn't find end of value len");
#endif
            goto exit;
        }
        exifElementTable[i].Value = malloc(valueLen + 1);
        if (exifElementTable[i].Value == NULL) {
            goto exit;
        }
        memcpy(exifElementTable[i].Value, attrPtr, valueLen);
        exifElementTable[i].Value[valueLen] = 0;
        exifElementTable[i].DataLength = valueLen;

        attrPtr += valueLen;

#ifdef SUPERDEBUG
        LOGE("tag %s id %d value %s data length=%d isGps=%d", tag, exifElementTable[i].Tag,
            exifElementTable[i].Value, exifElementTable[i].DataLength, exifElementTable[i].GpsTag);
#endif
    }

    filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
#ifdef SUPERDEBUG
    LOGE("Call loadAttributes() with filename is %s. Loading exif info\n", filename);
#endif
    loadExifInfo(filename, TRUE);

#ifdef SUPERDEBUG
//    DumpExifMap = TRUE;
    ShowTags = TRUE;
    ShowImageInfo(TRUE);
    LOGE("create exif 2");
#endif

    // If the jpg file has a thumbnail, preserve it.
    int thumbnailLength = ImageInfo.ThumbnailSize;
    if (ImageInfo.ThumbnailOffset) {
        Section_t* ExifSection = FindSection(M_EXIF);
        if (ExifSection) {
            uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
            thumbnailData = (uchar*)malloc(ImageInfo.ThumbnailSize);
            // if the malloc fails, we just won't copy the thumbnail
            if (thumbnailData) {
                memcpy(thumbnailData, thumbnailPointer, thumbnailLength);
            }
        }
    }

    create_EXIF(exifElementTable, exifTagCount, gpsTagCount);

    if (thumbnailData) {
        copyThumbnailData(thumbnailData, thumbnailLength);
    }

exit:
#ifdef SUPERDEBUG
    LOGE("cleaning up now in saveAttributes");
#endif
    // try to clean up resources
    if (attributes) {
        (*env)->ReleaseStringUTFChars(env, jattributes, attributes);
    }
    if (filename) {
        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
    }
    if (exifElementTable) {
        // free the table
        for (i = 0; i < attrCnt; i++) {
            free(exifElementTable[i].Value);
        }
        free(exifElementTable);
    }
    if (thumbnailData) {
        free(thumbnailData);
    }
#ifdef SUPERDEBUG
    LOGE("returning from saveAttributes");
#endif

// Temporarily saving these commented out lines because they represent a lot of figuring out
// patterns for JNI.
//    // Get link to Method "entrySet"
//    jmethodID entrySetMethod = (*env)->GetMethodID(env, jclass_of_hashmap, "entrySet", "()Ljava/util/Set;");
//
//    // Invoke the "entrySet" method on the HashMap object
//    jobject jobject_of_entryset = (*env)->CallObjectMethod(env, hashMap, entrySetMethod);
//
//    // Get the Set Class
//    jclass jclass_of_set = (*env)->FindClass(env, "java/util/Set");
//
//    if (jclass_of_set == 0) {
//        printf("java/util/Set lookup failed\n");
//        return;
//    }
//
//    // Get link to Method "iterator"
//    jmethodID iteratorMethod = (*env)->GetMethodID(env, jclass_of_set, "iterator", "()Ljava/util/Iterator;");
//
//    // Invoke the "iterator" method on the jobject_of_entryset variable of type Set
//    jobject jobject_of_iterator = (*env)->CallObjectMethod(env, jobject_of_entryset, iteratorMethod);
//
//    // Get the "Iterator" class
//    jclass jclass_of_iterator = (*env)->FindClass(env, "java/util/Iterator");
//
//    // Get link to Method "hasNext"
//    jmethodID hasNextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "hasNext", "()Z");
//
//    // Invoke - Get the value hasNextMethod
//    jboolean bHasNext = (*env)->CallBooleanMethod(env, jobject_of_iterator, hasNextMethod);

//    // Get link to Method "hasNext"
//    jmethodID nextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "next", "()Ljava/util/Map/Entry;");
//
//    jclass jclass_of_mapentry = (*env)->FindClass(env, "java/util/Map/Entry");
//
//    jmethodID getKeyMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getKey", "()Ljava/lang/Object");
//
//    jmethodID getValueMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getValue", "()Ljava/lang/Object");
}