@@ -90,6 +90,9 @@ public class UserDataFormatter {
9090 /** Pattern that matches all uppercase characters. */
9191 private static final Pattern ALL_UPPERCASE_CHARS_PATTERN = Pattern .compile ("^[A-Z]+$" );
9292
93+ /** Pattern that matches all non-alphanumeric characters, except spaces. */
94+ private static final Pattern SYMBOL_PATTERN = Pattern .compile ("[^\\ p{L}\\ p{N}\\ s]" );
95+
9396 private UserDataFormatter (MessageDigest sha256Digest ) {
9497 this .sha256Digest = sha256Digest ;
9598 }
@@ -267,6 +270,55 @@ public String formatPostalCode(String postalCode) {
267270 return postalCode ;
268271 }
269272
273+ /**
274+ * Returns the normalized and formatted location string.
275+ *
276+ * @param value the string to format
277+ * @param label the label used for error messages
278+ * @throws IllegalArgumentException if {@code value} is invalid. Examples of an invalid value
279+ * include a {@code null}, blank, or empty string.
280+ */
281+ private String formatLocationString (String value , String label ) {
282+ Preconditions .checkArgument (value != null , "Null %s" , label );
283+ value = value .trim ().toLowerCase (LOCALE );
284+ value = SYMBOL_PATTERN .matcher (value ).replaceAll ("" );
285+ Preconditions .checkArgument (!value .isEmpty (), "Empty or blank %s" , label );
286+ return value ;
287+ }
288+
289+ /**
290+ * Returns the provided address line, normalized and formatted.
291+ *
292+ * @param addressLine the address line to format
293+ * @throws IllegalArgumentException if {@code addressLine} is invalid. Examples of an invalid value
294+ * include a {@code null}, blank, or empty string.
295+ */
296+ public String formatAddressLine (String addressLine ) {
297+ return formatLocationString (addressLine , "address line" );
298+ }
299+
300+ /**
301+ * Returns the provided city, normalized and formatted.
302+ *
303+ * @param city the city to format
304+ * @throws IllegalArgumentException if {@code city} is invalid. Examples of an invalid value
305+ * include a {@code null}, blank, or empty string.
306+ */
307+ public String formatCity (String city ) {
308+ return formatLocationString (city , "city" );
309+ }
310+
311+ /**
312+ * Returns the provided administrative area, normalized and formatted.
313+ *
314+ * @param administrativeArea the administrative area to format
315+ * @throws IllegalArgumentException if {@code administrativeArea} is invalid. Examples of an invalid value
316+ * include a {@code null}, blank, or empty string.
317+ */
318+ public String formatAdministrativeArea (String administrativeArea ) {
319+ return formatLocationString (administrativeArea , "administrative area" );
320+ }
321+
270322 /**
271323 * Returns the SHA-256 hash of the provided string.
272324 *
@@ -474,6 +526,75 @@ public String processPostalCode(String postalCode) {
474526 return formatPostalCode (postalCode );
475527 }
476528
529+ /**
530+ * Formats the address line, hashes, and encodes using the specified encoding.
531+ *
532+ * <p>This is a convenience method that combines {@link #formatAddressLine(String)}, {@link
533+ * #hashString(String)}, and either {@link #hexEncode(byte[])} or {@link #base64Encode(byte[])}
534+ * into a single call.
535+ *
536+ * @return the address line, formatted, hashed, and encoded for the {@code AddressInfo.address_line}
537+ * field in the API.
538+ * @throws IllegalArgumentException if the address line is invalid
539+ */
540+ public String processAddressLine (String addressLine , Encoding encoding ) {
541+ return hashAndEncode (formatAddressLine (addressLine ), encoding );
542+ }
543+
544+ /**
545+ * Formats the address line, hashes, base 64-encodes, encrypts, and encodes using the specified
546+ * encoding.
547+ *
548+ * <p>This is a convenience method that combines {@link #formatAddressLine(String)}, {@link
549+ * #hashString(String)}, {@link #base64Encode(byte[])}, {@link Encrypter#encrypt(String)}, and
550+ * either {@link #hexEncode(byte[])} or {@link #base64Encode(byte[])} into a single call.
551+ *
552+ * @return the address line, formatted, hashed, encrypted, and encoded for the {@code
553+ * AddressInfo.address_line} field in the API.
554+ * @throws IllegalArgumentException if the address line is invalid
555+ * @throws NullPointerException if {@code encrypter} is null
556+ */
557+ public String processAddressLine (String addressLine , Encoding encoding , Encrypter encrypter ) {
558+ Preconditions .checkNotNull (encrypter , "Null encrypter" );
559+ return hashEncodeAndEncrypt (formatAddressLine (addressLine ), encoding , encrypter );
560+ }
561+
562+ /**
563+ * Processes the city.
564+ *
565+ * <p>This is a convenience method that simply calls {@link #formatCity(String)}. This
566+ * method exists for consistency so that all data types have a {@code process...} method.
567+ *
568+ * <p>Doesn't require an {@link Encoding} since cities shouldn't be encoded or hashed.
569+ *
570+ * <p>There is no overloaded counterpart that takes an {@link Encrypter} since cities
571+ * shouldn't be encrypted.
572+ *
573+ * @return the city, formatted for the {@code AddressInfo.city} field in the API.
574+ * @throws IllegalArgumentException if the city is invalid
575+ */
576+ public String processCity (String city ) {
577+ return formatCity (city );
578+ }
579+
580+ /**
581+ * Processes the administrative area.
582+ *
583+ * <p>This is a convenience method that simply calls {@link #formatAdministrativeArea(String)}. This
584+ * method exists for consistency so that all data types have a {@code process...} method.
585+ *
586+ * <p>Doesn't require an {@link Encoding} since administrative areas shouldn't be encoded or hashed.
587+ *
588+ * <p>There is no overloaded counterpart that takes an {@link Encrypter} since administrative areas
589+ * shouldn't be encrypted.
590+ *
591+ * @return the administrative area, formatted for the {@code AddressInfo.administrative_area} field in the API.
592+ * @throws IllegalArgumentException if the administrative area is invalid
593+ */
594+ public String processAdministrativeArea (String administrativeArea ) {
595+ return formatAdministrativeArea (administrativeArea );
596+ }
597+
477598 /**
478599 * Hashes the string and then encodes using the specified encoding.
479600 *
0 commit comments