-
Notifications
You must be signed in to change notification settings - Fork 108
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
format FareProduct amount as currency
When exporting to CSV, FareProduct amount should use the appropriate number of decimal places based on the currency specified. Introduces FareAmountFieldMappingFactory to do so.
- Loading branch information
Showing
5 changed files
with
346 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
...c/main/java/org/onebusaway/gtfs/serialization/mappings/FareAmountFieldMappingFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* Copyright (C) 2024 Sound Transit | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.onebusaway.gtfs.serialization.mappings; | ||
|
||
import java.text.DecimalFormat; | ||
import java.text.DecimalFormatSymbols; | ||
import java.text.NumberFormat; | ||
import java.util.Currency; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
|
||
import org.onebusaway.csv_entities.CsvEntityContext; | ||
import org.onebusaway.csv_entities.exceptions.CsvEntityException; | ||
import org.onebusaway.csv_entities.schema.AbstractFieldMapping; | ||
import org.onebusaway.csv_entities.schema.BeanWrapper; | ||
import org.onebusaway.csv_entities.schema.EntitySchemaFactory; | ||
import org.onebusaway.csv_entities.schema.FieldMapping; | ||
import org.onebusaway.csv_entities.schema.FieldMappingFactory; | ||
|
||
public class FareAmountFieldMappingFactory implements FieldMappingFactory { | ||
|
||
|
||
public FieldMapping createFieldMapping(EntitySchemaFactory schemaFactory, | ||
Class<?> entityType, String csvFieldName, String objFieldName, | ||
Class<?> objFieldType, boolean required) { | ||
|
||
return new FareAmountFieldMapping(entityType, csvFieldName, objFieldName, required); | ||
} | ||
|
||
private static class FareAmountFieldMapping extends AbstractFieldMapping { | ||
|
||
public FareAmountFieldMapping(Class<?> entityType, String csvFieldName, | ||
String objFieldName, boolean required) { | ||
super(entityType, csvFieldName, objFieldName, required); | ||
} | ||
|
||
@Override | ||
public void translateFromObjectToCSV(CsvEntityContext context, | ||
BeanWrapper object, Map<String, Object> csvValues) { | ||
|
||
String currencyString = (String) object.getPropertyValue("currency"); | ||
Currency currency = Currency.getInstance(currencyString); | ||
Float amount = (Float) object.getPropertyValue(_objFieldName); | ||
|
||
DecimalFormat formatter = (DecimalFormat) NumberFormat.getCurrencyInstance(Locale.US); | ||
formatter.setCurrency(currency); | ||
|
||
// remove "$", "¥", "₹" and other currency symbols from the output | ||
DecimalFormatSymbols symbols = formatter.getDecimalFormatSymbols(); | ||
symbols.setCurrencySymbol(""); | ||
formatter.setDecimalFormatSymbols(symbols); | ||
formatter.setMaximumFractionDigits(currency.getDefaultFractionDigits()); | ||
|
||
csvValues.put(_csvFieldName, formatter.format(amount)); | ||
} | ||
|
||
@Override | ||
public void translateFromCSVToObject(CsvEntityContext context, Map<String, Object> csvValues, BeanWrapper object) | ||
throws CsvEntityException { | ||
|
||
if (isMissingAndOptional(csvValues)) | ||
return; | ||
|
||
Object value = csvValues.get(_csvFieldName); | ||
|
||
Float amount = (float) value; | ||
object.setPropertyValue(_objFieldName, amount); | ||
} | ||
} | ||
} |
121 changes: 121 additions & 0 deletions
121
onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FareProductWriterTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/** | ||
* Copyright (C) 2024 Sound Transit <[email protected]> | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.onebusaway.gtfs.serialization; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.Scanner; | ||
|
||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.onebusaway.gtfs.impl.FileSupport; | ||
import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; | ||
import org.onebusaway.gtfs.model.AgencyAndId; | ||
import org.onebusaway.gtfs.model.FareProduct; | ||
|
||
public class FareProductWriterTest { | ||
private FileSupport _support = new FileSupport(); | ||
private File _tmpDirectory; | ||
|
||
@BeforeEach | ||
public void setup() throws IOException { | ||
_tmpDirectory = File.createTempFile("FareProductWriterTest-", "-tmp"); | ||
if (_tmpDirectory.exists()) | ||
_support.deleteFileRecursively(_tmpDirectory); | ||
_tmpDirectory.mkdirs(); | ||
_support.markForDeletion(_tmpDirectory); | ||
} | ||
|
||
@AfterEach | ||
public void teardown() { | ||
_support.cleanup(); | ||
} | ||
|
||
@Test | ||
public void testWriteAmountWithCorrectDecimalPlaces() throws IOException { | ||
|
||
GtfsWriter writer = new GtfsWriter(); | ||
writer.setOutputLocation(_tmpDirectory); | ||
|
||
FareProduct fp = new FareProduct(); | ||
String agencyId = "a1"; | ||
String fareProductId = "fp1"; | ||
AgencyAndId fpAgencyAndId = new AgencyAndId(agencyId, fareProductId); | ||
float floatCurrency = 4.7f; | ||
String formattedCurrency = "4.70"; | ||
|
||
fp.setId(fpAgencyAndId); | ||
fp.setFareProductId(fpAgencyAndId); | ||
fp.setCurrency("USD"); | ||
fp.setAmount(floatCurrency); | ||
|
||
writer.handleEntity(fp); | ||
|
||
writer.close(); | ||
|
||
GtfsReader reader = new GtfsReader(); | ||
reader.setDefaultAgencyId(agencyId); | ||
reader.setInputLocation(_tmpDirectory); | ||
|
||
Scanner scan = new Scanner(new File(_tmpDirectory + "/fare_products.txt")); | ||
|
||
Boolean onHeader = true; | ||
Boolean containsExpected = false; | ||
while(scan.hasNext()){ | ||
String line = scan.nextLine(); | ||
if (onHeader) { | ||
onHeader = false; | ||
} else { | ||
if (line.contains(formattedCurrency)) { | ||
containsExpected = true; | ||
} | ||
} | ||
} | ||
scan.close(); | ||
|
||
assertTrue(containsExpected, "Line in fare_products.txt did not contain formatted currency amount 4.70"); | ||
|
||
GtfsRelationalDaoImpl dao = new GtfsRelationalDaoImpl(); | ||
reader.setEntityStore(dao); | ||
|
||
reader.readEntities(FareProduct.class); | ||
|
||
FareProduct fareProductFromFile = dao.getAllFareProducts().iterator().next(); | ||
|
||
assertEquals(fareProductFromFile.getAmount(), floatCurrency); | ||
} | ||
|
||
public static void deleteFileRecursively(File file) { | ||
|
||
if (!file.exists()) | ||
return; | ||
|
||
if (file.isDirectory()) { | ||
File[] files = file.listFiles(); | ||
if (files != null) { | ||
for (File child : files) | ||
deleteFileRecursively(child); | ||
} | ||
} | ||
|
||
file.delete(); | ||
} | ||
} |
128 changes: 128 additions & 0 deletions
128
...st/java/org/onebusaway/gtfs/serialization/mappings/FareAmountFieldMappingFactoryTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/** | ||
* Copyright (C) 2024 Sound Transit | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.onebusaway.gtfs.serialization.mappings; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.onebusaway.csv_entities.CsvEntityContextImpl; | ||
import org.onebusaway.csv_entities.schema.BeanWrapperFactory; | ||
import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory; | ||
import org.onebusaway.csv_entities.schema.DefaultFieldMapping; | ||
import org.onebusaway.csv_entities.schema.FieldMapping; | ||
import org.onebusaway.gtfs.model.FareProduct; | ||
import org.onebusaway.gtfs.serialization.GtfsEntitySchemaFactory; | ||
|
||
public class FareAmountFieldMappingFactoryTest { | ||
|
||
private FieldMapping _fieldMapping; | ||
|
||
@BeforeEach | ||
public void before() { | ||
_fieldMapping = buildFieldMapping(); | ||
} | ||
|
||
@Test | ||
public void testTranslateFromCSVToObject() { | ||
Map<String, Object> csvValues = new HashMap<String, Object>(); | ||
csvValues.put("amount", 47.1234f); | ||
csvValues.put("currency", "USD"); | ||
FareProduct fp = new FareProduct(); | ||
_fieldMapping.translateFromCSVToObject(new CsvEntityContextImpl(), csvValues, BeanWrapperFactory.wrap(fp)); | ||
assertEquals(47.1234, fp.getAmount(), 0.00001); | ||
} | ||
|
||
@Test | ||
public void testTranslateFromObjectToCSV() { | ||
FareProduct fp = new FareProduct(); | ||
fp.setAmount(4.7f); | ||
fp.setCurrency("USD"); | ||
Map<String, Object> csvValues = new HashMap<String, Object>(); | ||
|
||
_fieldMapping.translateFromObjectToCSV(new CsvEntityContextImpl(), BeanWrapperFactory.wrap(fp), csvValues); | ||
assertEquals("4.70", csvValues.get("amount")); | ||
} | ||
|
||
@Test | ||
public void testTranslateFromObjectToCSVCents() { | ||
FareProduct fp = new FareProduct(); | ||
fp.setAmount(4.75f); | ||
fp.setCurrency("USD"); | ||
Map<String, Object> csvValues = new HashMap<String, Object>(); | ||
|
||
_fieldMapping.translateFromObjectToCSV(new CsvEntityContextImpl(), BeanWrapperFactory.wrap(fp), csvValues); | ||
assertEquals("4.75", csvValues.get("amount")); | ||
} | ||
|
||
@Test | ||
public void testTranslateFromObjectToCSVCentsAndMore() { | ||
FareProduct fp = new FareProduct(); | ||
fp.setAmount(4.123f); | ||
fp.setCurrency("USD"); | ||
Map<String, Object> csvValues = new HashMap<String, Object>(); | ||
|
||
_fieldMapping.translateFromObjectToCSV(new CsvEntityContextImpl(), BeanWrapperFactory.wrap(fp), csvValues); | ||
assertEquals("4.12", csvValues.get("amount")); | ||
} | ||
|
||
@Test | ||
public void testTranslateFromObjectToCSVNonUSD() { | ||
FareProduct fp = new FareProduct(); | ||
fp.setAmount(0.7f); | ||
fp.setCurrency("EUR"); | ||
Map<String, Object> csvValues = new HashMap<String, Object>(); | ||
|
||
_fieldMapping.translateFromObjectToCSV(new CsvEntityContextImpl(), BeanWrapperFactory.wrap(fp), csvValues); | ||
assertEquals("0.70", csvValues.get("amount")); | ||
} | ||
|
||
@Test | ||
public void testTranslateFromObjectToCSVWhole() { | ||
FareProduct fp = new FareProduct(); | ||
fp.setAmount(4f); | ||
fp.setCurrency("USD"); | ||
Map<String, Object> csvValues = new HashMap<String, Object>(); | ||
|
||
_fieldMapping.translateFromObjectToCSV(new CsvEntityContextImpl(), BeanWrapperFactory.wrap(fp), csvValues); | ||
assertEquals("4.00", csvValues.get("amount")); | ||
} | ||
|
||
@Test | ||
public void testTranslateFromObjectToCSVNoDecimal() { | ||
FareProduct fp = new FareProduct(); | ||
fp.setAmount(4f); | ||
fp.setCurrency("VND"); | ||
Map<String, Object> csvValues = new HashMap<String, Object>(); | ||
|
||
_fieldMapping.translateFromObjectToCSV(new CsvEntityContextImpl(), BeanWrapperFactory.wrap(fp), csvValues); | ||
assertEquals("4", csvValues.get("amount")); | ||
|
||
fp.setAmount(5.2f); | ||
_fieldMapping.translateFromObjectToCSV(new CsvEntityContextImpl(), BeanWrapperFactory.wrap(fp), csvValues); | ||
assertEquals("5", csvValues.get("amount"), "Amount did not get rounded to nearest whole number for currency that doesn't use decimals"); | ||
} | ||
|
||
private FieldMapping buildFieldMapping() { | ||
FareAmountFieldMappingFactory factory = new FareAmountFieldMappingFactory(); | ||
DefaultEntitySchemaFactory schemaFactory = GtfsEntitySchemaFactory.createEntitySchemaFactory(); | ||
return factory.createFieldMapping(schemaFactory, FareProduct.class, "amount", | ||
"amount", Float.TYPE, true); | ||
} | ||
} |