function getV4AnnotationPaths(sPath, oData) {
			var aProperties = Object.keys(oData),
				aV4AnnotationPaths = aProperties.filter(function (sProperty) {
					return sProperty.includes(".");
				}).map(function (sProperty) {
					return sPath + "/" + sProperty;
				});

			aProperties.filter(function (sProperty) {
				return !sProperty.includes(".") && typeof oData[sProperty] === "object" && oData[sProperty];
			}).forEach(function (sProperty) {
				// use names for index properties to find the location in the metadata more easily
				var sPropertyPath = sPath + "/";

				if (isNaN(Number(sProperty)) || !oData[sProperty].name) {
					sPropertyPath += sProperty;
				} else {
					sPropertyPath += oData[sProperty].name;
				}

				aV4AnnotationPaths = aV4AnnotationPaths.concat(getV4AnnotationPaths(sPropertyPath, oData[sProperty]));
			});

			return aV4AnnotationPaths;
		}
******************************************************************************************
	QUnit.test("fetchCodeList: destroy ODataMetaModel before read is completed", function (assert) {
		var mCodeListUrl2Promise, oFetchCodeListPromise, fnReadCallResolve,
			oCodeListModel = {
				destroy : function () {},
				_read : function () {}
			},
			oCodeListCacheMock = this.mock(Map.prototype),
			oCodeListModelMock = this.mock(oCodeListModel),
			oDataModel = new ODataModel("/fake/emptySchema"),
			oEntityContainer = {
				"com.sap.vocabularies.CodeList.v1.~term" : {
					CollectionPath : {String : "~CollectionPath"},
					Url : {String : "./$metadata"}
				}
			},
			oMetaModel = oDataModel.getMetaModel(),
			oMetaModelMock = this.mock(oMetaModel),
			sMetaModelId = oMetaModel.getId(),
			fnReadCallbackPromise = new Promise(function (resolve, reject) {
				fnReadCallResolve = resolve;
			});

		oMetaModelMock.expects("getODataEntityContainer").withExactArgs().returns(oEntityContainer);
		this.mock(oDataModel).expects("getMetadataUrl").withExactArgs().returns("~sMetadataUrl");
		// A workaround to get the global cache instances as they are not visible to the test, don't
		// mock Map.prototype as test framework uses also Map i.e. for storing uncaught promises
		oCodeListCacheMock.expects("get")
			.withExactArgs("~sMetadataUrl#~CollectionPath")
			.callsFake(function (sKey) {
				mCodeListUrl2Promise = this;

				return undefined;
			});
		oCodeListCacheMock.expects("get")
			.withExactArgs("~sMetadataUrl#~CollectionPath#" + sMetaModelId)
			.callsFake(function (sKey) {
				assert.strictEqual(this, mCodeListUrl2Promise);
				Map.prototype.get.restore();

				return undefined;
			});
		oMetaModelMock.expects("_getOrCreateSharedModelCache").withExactArgs()
			.callsFake(function () {
				this.oSharedModelCache = {
					// simulate a read for a the second code list; destroy of the meta data model
					// must not lead to a duplicate destroy of the code list model
					bFirstCodeListRequested : true,
					oModel : oCodeListModel
				};

				return this.oSharedModelCache;
			});
		this.mock(ODataMetaModel).expects("cleanupCodeListModelURLParameters")
			.withExactArgs(sinon.match.same(oCodeListModel), "~sMetadataUrl");
		oCodeListModelMock.expects("_read")
			.withExactArgs("/~CollectionPath", sinon.match.object, undefined, sinon.match.func)
			.callsFake(function (sPath, mParams) {
				oCodeListModelMock.expects("destroy").withExactArgs()
					.callsFake(function () {
						this.bDestroyed = true;

						fnReadCallResolve(mParams.error);
					});

				// code under test - destroy data model before read resolves
				// data model destroys meta model, meta model destroys code list model
				oDataModel.destroy();
			});
		oMetaModelMock.expects("_getPropertyNamesForCodeListCustomizing")
			.withExactArgs("~CollectionPath")
			.returns({/* not relevant