% CompressLib.m
%--------------------------------------------------------------------------
% This Matlab class contains only static methods.
% These methods will compress matlab variables in java GZIP functions.
% Matrices, strings, structures, and cell arrays are supported. Matlab
% objects are also supported provided they implement a "toByteArray" method
% and a constructor of the form:
%          obj = constructor(byteArray,'PackedBytes')
%
% Usage:
%   x = % some matlab variable,
%       % can be a struct, cell-array,
%       % matrix, or object (if object conforms to certain standards)
%
%   cx = CompressLib.compress(x); % cx is a byte-array which contains
%                                 % compressed version of x
%
%   x2 = CompressLib.decompress(cx); %x2 is now a copy of x
%
%
% The methods CompressLib.packBytes and CompressLib.unpackBytes are also
% available.  These methods may be used to make Matlab classes compliant
% for the compression routines. See "packableObject.m" for an example
% Matlab class which is compliant with CompressLib.
%
% Use CompressLib.test to run a series of tests for functionality of this
% class.
%
% See also CompressLib.decompress  CompressLib.compress  packableObject
%          CompressLib.unpackBytes CompressLib.packBytes CompressLib.test
%
% Original Author: Jesse Hopkins
%
% $Author: jhopkin $
% $Date: 2009/10/29 20:24:59 $
% $Name:  $
% $Revision: 1.5 $
%--------------------------------------------------------------------------

classdef CompressLib
	methods(Static = true)

		function out = decompress(byteArray)
% CompressLib.decompress
%--------------------------------------------------------------------------
% out = CompressLib.decompress(byteArray)
%
% Function will decompress "byteArray" (created by CompressLib.compress).
% "byteArray" must be a 1-D array of bytes (uint8).
%
% Output will be the same class and dimension as original.
%
% See also CompressLib.compress    CompressLib.test      CompressLib
%          CompressLib.unpackBytes CompressLib.packBytes packableObject
%--------------------------------------------------------------------------

			import com.mathworks.mlwidgets.io.InterruptibleStreamCopier

			if ~strcmpi(class(byteArray),'uint8') || ndims(byteArray) > 2 || min(size(byteArray) ~= 1)
				error('Input must be a 1-D array of uint8');
			end

			%------Decompress byte-array "byteArray" to "byteData" using java methods------
			a=java.io.ByteArrayInputStream(byteArray);
			b=java.util.zip.GZIPInputStream(a);
			isc = InterruptibleStreamCopier.getInterruptibleStreamCopier;
			c = java.io.ByteArrayOutputStream;
			isc.copyStream(b,c);
			byteData = typecast(c.toByteArray,'uint8');
			%----------------------------------------------------------------------

			%now interpret the decompressed byte array back to matlab types
			out = CompressLib.unpackBytes(byteData);
		end

		function byteArray = compress(in)
% CompressLib.compress
%--------------------------------------------------------------------------
% byteArray = CompressLib.compress(in)
%
% This method will compress input using GZIP java libraries.  Numeric
% matricies, structures, cell-arrays, and character strings are supported.
%
% Objects are also supported provided they implement a "toByteArray" method
% and a constructor of the form:
%         obj = constructor(byteArray,'PackedBytes')
%
% Outputs an array of type uint8.  Use CompressLib.decomress to decompress
% to original data.
%
% See also CompressLib.decompress  CompressLib.test      CompressLib
%          CompressLib.unpackBytes CompressLib.packBytes packableObject
%--------------------------------------------------------------------------

			%pack the input variable to an array of bytes
			byteData = CompressLib.packBytes(in);

			%-------compress the array of bytes using java GZIP--------------------
			f=java.io.ByteArrayOutputStream();
			g=java.util.zip.GZIPOutputStream(f);
			g.write(byteData);
			g.close;
			byteArray=typecast(f.toByteArray,'uint8');
			f.close;
			%----------------------------------------------------------------------
		end

		function out = unpackBytes(byteData)
