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 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;
}