public void ApplyParameterDefaults(rws.ReportParameter[] wsReportParameters, CrcReportDefinition repDef)
        {
            // first build up a choicecollection from the defaults
            var choiceCol = new CrcParameterChoiceCollection();

            foreach (var paramLoop in wsReportParameters)
            {
                if (paramLoop.DefaultValues != null)
                {
                    var crcChoice = new CrcParameterChoice(paramLoop.Name);
                    foreach (string valString in paramLoop.DefaultValues)
                    {
                        crcChoice.Values.Add(valString);
                    }

                    choiceCol.ParameterChoiceList.Add(crcChoice);
                }
            }
            // now apply it to the repdef
            var mapper    = new CrcParameterChoiceMapper();
            var mapResult = mapper.MapParameterChoices(repDef, choiceCol);

            if (!mapResult.MappingValid)
            {
                throw new ApplicationException(string.Format("Could not map report defaults for report {0}. Problems: {1}",
                                                             repDef.DisplayName, string.Join(", ", mapResult.Complaints.ToArray())));
            }
        }
Ejemplo n.º 2
0
        public CrcParameterChoiceCollection Create(string paramString)
        {
            var pcCol = new CrcParameterChoiceCollection();

            string[] clauses = paramString.Split("&".ToCharArray());
            if (clauses.Count() == 1 && string.IsNullOrEmpty(clauses[0]))
            {
                return(pcCol);
            }
            foreach (string clauseLoop in clauses)
            {
                int    isnullPos = clauseLoop.IndexOf(":isnull");
                int    equalsPos = clauseLoop.IndexOf("=");
                string pname     = null;
                string pval      = null;
                if (isnullPos > 0)
                {
                    pname = clauseLoop.Substring(0, isnullPos);
                    string boolString  = clauseLoop.Substring(isnullPos + 8);
                    bool   isNullValue = bool.Parse(boolString);
                    if (!isNullValue)
                    {
                        throw new ApplicationException(String.Format("not sure how to interpret false in clause {0}", clauseLoop));
                    }
                    else
                    {
                        pval = null;
                    }
                }
                else if (equalsPos > 0)
                {
                    pname = clauseLoop.Substring(0, equalsPos);
                    pval  = HttpUtility.UrlDecode(clauseLoop.Substring(equalsPos + 1));
                }
                else
                {
                    throw new ApplicationException(String.Format("Could not parse clause {0}", clauseLoop));
                }

                // we now have a pname and pval
                var pcExists = pcCol.ParameterChoiceList.FirstOrDefault(p => p.Name == pname);
                if (pcExists == null)
                {
                    var newPc = new CrcParameterChoice(pname);
                    pcCol.ParameterChoiceList.Add(newPc);
                    pcExists = newPc;
                }
                pcExists.Values.Add(pval);
            }
            return(pcCol);
        }