% CompressLib.unpackBytes
%--------------------------------------------------------------------------
% out = CompressLib.unpackBytes(byteArray)
%
% Function will unpack "byteArray" (created by CompressLib.packBytes)
% to its original Matlab datatype.
%
% Matrices, strings, structures, and cell arrays are supported. Matlab
% objects are also supported provided they implement a constructor
% of the form:
%          obj = constructor(byteArray,'PackedBytes')
%
% See "packableObject.m" for an example Matlab class that is compliant.
%
% See also CompressLib.decompress  CompressLib.compress CompressLib
%          CompressLib.packBytes   CompressLib.test     packableObject
%--------------------------------------------------------------------------

			out = CompressLib.unpackBytesRecurse(byteData,1);
		end

		function [byteData] = packBytes(in)
% CompressLib.packBytes
%--------------------------------------------------------------------------
% byteArray = CompressLib.packBytes(in)
%
% This function will convert "in" to a byte-array representation.  Numeric
% matricies, structures, cell-arrays, and character strings are supported.
% Use CompressLib.unpackBytes to convert back to original Matlab
% representation.
%
% Objects are also supported provided they implement a "toByteArray"
% method. See "packableObject.m" for an example Matlab class that
% is compliant.
%
% See also CompressLib.decompress  CompressLib.compress CompressLib
%          CompressLib.unpackBytes CompressLib.test     packableObject
%--------------------------------------------------------------------------



			%pre-allocate bytedata
			tmp = whos('in');
			byteData = zeros(tmp.bytes + 200,1,'uint8');
			curOffset = 1;

			[byteData curOffset] = CompressLib.packBytesRecurse(in,byteData,curOffset);

			%chop byteData to proper size
			byteData = byteData(1:curOffset-1);
		end

		function pass = test
