| /* |
| * Copyright (C) 2010 Google Inc. |
| * |
| * 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 com.google.i18n.addressinput.common; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Configuration options for the address input widget used to control the visibility and interaction |
| * of specific fields to suit specific use cases (eg, collecting business addresses, collecting |
| * addresses for credit card verification etc...). |
| * <p> |
| * When form options are passed to the address widget a snapshot is taken and any further changes to |
| * the options are ignored. |
| * <p> |
| * This design is somewhat like using a builder but has the advantage that the caller only sees the |
| * outer (mutable) type and never needs to know about the "built" snapshot. This reduces the public |
| * API footprint and simplifies usage of this class. |
| */ |
| // This is an external class and part of the widget's public API. |
| public final class FormOptions { |
| |
| // These fields must never be null). |
| private Set<AddressField> hiddenFields = EnumSet.noneOf(AddressField.class); |
| private Set<AddressField> readonlyFields = EnumSet.noneOf(AddressField.class); |
| private Set<String> blacklistedRegions = new HashSet<String>(); |
| // Key is ISO 2-letter region code. |
| private Map<String, List<AddressField>> customFieldOrder = |
| new HashMap<String, List<AddressField>>(); |
| |
| /** Creates an empty, mutable, form options instance. */ |
| public FormOptions() { |
| } |
| |
| /** |
| * Hides the given address field. Calls to this method <strong>are cumulative</strong>. Fields |
| * which are specified here but not part of a country's specified fields will be ignored. |
| * <p> |
| * Note also that hiding fields occurs after custom ordering has been applied, although combining |
| * these two features is not generally recommended due to the confusion it is likely to cause. |
| */ |
| public FormOptions setHidden(AddressField field) { |
| hiddenFields.add(field); |
| return this; |
| } |
| |
| /** |
| * Sets the given address field as read-only. Calls to this method <strong>are cumulative |
| * </strong>. Fields which are specified here but not part of a country's specified fields will be |
| * ignored. |
| * <p> |
| * This method is j2objc- & iOS API friendly as the signature does not expose varargs / Java |
| * arrays or collections. |
| */ |
| public FormOptions setReadonly(AddressField field) { |
| readonlyFields.add(field); |
| return this; |
| } |
| |
| /** |
| * Sets the order of address input fields for the given ISO 3166-1 two letter country code. |
| * <p> |
| * Input fields affected by custom ordering will be shown in the widget in the order they are |
| * given to this method (for the associated region code). Fields which are visible for a region, |
| * but which are not specified here, will appear in their original position in the form. For |
| * example, if a region defines the following fields: |
| * <pre> |
| * [ RECIPIENT -> ORGANIZATION -> STREET_ADDRESS -> LOCALITY -> ADMIN_AREA -> COUNTRY ] |
| * </pre> |
| * and the custom ordering for that region is (somewhat contrived): |
| * <pre> |
| * [ ORGANIZATION -> COUNTRY -> RECIPIENT ] |
| * </pre> |
| * Then the visible order of the input fields will be: |
| * <pre> |
| * [ ORGANIZATION -> COUNTRY -> STREET_ADDRESS -> LOCALITY -> ADMIN_AREA -> RECIPIENT ] |
| * </pre> |
| * <ul> |
| * <li>Fields not specified in the custom ordering (STREET_ADDRESS, LOCALITY, ADMIN_AREA) |
| * remain in their original, absolute, positions. |
| * <li>Custom ordered fields are re-positioned such that their relative order is now as |
| * specified (but other, non custom-ordered, fields can appear between them). |
| * </ul> |
| * <p> |
| * If the custom order contains a field which is not present for the specified region, it is |
| * silently ignored. Setting a custom ordering can never be used as a way to add fields for a |
| * region. |
| * <p> |
| * Typically this feature is used to reverse things like RECIPIENT and ORGANIZATION for certain |
| * business related use cases. It should not be used to "correct" perceived bad field ordering |
| * or make different countries "more consistent with each other". |
| */ |
| public FormOptions setCustomFieldOrder(String regionCode, AddressField... fields) { |
| // TODO: Consider checking the given region code for validity against RegionDataConstants. |
| List<AddressField> fieldList = Collections.unmodifiableList(Arrays.asList(fields)); |
| if (fieldList.size() > 0) { |
| if (EnumSet.copyOf(fieldList).size() != fieldList.size()) { |
| throw new IllegalArgumentException("duplicate address field: " + fieldList); |
| } |
| customFieldOrder.put(regionCode, fieldList); |
| } else { |
| customFieldOrder.remove(regionCode); |
| } |
| return this; |
| } |
| |
| /** |
| * Blacklist the given CLDR (Common Locale Data Repository) region (country) code |
| * indicating countries that for legal or other reasons should not be available. |
| * <p> |
| * Calls are cumulative, call this method once for each region that needs to be blacklisted. |
| * <p> |
| * We reserve the right to change this API from taking individual regions to taking a set. |
| */ |
| public FormOptions blacklistRegion(String regionCode) { |
| if (regionCode == null) { |
| throw new NullPointerException(); |
| } |
| // TODO(addresswidget-team): Add region code validation against RegionDataConstants. |
| blacklistedRegions.add(Util.toUpperCaseLocaleIndependent(regionCode)); |
| return this; |
| } |
| |
| /** Returns an immutable snapshot of the current state of the form options. */ |
| public Snapshot createSnapshot() { |
| return new Snapshot(this); |
| } |
| |
| /** An immutable snapshot of a {@code FormOptions} instance. */ |
| public static class Snapshot { |
| private final Set<AddressField> hiddenFields; |
| private final Set<AddressField> readonlyFields; |
| private final Set<String> blacklistedRegions; |
| private final Map<String, List<AddressField>> customFieldOrder; |
| |
| Snapshot(FormOptions options) { |
| this.hiddenFields = Collections.unmodifiableSet(EnumSet.copyOf(options.hiddenFields)); |
| this.readonlyFields = Collections.unmodifiableSet(EnumSet.copyOf(options.readonlyFields)); |
| // Shallow copy as lists are already immutable. |
| this.customFieldOrder = Collections.unmodifiableMap( |
| new HashMap<String, List<AddressField>>(options.customFieldOrder)); |
| this.blacklistedRegions = |
| Collections.unmodifiableSet(new HashSet<String>(options.blacklistedRegions)); |
| } |
| |
| public boolean isHidden(AddressField field) { |
| return hiddenFields.contains(field); |
| } |
| |
| public boolean isReadonly(AddressField field) { |
| return readonlyFields.contains(field); |
| } |
| |
| /** |
| * Gets the overridden field orders with their corresponding region code. Returns null if field |
| * orders for {@code regionCode} is not specified. |
| */ |
| List<AddressField> getCustomFieldOrder(String regionCode) { |
| return customFieldOrder.get(regionCode); |
| } |
| |
| boolean isBlacklistedRegion(String regionCode) { |
| return blacklistedRegions.contains(Util.toUpperCaseLocaleIndependent(regionCode)); |
| } |
| } |
| } |