Ejemplo n.º 3
0
        public CrcParameterChoiceMapper.ParameterMapResult MapParameterChoices(CrcParameterChoiceCollection paramChoices)
        {
            // dry run on a clone first to check for errors
            var clone  = this.DeepClone();
            var mapper = new CrcParameterChoiceMapper();
            var verify = mapper.MapParameterChoices(clone, paramChoices);

            if (!verify.MappingValid)
            {
                return(verify);
            }

            // if still here then there are no errors - map to real object
            var results = mapper.MapParameterChoices(this, paramChoices);

            return(results);
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Applies the specified choices to the already-existing report definition
        /// and updates the dependant parameters
        /// </summary>
        public void RefreshDependantParameters(CrcReportDefinition repDefn, CrcParameterChoiceCollection newChoices)
        {
            var mapResult = repDefn.MapParameterChoices(newChoices);

            if (!mapResult.MappingValid)
            {
                // todo - friendlier message back to ui
                throw new ApplicationException(string.Format("invalid params - could not map supplied values to definitions for report {0}. complaints: {1}",
                                                             repDefn.DisplayName, string.Join(", ", mapResult.Complaints.ToArray())));
            }
            var conv = new CrcParameterConverter();
            List <ParameterValue> valueList = conv.GetParametersValuesForSsrsWebService(repDefn);
            // get new params from web service
            ReportingService2005Soap rService = m_ssrsSoapClientFactory.MakeSsrsSoapClient();

            logger.DebugFormat("RefreshDependantParameters: rep {0} calling WS to get new validvalid. Passing {1} values", repDefn.DisplayName, valueList.Count());

            var grpRequest  = new GetReportParametersRequest(repDefn.ReportPath, null, true, valueList.ToArray(), null);
            var grpResponse = rService.GetReportParameters(grpRequest);

            // work out which params to refresh
            List <string> paramsToRefresh = new List <string>();

            foreach (string updatedParam in newChoices.ParameterChoiceList.Select(p => p.Name))
            {
                var paramDefn = repDefn.ParameterDefinitions.First(p => p.Name == updatedParam);
                paramsToRefresh = paramsToRefresh.Union(paramDefn.DependantParameterNames).ToList();
            }
            logger.DebugFormat("RefreshDependantParameters: rep {0} based on choices, have {1} parameters that need refreshing", repDefn.DisplayName, paramsToRefresh.Count());

            var refresher = new CrcParameterRefresher();

            foreach (string paramLoop in paramsToRefresh)
            {
                var paramDefn          = repDefn.ParameterDefinitions.First(p => p.Name == paramLoop);
                var latestParamDetails = grpResponse.Parameters.FirstOrDefault(p => p.Name == paramLoop);
                if (latestParamDetails == null)
                {
                    throw new ApplicationException(String.Format("Was expecting web service to return new details for parameter {0} but none found",
                                                                 paramLoop));
                }
                refresher.RefreshParameter(paramDefn, latestParamDetails);
            }
        }
Ejemplo n.º 5
0
        public CrcParameterChoiceCollection Create(string paramString)
        {
            var pcCol = new CrcParameterChoiceCollection();
            string[] clauses = paramString.Split("&".ToCharArray());
            if (clauses.Count() == 1 && string.IsNullOrEmpty(clauses[0]))
                return pcCol;
            foreach (string clauseLoop in clauses)
            {
                int isnullPos = clauseLoop.IndexOf(":isnull");
                int equalsPos = clauseLoop.IndexOf("=");
                string pname = null;
                string pval = null;
                if (isnullPos > 0)
                {
                    pname = clauseLoop.Substring(0, isnullPos);
                    string boolString = clauseLoop.Substring(isnullPos + 8);
                    bool isNullValue = bool.Parse( boolString);
                    if (!isNullValue)
                        throw new ApplicationException(String.Format("not sure how to interpret false in clause {0}", clauseLoop));
                    else
                        pval = null;
                }
                else if (equalsPos > 0)
                {
                    pname = clauseLoop.Substring(0, equalsPos);
                    pval = HttpUtility.UrlDecode(clauseLoop.Substring(equalsPos + 1));
                }
                else
                    throw new ApplicationException(String.Format("Could not parse clause {0}", clauseLoop));

                // we now have a pname and pval
                var pcExists = pcCol.ParameterChoiceList.FirstOrDefault(p => p.Name == pname);
                if (pcExists == null)
                {
                    var newPc = new CrcParameterChoice(pname);
                    pcCol.ParameterChoiceList.Add(newPc);
                    pcExists = newPc;
                }
                pcExists.Values.Add(pval);
            }
            return pcCol;
        }
        public void CanMapDateParam()
        {
            var repDefn = this.MakeTestReportDefn();
            var choiceColl = new CrcParameterChoiceCollection();
            var dateChoice = new CrcParameterChoice();
            dateChoice.Name = "PretendDateParam";
            dateChoice.SingleValue = "2010-06-02";
            choiceColl.ParameterChoiceList.Add(dateChoice);

            var mapper = new CrcParameterChoiceMapper();
            var mapResult = mapper.MapParameterChoices(repDefn, choiceColl);

            Assert.IsTrue(mapResult.MappingValid);
            Assert.AreEqual(0, mapResult.Complaints.Count());

            var dateParamDefn = repDefn.ParameterDefinitions.FirstOrDefault(p=> p.Name == "PretendDateParam");
            Assert.IsNotNull(dateParamDefn);
            Assert.IsNotNull(dateParamDefn.ParameterChoice);
            Assert.AreEqual("2010-06-02", dateParamDefn.ParameterChoice.SingleValue);
        }
Ejemplo n.º 7
0
        /// <summary>
        /// applies the specified CrcParameterChoiceCollection to the specified report definition
        /// will set the ParameterChoice property of matching ParameterDefinitions as required
        /// </summary>
        /// <param name="repDefn"></param>
        /// <param name="paramChoices"></param>
        /// <returns>ParameterMapResult - if MappingValid is false, see Complaints collection</returns>
        public ParameterMapResult MapParameterChoices(CrcReportDefinition repDefn, CrcParameterChoiceCollection paramChoices)
        {
            var res = new ParameterMapResult();
            res.Complaints = new List<string>();
            string cultureForDateParsing = GetCultureForDateParsing();
            foreach (CrcParameterChoice choiceLoop in paramChoices.ParameterChoiceList)
            {
                var defnMatch = repDefn.ParameterDefinitions.FirstOrDefault(pd => pd.Name == choiceLoop.Name);
                if (defnMatch == null)
                {
                    res.Complaints.Add(String.Format("Param {0} not found", choiceLoop.Name));
                    continue;
                }
                if (defnMatch.ParameterType == CrcParameterType.Date)
                {
                    if (choiceLoop.SingleValue == null)
                    {
                        defnMatch.ParameterChoice = choiceLoop.DeepClone();
                        continue;
                    }

                    var parseResult = ParseDateStringVariousWays(choiceLoop.SingleValue, cultureForDateParsing);
                    if (parseResult.CouldParse)
                    {
                        var parsedChoice = new CrcParameterChoice(choiceLoop.Name);
                        parsedChoice.SingleValue = parseResult.DateTime.ToString("yyyy-MM-dd");
                        defnMatch.ParameterChoice = parsedChoice;
                        continue;
                    }
                    res.Complaints.Add(String.Format("Could not parse date {0} into Param {1}",
                        choiceLoop.SingleValue, choiceLoop.Name));
                }
                else if (defnMatch.ParameterType == CrcParameterType.Boolean)
                {
                    if (string.IsNullOrEmpty(choiceLoop.SingleValue) || choiceLoop.SingleValue == "True"
                        || choiceLoop.SingleValue == "False")
                    {
                        defnMatch.ParameterChoice = choiceLoop.DeepClone();
                        continue;
                    }
                    res.Complaints.Add(String.Format("Could not parse boolean {0} into Param {1}",
                        choiceLoop.SingleValue, choiceLoop.Name));
                }
                else if (defnMatch.ParameterType == CrcParameterType.Text)
                {
                    defnMatch.ParameterChoice = choiceLoop.DeepClone();
                    continue;
                }
                else if (defnMatch.ValidValues.Count() > 0)
                {
                    bool valuesOK = true;
                    foreach (string chosenValue in choiceLoop.Values)
                    {
                        if (defnMatch.ValidValues.FirstOrDefault(vv => vv.Value == chosenValue) == null)
                        {
                            res.Complaints.Add(String.Format("Could not apply value {0} into Param {1} because it is not a valid option",
                             chosenValue, choiceLoop.Name));
                            valuesOK = false;
                        }

                    }
                    if (valuesOK)
                    {
                        defnMatch.ParameterChoice = choiceLoop.DeepClone();
                        continue;
                    }
                }
            }
            res.MappingValid = (res.Complaints.Count() == 0);
            return res;
        }
Ejemplo n.º 8
0
        /// <summary>
        /// applies the specified CrcParameterChoiceCollection to the specified report definition
        /// will set the ParameterChoice property of matching ParameterDefinitions as required
        /// </summary>
        /// <param name="repDefn"></param>
        /// <param name="paramChoices"></param>
        /// <returns>ParameterMapResult - if MappingValid is false, see Complaints collection</returns>
        public ParameterMapResult MapParameterChoices(CrcReportDefinition repDefn, CrcParameterChoiceCollection paramChoices)
        {
            var res = new ParameterMapResult();

            res.Complaints = new List <string>();
            string cultureForDateParsing = GetCultureForDateParsing();

            foreach (CrcParameterChoice choiceLoop in paramChoices.ParameterChoiceList)
            {
                var defnMatch = repDefn.ParameterDefinitions.FirstOrDefault(pd => pd.Name == choiceLoop.Name);
                if (defnMatch == null)
                {
                    res.Complaints.Add(String.Format("Param {0} not found", choiceLoop.Name));
                    continue;
                }
                if (defnMatch.ParameterType == CrcParameterType.Date)
                {
                    if (choiceLoop.SingleValue == null)
                    {
                        defnMatch.ParameterChoice = choiceLoop.DeepClone();
                        continue;
                    }

                    var parseResult = ParseDateStringVariousWays(choiceLoop.SingleValue, cultureForDateParsing);
                    if (parseResult.CouldParse)
                    {
                        var parsedChoice = new CrcParameterChoice(choiceLoop.Name);
                        parsedChoice.SingleValue  = parseResult.DateTime.ToString("yyyy-MM-dd");
                        defnMatch.ParameterChoice = parsedChoice;
                        continue;
                    }
                    res.Complaints.Add(String.Format("Could not parse date {0} into Param {1}",
                                                     choiceLoop.SingleValue, choiceLoop.Name));
                }
                else if (defnMatch.ParameterType == CrcParameterType.Boolean)
                {
                    if (string.IsNullOrEmpty(choiceLoop.SingleValue) || choiceLoop.SingleValue == "True" ||
                        choiceLoop.SingleValue == "False")
                    {
                        defnMatch.ParameterChoice = choiceLoop.DeepClone();
                        continue;
                    }
                    res.Complaints.Add(String.Format("Could not parse boolean {0} into Param {1}",
                                                     choiceLoop.SingleValue, choiceLoop.Name));
                }
                else if (defnMatch.ParameterType == CrcParameterType.Text)
                {
                    defnMatch.ParameterChoice = choiceLoop.DeepClone();
                    continue;
                }
                else if (defnMatch.ValidValues.Count() > 0)
                {
                    bool valuesOK = true;
                    foreach (string chosenValue in choiceLoop.Values)
                    {
                        if (defnMatch.ValidValues.FirstOrDefault(vv => vv.Value == chosenValue) == null)
                        {
                            res.Complaints.Add(String.Format("Could not apply value {0} into Param {1} because it is not a valid option",
                                                             chosenValue, choiceLoop.Name));
                            valuesOK = false;
                        }
                    }
                    if (valuesOK)
                    {
                        defnMatch.ParameterChoice = choiceLoop.DeepClone();
                        continue;
                    }
                }
            }
            res.MappingValid = (res.Complaints.Count() == 0);
            return(res);
        }
        public void CanMapSingleSelectParam()
        {
            var repDefn = this.MakeTestReportDefn();
            var choiceColl = new CrcParameterChoiceCollection();
            var singleSelect = new CrcParameterChoice();
            singleSelect.Name = "PretendSingleSelect";
            singleSelect.SingleValue = "S2";
            choiceColl.ParameterChoiceList.Add(singleSelect);

            var mapper = new CrcParameterChoiceMapper();
            var mapResult = mapper.MapParameterChoices(repDefn, choiceColl);

            Assert.IsTrue(mapResult.MappingValid);
            Assert.AreEqual(0, mapResult.Complaints.Count());

            var singleParamDefn = repDefn.ParameterDefinitions.FirstOrDefault(p => p.Name == "PretendSingleSelect");
            Assert.IsNotNull(singleParamDefn);
            Assert.IsNotNull(singleParamDefn.ParameterChoice);
            Assert.AreEqual("S2", singleParamDefn.ParameterChoice.SingleValue);
        }
        public void CanMapMultiSelectParam()
        {
            var repDefn = this.MakeTestReportDefn();
            var choiceColl = new CrcParameterChoiceCollection();
            var multiSelect = new CrcParameterChoice();
            multiSelect.Name = "PretendMultiSelect";
            multiSelect.Values = new List<string>() { "M2", "M3" };

            choiceColl.ParameterChoiceList.Add(multiSelect);

            var mapper = new CrcParameterChoiceMapper();
            var mapResult = mapper.MapParameterChoices(repDefn, choiceColl);

            Assert.IsTrue(mapResult.MappingValid);
            Assert.AreEqual(0, mapResult.Complaints.Count());

            var multiParamDefn = repDefn.ParameterDefinitions.FirstOrDefault(p => p.Name == "PretendMultiSelect");
            Assert.IsNotNull(multiParamDefn);
            Assert.IsNotNull(multiParamDefn.ParameterChoice);
            Assert.AreEqual(2, multiParamDefn.ParameterChoice.Values.Count());
            Assert.IsNotNull(multiParamDefn.ParameterChoice.Values.FirstOrDefault(v => v == "M2"));
            Assert.IsNotNull(multiParamDefn.ParameterChoice.Values.FirstOrDefault(v => v == "M3"));
        }
        public void CanMapDetectWrongName()
        {
            var repDefn = this.MakeTestReportDefn();
            var choiceColl = new CrcParameterChoiceCollection();
            var multiSelect = new CrcParameterChoice();
            multiSelect.Name = "WrongName";
            multiSelect.Values = new List<string>() { "M2", "M3" };

            choiceColl.ParameterChoiceList.Add(multiSelect);

            var mapper = new CrcParameterChoiceMapper();
            var mapResult = mapper.MapParameterChoices(repDefn, choiceColl);

            Assert.IsFalse(mapResult.MappingValid);
            Assert.IsTrue(mapResult.Complaints.Contains("Param WrongName not found"));
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Applies the specified choices to the already-existing report definition
        /// and updates the dependant parameters
        /// </summary>
        public void RefreshDependantParameters(CrcReportDefinition repDefn, CrcParameterChoiceCollection newChoices)
        {
            var mapResult = repDefn.MapParameterChoices(newChoices);
            if (!mapResult.MappingValid)
            {
                // todo - friendlier message back to ui
                throw new ApplicationException(string.Format("invalid params - could not map supplied values to definitions for report {0}. complaints: {1}",
                    repDefn.DisplayName, string.Join(", ", mapResult.Complaints.ToArray())));
            }
            var conv = new CrcParameterConverter();
            List<ParameterValue> valueList = conv.GetParametersValuesForSsrsWebService(repDefn);
            // get new params from web service
            ReportingService2005Soap rService = m_ssrsSoapClientFactory.MakeSsrsSoapClient();
            logger.DebugFormat("RefreshDependantParameters: rep {0} calling WS to get new validvalid. Passing {1} values", repDefn.DisplayName, valueList.Count());

            var grpRequest = new GetReportParametersRequest(repDefn.ReportPath, null, true, valueList.ToArray(), null);
            var grpResponse = rService.GetReportParameters(grpRequest);

            // work out which params to refresh
            List<string> paramsToRefresh = new List<string>();
            foreach (string updatedParam in newChoices.ParameterChoiceList.Select(p => p.Name))
            {
                var paramDefn = repDefn.ParameterDefinitions.First(p => p.Name == updatedParam);
                paramsToRefresh = paramsToRefresh.Union(paramDefn.DependantParameterNames).ToList();
            }
            logger.DebugFormat("RefreshDependantParameters: rep {0} based on choices, have {1} parameters that need refreshing", repDefn.DisplayName, paramsToRefresh.Count());

            var refresher = new CrcParameterRefresher();
            foreach (string paramLoop in paramsToRefresh)
            {
                var paramDefn = repDefn.ParameterDefinitions.First(p => p.Name == paramLoop);
                var latestParamDetails = grpResponse.Parameters.FirstOrDefault(p => p.Name == paramLoop);
                if (latestParamDetails == null)
                    throw new ApplicationException(String.Format("Was expecting web service to return new details for parameter {0} but none found",
                        paramLoop));
                refresher.RefreshParameter(paramDefn, latestParamDetails);
            }
        }
Ejemplo n.º 13
0
        public void CanRefreshDependantParameters_Simple()
        {
            // make report defn
            var repDefn = new CrcReportDefinition();
            repDefn.ReportPath = "Test/Report";
            var pd1 = new CrcParameterDefinition();
            pd1.Name = "ParamOne";
            pd1.ValidValues.Add(new CrcValidValue() { Label = "Label1", Value = "Value1" });
            pd1.ValidValues.Add(new CrcValidValue() { Label = "Label2", Value = "Value2" });
            pd1.ValidValues.Add(new CrcValidValue() { Label = "Label3", Value = "Value3" });
            pd1.DependantParameterNames.Add("ParamTwo");
            var pd2 = new CrcParameterDefinition();
            pd2.Name = "ParamTwo";
            pd2.ValidValues.Add(new CrcValidValue() { Label = "SubLabel1_1", Value = "SubValue1_1" });
            pd2.ValidValues.Add(new CrcValidValue() { Label = "SubLabel1_2", Value = "SubValue1_2" });
            pd2.ValidValues.Add(new CrcValidValue() { Label = "SubLabel2_1", Value = "SubValue2_1" });
            pd2.ValidValues.Add(new CrcValidValue() { Label = "SubLabel2_2", Value = "SubValue2_2" });
            pd2.ValidValues.Add(new CrcValidValue() { Label = "SubLabel3_1", Value = "SubValue3_1" });
            repDefn.ParameterDefinitions.Add(pd1);
            repDefn.ParameterDefinitions.Add(pd2);

            // make choice collection
            var choiceCollection = new CrcParameterChoiceCollection();
            var paramChoice = new CrcParameterChoice("ParamOne");
            paramChoice.Values.Add("Value2");
            choiceCollection.ParameterChoiceList.Add( paramChoice);

            // make server side params to return
            ReportParameter p1 = new ReportParameter();
            p1.Name = "ParamOne";
            p1.ValidValues = new ValidValue[]{ new ValidValue(){Label = "Label1", Value = "Value1"},
                                    new ValidValue(){Label = "Label2", Value = "Value2"},
                                    new ValidValue(){Label = "Label3", Value = "Value3"}};
            p1.DefaultValues = new string[] { };
            ReportParameter p2 = new ReportParameter();
            p2.Name = "ParamTwo";
            p2.ValidValues = new ValidValue[]{ new ValidValue(){Label = "SubLabel2_1", Value = "SubValue2_1"},
                                    new ValidValue(){Label = "SubLabel2_2", Value = "SubValue2_2"}};
            p2.DefaultValues = new string[] { };
            ReportParameter[] paramArrayToReturn = new ReportParameter[] { p1, p2 };

            // make mocks for ccs
            var mf = new TestDoubles.MockSsrsWebServiceFactory();
            var soapClientFactory = mf.MakeMockSoapClientFactory(
                        mf.MakeMockReportingService2005Soap("Value2", paramArrayToReturn));

            var cacheMock = MockRepository.GenerateMock<CrcCacheManager>();
            var configMock = MockRepository.GenerateMock<CrcExtraConfiguration>();

            // make ccs
            var ccs = new CrissCrossServices(soapClientFactory, cacheMock, configMock, null);

            ccs.RefreshDependantParameters(repDefn, choiceCollection);

            Assert.AreEqual(2,repDefn.ParameterDefinitions.Count());
            var pd1check = repDefn.ParameterDefinitions.FirstOrDefault(p => p.Name == "ParamOne");
            Assert.IsNotNull(pd1check);
            Assert.AreEqual("Value2", pd1check.ParameterChoice.SingleValue);
            var pd2check = repDefn.ParameterDefinitions.FirstOrDefault(p => p.Name == "ParamTwo");
            Assert.IsNotNull(pd2check);
            Assert.AreEqual(2, pd2check.ValidValues.Count());
            Assert.IsNotNull(pd2check.ValidValues.FirstOrDefault( vv => vv.Value == "SubValue2_1"));
            Assert.IsNotNull(pd2check.ValidValues.FirstOrDefault( vv => vv.Value == "SubValue2_2"));
        }
Ejemplo n.º 14
0
        public void ApplyParameterDefaults(rws.ReportParameter[] wsReportParameters, CrcReportDefinition repDef)
        {
            // first build up a choicecollection from the defaults
            var choiceCol = new CrcParameterChoiceCollection();
            foreach (var paramLoop in wsReportParameters)
            {
                if (paramLoop.DefaultValues != null)
                {
                    var crcChoice = new CrcParameterChoice(paramLoop.Name);
                    foreach (string valString in paramLoop.DefaultValues)
                        crcChoice.Values.Add(valString);

                    choiceCol.ParameterChoiceList.Add(crcChoice);
                }
            }
            // now apply it to the repdef
            var mapper = new CrcParameterChoiceMapper();
            var mapResult = mapper.MapParameterChoices(repDef, choiceCol);
            if (!mapResult.MappingValid)
                throw new ApplicationException(string.Format("Could not map report defaults for report {0}. Problems: {1}",
                    repDef.DisplayName, string.Join(", ", mapResult.Complaints.ToArray())));
        }