% CompressLib.test
%--------------------------------------------------------------------------
% pass = CompressLib.test()
%
% Use this method to test the functionality of the entire CompressLib class.
% Will perform a suite of tests and report results. Outputs "true" if all
% tests passed, outputs false if any test fails.
%
% (A test is declared "passed" if the isequal() returns true for the original
% data and decompressed data)
%
% See also CompressLib.decompress  CompressLib.compress    CompressLib
%          CompressLib.packBytes   CompressLib.unpackBytes packableObject
%--------------------------------------------------------------------------
			i = 0;

			test = struct('data',{},'desc',{},'result',{});
			numericClasses = {'double','single','uint64','uint32','uint16','uint8','int64','int32','int16','int8'};
			%----------TEST 1-D NUMERIC CLASSES----------------------------
			for classCnt = 1:length(numericClasses)
				i = i+1;
				test(i).data = ones(1,1000,numericClasses{classCnt});
				test(i).desc = sprintf('ones(1,1000,%s',numericClasses{classCnt});
			end
			%--------------------------------------------------------------

			%----------TEST 2-D NUMERIC CLASSES----------------------------
			for classCnt = 1:length(numericClasses)
				i = i+1;
				test(i).data = ones(50,100,numericClasses{classCnt});
				test(i).desc = sprintf('ones(50,100,%s',numericClasses{classCnt});
			end
			%--------------------------------------------------------------

			%----------TEST 3-D NUMERIC CLASSES----------------------------
			for classCnt = 1:length(numericClasses)
				i = i+1;
				test(i).data = ones(50,100,20,numericClasses{classCnt});
				test(i).desc = sprintf('ones(50,100,20,%s',numericClasses{classCnt});
			end
			%--------------------------------------------------------------

			%---------TEST 1D LOGICALS-------------------------------------
			i = i+1;
			test(i).data = logical(rand(1,1000) > 0.5);
			test(i).desc = '1-D Logical';
			%--------------------------------------------------------------

			%---------TEST 2D LOGICALS-------------------------------------
			i = i+1;
			test(i).data = logical(reshape(test(i-1).data,100,10));
			test(i).desc = '2-D Logical';
			%--------------------------------------------------------------

			%---------TEST 3D LOGICALS-------------------------------------
			i = i+1;
			test(i).data = logical(reshape(test(i-2).data,5,2,100));
			test(i).desc = '3-D Logical';
			%--------------------------------------------------------------

			%---------TEST 1-D Char----------------------------------------
			i = i+1;
			test(i).data = 'I am a string, please compress me!!!!!!!';
			test(i).desc = '1-D char';
			%--------------------------------------------------------------

			%---------TEST 2-D Char----------------------------------------
			i = i+1;
			test(i).data = reshape(test(i-1).data,2,20);
			test(i).desc = '2-D char';
			%--------------------------------------------------------------

			%---------TEST 3-D Char----------------------------------------
			i = i+1;
			test(i).data =  reshape(test(i-2).data,2,5,4);
			test(i).desc = '3-D char';
			%--------------------------------------------------------------

			%---------TEST CHAR CELL---------------------------------------
			i = i+1;
			charcell = {'I am string1','I am string2','I am string3', ...
				        'I am a string of another dimension','Blah blah blah'};
			test(i).data = charcell;
			test(i).desc = '1-D cell of strings';
			%--------------------------------------------------------------

			%---------TEST MIXED CELLS-------------------------------------
			mixedcell = {'I am a string','I am another string',[4 5 6 7],uint8([9 10 11])};
			i = i+1;
			test(i).data = mixedcell;
			test(i).desc = 'Cell Array of Mixed Data Types';
			%--------------------------------------------------------------

			%---------TEST SUB CELLS---------------------------------------
			subcell = {'I am a string','I am another string',mixedcell,charcell};
			i = i+1;
			test(i).data = subcell;
			test(i).desc = 'Cell containing sub-cells';
			%--------------------------------------------------------------

			%-------TEST MULTI-DIM SUB CELLS-------------------------------
			multidimsubcell = {'I am a string','I am another string',mixedcell,charcell ...
				               subcell        , uint16([5 2  65535]),[345.34 3],[4.234 5.630;4.2 10e20]};
			i = i+1;
			test(i).data = multidimsubcell;
			test(i).desc = 'Multi-dimensional cell containing sub-cells';
			%--------------------------------------------------------------

			%-----------TEST STRUCT----------------------------------------
			i = i+1;
			teststruct.field1 = rand(1,10);
			teststruct.field2 = {'abc','def',[4 5 6],uint8([9 10 11])};
			teststruct.field3 = multidimsubcell;
			test(i).data = teststruct;
			test(i).desc = 'Single Structure';
			%--------------------------------------------------------------

			%-----------TEST 1-D STRUCT------------------------------------
			i = i+1;
			struct1d = repmat(teststruct,100,1);
			test(i).data = struct1d;
			test(i).desc = '1-D Structure Array';
			%--------------------------------------------------------------

			%-----------TEST 2-D STRUCT------------------------------------
			i = i+1;
			test(i).data = reshape(struct1d,50,2);
			test(i).desc = '2-D Structure Array';
			%--------------------------------------------------------------

			%-----------TEST 3-D STRUCT------------------------------------
			i = i+1;
			test(i).data = reshape(struct1d,5,10,2);
			test(i).desc = '3-D Structure Array';
			%--------------------------------------------------------------

			%-----------TEST 1-D STRUCT w/ substructs and cells------------
			i = i+1;
			substruct = struct1d;
			substruct(3).field2 = struct1d;
			substruct(4).field3 = multidimsubcell;
			substruct(5).field1 = {multidimsubcell{:},substruct};
			test(i).data = substruct;
			test(i).desc = '1-D Structure w/ substructs and cells';
			%--------------------------------------------------------------

			%-----------TEST 3-D STRUCT------------------------------------
			i = i+1;
			test(i).data = reshape(substruct,50,2);
			test(i).desc = '3-D Structure Array w/ substructs and cells';
			%--------------------------------------------------------------

			%-----------TEST 3-D STRUCT------------------------------------
			i = i+1;
			test(i).data = reshape(substruct,5,10,2);
			test(i).desc = '3-D Structure Array w/ substructs and cells';
			%--------------------------------------------------------------

			%--------TEST an OBJECT----------------------------------------
			i = i+1;
			test(i).data = packableObject;
			test(i).desc = 'A single object';
			%--------------------------------------------------------------

			%-----TEST array of OBJECTS------------------------------------
			i = i+1;
			test(i).data = repmat(test(i-1).data,1,100);
			test(i).desc = '1-D Object Array';
			%--------------------------------------------------------------

			%-----TEST 2-D array of OBJECTS--------------------------------
			i = i+1;
			test(i).data = reshape(test(i-1).data,50,2);
			test(i).desc = '2-D Object Array';
			%--------------------------------------------------------------

			%-----TEST 3-D array of OBJECTS--------------------------------
			i = i+1;
			test(i).data = reshape(test(i-2).data,10,5,2);
			test(i).desc = '3-D Object Array';
			%--------------------------------------------------------------

			%-----TEST Structure containing OBJECTS--------------------------------
			i = i+1;
			structwobject = struct1d;
			structwobject(1).field1 = test(i-1).data;
			test(i).data = structwobject;
			test(i).desc = 'Structure Containing Objects (among other things)';
			%--------------------------------------------------------------

			%--------TEST BIG CELL ARRAY-----------------------------------
			test(i).data = num2cell(rand(100,500));
			test(i).desc = 'Big cell matrix: num2cell(rand(100,500))';
			%--------------------------------------------------------------

			%perform tests
			for j = 1:length(test)
				[test(j).result totalTime] = CompressLib.dotest(test(j).data,test(j).desc,j);
			end


			%print summary
			results = cell2mat({test(:).result});
			failedTests = find(results == 0);
			if isempty(failedTests)
				passStr = 'Passed all tests!';
			else
				passStr = 'Failed';
			end

			fprintf('\n\n=======SUMMARY===================');
			fprintf('\nResult: %s',passStr);
			fprintf('\nTotal time in compress/decompress: %f sec',totalTime);
			if ~isempty(failedTests)
			fprintf('\nFailed Tests: \n%d',failedTests);
			end
			fprintf('\n=================================\n');

			%set output variable
			pass = isempty(failedTests);
		end
	end

	methods(Static = true,Hidden = true)
		function out = classStr2EnumVal(classStr)
			switch classStr
				case 'double'
					out = 1;
				case 'single'
					out = 2;
				case 'logical'
					out = 3;
				case 'char'
					out = 4;
				case 'int8'
					out = 5;
				case 'uint8'
					out = 6;
				case 'int16'
					out = 7;
				case 'uint16'
					out = 8;
				case 'int32'
					out = 9;
				case 'uint32'
					out = 10;
				case 'int64'
					out = 11;
				case 'uint64'
					out = 12;
				case 'struct'
					out = 13;
				case 'cell'
					out = 14;
				otherwise
					out = 0;
			end
		end

		function byteData = conditionBuffer(byteData,curOffset,writeLength)
			%this function exists to make sure that the byteData array
			%is long enough to store "writeLength" more bytes based on
			%"curOffset" in the array.
			if length(byteData) < curOffset + writeLength
				extraLengthNeeded = curOffset + writeLength - length(byteData);
				extraLengthNeeded = extraLengthNeeded + 100; %add 100 extra bytes
				disp(['CompressLib.packBytes is allocating ' num2str(extraLengthNeeded) ' more bytes!']);
				byteData = [byteData;zeros(extraLengthNeeded,1,'uint8')];
			end
		end

		function [pass totalTimeOut] = dotest(x,desc,testnum)
			persistent totalTime;
			if isempty(totalTime) || testnum == 1
				totalTime = 0;
			end

			tic;
			cx = CompressLib.compress(x);
			compressTime = toc;
			tic
			x2 = CompressLib.decompress(cx);
			decompressTime = toc;
			pass = isequal(x,x2);
			if pass
				passStr = 'pass';
			else
				passStr = 'FAIL';
			end


			xinfo  = whos('x');
			cxinfo = whos('cx');
			x2info = whos('x2');

			fprintf('\n\nTest %d: %s',testnum,passStr);
			fprintf('\n%s',desc);
			fprintf('\n----------------');
			fprintf('\nInput Class     : %s',xinfo.class);
			fprintf('\nInput Dim       : %s',num2str(xinfo.size));
			fprintf('\nInput Size      : %d bytes',xinfo.bytes);
			fprintf('\nCompress Size   : %d',cxinfo.bytes);
			fprintf('\nBytes Saved     : %d',xinfo.bytes-cxinfo.bytes);
			fprintf('\nCompress Ratio  : %2.1f%%',100*(xinfo.bytes-cxinfo.bytes)/xinfo.bytes);
			fprintf('\nOutput Size     : %d',x2info.bytes);
			fprintf('\nOutput Size diff: %d',x2info.bytes - xinfo.bytes);
			fprintf('\nCompress Time   : %f sec',compressTime);
			fprintf('\nDecompress Time : %f sec',decompressTime);



			totalTime = totalTime + compressTime + decompressTime;
			totalTimeOut = totalTime;

		end

		function [byteData curOffset] = packBytesRecurse(in,byteData,curOffset)
			%matrix data
			%header = [type  ndims  dim1  dim2    ...     DATA
			%          uint8 uint8 uint32 uint32 uint32   uint8[]
			%
			%struct data
			%header = [type         ndims  dim1    dim2    ...    numfields  | field1len  Fieldname    | field2len Fieldname | ... | Struct(1).fieldname{1} DATA  | Struct(1).fieldname{2} DATA | ... | Struct(2).filename{1} DATA |
			%          13(uint8) uint8  uint32  uint32  uint32     uint32    |  uint8       uint8[]    | uint8      uint8[]  | ... |                              |                             | ... |                            |
			%
			%cell data
			%header = [type        ndims  dim1  dim2    ...    | DATA{1}  | DATA{2} |
			%          14 (uint8)  uint8 uint32 uint32 uint32  |          |         |
			%
			%user-defined class
			%header = [type  ndims  dim1  dim2    ...   classNameLen  className    | datasize DATA(1)  | ... |
			%          0     uint8 uint32 uint32 uint32   uint8         uint8[]    | uint32    uint8[] | ... |

			classEnumVal2Str = { 'double';
			                 'single';
			                 'logical';
			                 'char';
			                 'int8';
			                 'uint8';
			                 'int16';
			                 'uint16';
			                 'int32';
			                 'uint32';
			                 'int64';
			                 'uint64';
			                 'struct';
			                 'cell'};
			getSizeFromClassEnum = [8;  % double
                                4;  % single
                                1;  % logical
                                1;  % char8
                                1;  % int8
                                1;  % uint8
                                2;  % int16
                                2;  % uint16
                                4;  % int32
                                4;  % uint32
                                8;  % int64
                                8]; % uint64
							
			inSize = size(in);
			inClass = class(in);

			%-----------------GET THE CLASS TYPE-----------------------------------
			inClassEnum = CompressLib.classStr2EnumVal(inClass);


			if inClassEnum == 0
				%this is some other class.  See if it has a "to byte array" method
				if ~ismethod(inClass,'toByteArray')
					error(['"' inClass '" is not supported for compression! (A class must implement method "toByteArray" in order to be compressed']);
				else
					inClassEnum = 0;
				end
			end

			%convert to unsigned int.
			inClassEnum = uint8(inClassEnum);

			%----------------------------------------------------------------------

			%-----Format the size--------------------------------------------------
			ndims_double = length(inSize);
			if ndims_double > 255
				error('To many dimensions, must have (ndims must be < 255)');
			else
				ndims = uint8(ndims_double);
				if max(inSize) >=4294967295
					error('Maximum dimension must be less than 4294967295!');
				else
					inSize = uint32(inSize);
				end
			end
			%----------------------------------------------------------------------


			%If the class type is logical or char, then convert to uint8
			if inClassEnum == 3 || inClassEnum == 4
				in=uint8(in);
			end

			if inClassEnum == 0
				%user-defined class
				%header = [type  ndims  dim1  dim2    ...   classNameLen  className    | datasize DATA(1)  | ... |
				%          14    uint8 uint32 uint32 uint32   uint8         uint8[]    | uint32    uint8[] | ... |
				writeLength = 3 + ndims_double*4 + length(inClass);
				byteData = CompressLib.conditionBuffer(byteData,curOffset,writeLength);
				                                                    %type   %ndims    %dim1 ... dimn
				byteData(curOffset:curOffset + writeLength-1) = [inClassEnum ndims typecast(inSize,'uint8') uint8(length(inClass)) uint8(inClass)];
				curOffset = curOffset + writeLength;

				in = in(:);
				for i = 1:length(in)
					objectByteArray = toByteArray(in(i));
					if ~strcmp(class(objectByteArray),'uint8')
						error('object method "toByteArray" did not return array of uint8!');
					end

					objectLen = length(objectByteArray);

					if objectLen > 4294967295
						error('object byte array length must be less than 4294967295!');
					end

					if length(inClass) > 255
						error('Class name must be less than 255!');
					end

					writeLength = 4 + numel(objectByteArray);
					byteData = CompressLib.conditionBuffer(byteData,curOffset,writeLength);
					byteData(curOffset:curOffset+writeLength-1) = [typecast(uint32(objectLen),'uint8') objectByteArray(:)' ];
					curOffset = curOffset + writeLength;
				end

			elseif inClassEnum == 13
				%we have a struct
				structfields = fieldnames(in);

				writeLength = 6 + ndims_double*4;
				byteData = CompressLib.conditionBuffer(byteData,curOffset,writeLength);
				                                                %type     %ndims    %dim1 ... dimn                %numfields
				byteData(curOffset:curOffset+writeLength-1) = [inClassEnum ndims typecast(inSize,'uint8') typecast(uint32(length(structfields)),'uint8')];
				curOffset = curOffset + writeLength;

				for i = 1:length(structfields)
					%add the fields
					if length(structfields(i)) > 255
						error('Fieldname length cannot exceed 255!');
					end

					%add fieldData
					writeLength = 1 + length(structfields{i});
					byteData = CompressLib.conditionBuffer(byteData,curOffset,writeLength);
					byteData(curOffset:curOffset+writeLength-1) = [uint8(length(structfields{i})) uint8(structfields{i})];
					curOffset = curOffset + writeLength;
				end

				%now add data
				in = in(:);
				for i = 1:length(in)
					for j = 1:length(structfields)
						[byteData curOffset] = CompressLib.packBytesRecurse(in(i).(structfields{j}),byteData,curOffset);
					end
				end
			elseif inClassEnum == 14
				%we have a cell!
				%cell data
				%header = [type  ndims  dim1  dim2    ...    | DATA{1}  | DATA{2} |
				%          14    uint8 uint32 uint32 uint32  |          |         |
				writeLength = 2 + ndims_double*4;
				byteData = CompressLib.conditionBuffer(byteData,curOffset,writeLength);
				                                                  %type     %ndims    %dim1 ... dimn
				byteData(curOffset:curOffset + writeLength-1) = [inClassEnum ndims typecast(inSize,'uint8') ];
				curOffset = curOffset + writeLength;

				in = in(:);
				for i = 1:length(in)
					[byteData curOffset] = CompressLib.packBytesRecurse(in{i},byteData,curOffset);
				end
			else
				in = in(:)';
				writeLength = 2 + ndims_double*4 + getSizeFromClassEnum(inClassEnum)*numel(in);
				byteData = CompressLib.conditionBuffer(byteData,curOffset,writeLength);
				byteData(curOffset:curOffset+writeLength-1) = [inClassEnum typecast(ndims,'uint8') typecast(inSize,'uint8') typecast(in,'uint8')];
				curOffset = curOffset + writeLength;
			end
		end

		function [data curOffset] = unpackBytesRecurse(byteData,curOffset)
			classEnumVal2Str = { 'double';
			                 'single';
			                 'logical';
			                 'char';
			                 'int8';
			                 'uint8';
			                 'int16';
			                 'uint16';
			                 'int32';
			                 'uint32';
			                 'int64';
			                 'uint64';
			                 'struct';
			                 'cell'};
						 
			getSizeFromClassEnum = [8;  % double
                                4;  % single
                                1;  % logical
                                1;  % char8
                                1;  % int8
                                1;  % uint8
                                2;  % int16
                                2;  % uint16
                                4;  % int32
                                4;  % uint32
                                8;  % int64
                                8]; % uint64
			
			byteData = byteData(:);

			dataClassEnum = byteData(curOffset);
			curOffset = curOffset + 1;
			dataNumDims   = byteData(curOffset);
			curOffset = curOffset + 1;

			dataSize      = zeros(1,dataNumDims,'uint32');

			for i = 1:dataNumDims
				dataSize(i) = typecast(byteData(curOffset:curOffset+3),'uint32');
				curOffset = curOffset+4;
			end

			if dataClassEnum == 0
				%custom object
				%header = [type  ndims  dim1  dim2    ...   classNameLen  className    | datasize DATA(1)  | ... |
				%          0     uint8 uint32 uint32 uint32   uint8         uint8[]    | uint32    uint8[] | ... |

				classNameLen = double(byteData(curOffset));
				curOffset = curOffset + 1;

				className = char(byteData(curOffset:curOffset+classNameLen-1));
				curOffset = curOffset+classNameLen;

				%compute number of elements
				numEls = prod(double(dataSize));
				for i = 1:numEls
					%grab the size for this object
					objSize = double(typecast(byteData(curOffset:curOffset+3),'uint32'));
					curOffset = curOffset + 4;

					%call the objects constructor w/ the byte array and 'ByteData' string
					data(i) = feval(className,byteData(curOffset:curOffset+objSize-1),'PackedBytes');
					curOffset = curOffset + objSize;
				end
				data = reshape(data,dataSize);

			elseif dataClassEnum == 13
				%struct

				%----Grab number of fields-----------------------------------------
				numFields = typecast(byteData(curOffset:curOffset+3),'uint32');
				curOffset = curOffset+4;
				%------------------------------------------------------------------

				%compute number of elements
				numEls = prod(double(dataSize));

				%-------Grab the fieldnames----------------------------------------
				fieldname = cell(1,numFields);
				for fieldcnt = 1:numFields
					fieldlen = double(byteData(curOffset));
					curOffset = curOffset + 1;

					fieldname{fieldcnt} = char(byteData(curOffset:curOffset+fieldlen-1))';
					curOffset = curOffset + fieldlen;
				end
				%------------------------------------------------------------------


				%-----------create data struct-array------------------------------
				tmpcell = cell(1,2*length(fieldname));
				tmpcell(1:2:end) = fieldname;
				tmpcell(2:2:end) = repmat({cell(1,numEls)},1,length(fieldname));
				data = struct(tmpcell{:});
				%------------------------------------------------------------------

				%------------slurp the data----------------------------------------
				for elcnt = 1:numEls
					for fieldcnt = 1:numFields
						[data(elcnt).(fieldname{fieldcnt}) curOffset] = CompressLib.unpackBytesRecurse(byteData,curOffset);
					end
				end
				data = reshape(data,dataSize);
				%------------------------------------------------------------------
			elseif dataClassEnum == 14
				%cell

				%compute number of elements
				numEls = prod(double(dataSize));

				data = cell(numEls,1);
				for i = 1:numEls
					[data{i} curOffset] = CompressLib.unpackBytesRecurse(byteData,curOffset);
				end
				data = reshape(data,dataSize);
			else
				%numeric/logical/char

				readLength = prod(double(dataSize))*getSizeFromClassEnum(dataClassEnum);
				switch dataClassEnum
					case 3
						%logical
						data = reshape(logical(byteData(curOffset:curOffset+readLength-1)),dataSize);
					case 4
						%char
						data = reshape(char(byteData(curOffset:curOffset+readLength-1)),dataSize);
					otherwise
						data = reshape(typecast(byteData(curOffset:curOffset+readLength-1),classEnumVal2Str{dataClassEnum}),dataSize);
				end
				curOffset = curOffset + double(readLength);
			end
		end
	end
end